| /* |
| * Copyright (c) 2000-2001 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it would be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <sys/mman.h> |
| #include <sys/param.h> |
| #include <sys/stat.h> |
| #include <sys/ioctl.h> |
| #include <attr/attributes.h> |
| #include <xfs/handle.h> |
| #include <time.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <dirent.h> |
| #include <utime.h> |
| #include <malloc.h> |
| #include <pthread.h> |
| #include <assert.h> |
| #include <string.h> |
| #include <uuid/uuid.h> |
| #include <xfs/xfs.h> |
| #include <xfs/jdm.h> /* only for util.h */ |
| |
| #include "config.h" |
| |
| #include "types.h" |
| #include "timeutil.h" |
| #include "util.h" /* only for r/w routines, ALIGN_PTR */ |
| #include "cldmgr.h" |
| #include "qlock.h" |
| #include "lock.h" |
| #include "path.h" |
| #include "openutil.h" |
| #include "exit.h" |
| #include "getopt.h" |
| #include "mlog.h" |
| #include "dlog.h" |
| #include "bag.h" |
| #include "node.h" |
| #include "namreg.h" |
| #include "stream.h" |
| #include "global.h" |
| #include "drive.h" |
| #include "media.h" |
| #include "content.h" |
| #include "content_inode.h" |
| #include "inomap.h" |
| #include "dirattr.h" |
| #include "tree.h" |
| #include "inventory.h" |
| #include "mmap.h" |
| #include "arch_xlate.h" |
| #include "win.h" |
| #include "hsmapi.h" |
| |
| /* content.c - manages restore content |
| */ |
| |
| /* structure definitions used locally ****************************************/ |
| |
| #define HOUSEKEEPING_MAGIC 0x686b6d61 |
| /* "hkma" - see the housekeeping_magic field of pers_t below. |
| */ |
| #define HOUSEKEEPING_VERSION 2 |
| /* see the housekeeping_version field of pers_t below. |
| * version 2 changed the size of a gen_t, which caused node_t |
| * to change in size. also p_truncategenpr was added to treepers_t. |
| */ |
| |
| #define WRITE_TRIES_MAX 3 |
| /* retry loop tuning for write(2) workaround |
| */ |
| typedef enum { SYNC_INIT, SYNC_BUSY, SYNC_DONE } sync_t; |
| /* for lock-step synchronization |
| */ |
| typedef struct { xfs_ino_t eg_ino; off64_t eg_off; } egrp_t; |
| /* extent group descriptor |
| */ |
| typedef char label_t[GLOBAL_HDR_STRING_SZ]; |
| /* dump or mobj label |
| */ |
| typedef enum { PURP_SEARCH, PURP_DIR, PURP_NONDIR } purp_t; |
| /* to describe purpose for a media file request. may be for |
| * searching for a dump to restore, for dir restore, or non-dir |
| */ |
| typedef off_t dh_t; |
| /* handles for descriptors in persistent state inventory |
| * encoded as byte offset plus one of descriptor into descriptor |
| * portion of persistent state. plus one so DH_NULL can be zero. |
| */ |
| #define DH_NULL ((dh_t)0) |
| /* NULL inv. descriptor handles, to terminate linked descriptor lists. |
| * must be zero-valued, so memset of pers.s sets freeh to DH_NULL. |
| */ |
| #define DH2F(h) ((pers_file_t *)((char *)descp + (h - 1))) |
| #define DH2O(h) ((pers_obj_t *)((char *)descp + (h - 1))) |
| #define DH2S(h) ((pers_strm_t *)((char *)descp + (h - 1))) |
| #define DH2D(h) ((pers_desc_t *)((char *)descp + (h - 1))) |
| /* convert file, object, and stream inv. descriptor handle into |
| * descriptor pointers |
| */ |
| #define DAU 1 |
| /* number of descriptor pages to allocate when free list exhausted |
| */ |
| #define IBPGINCR 32 |
| /* session inv. restore retry buffer increment |
| */ |
| |
| /* Media state abstraction |
| */ |
| struct Media { |
| drive_t *M_drivep; |
| global_hdr_t *M_grhdrp; |
| drive_hdr_t *M_drhdrp; |
| media_hdr_t *M_mrhdrp; |
| content_hdr_t *M_crhdrp; |
| content_inode_hdr_t *M_scrhdrp; |
| |
| enum { POS_UNKN, /* whenever media file not open */ |
| POS_ATHDR, /* at beginning of media file */ |
| POS_INDIR, /* at beginning of inomap/dirdump */ |
| POS_ATNONDIR, /* at first non-dir file */ |
| POS_END, /* EOM/EOD */ |
| POS_USELESS, /* current object contains nothing useful */ |
| POS_BLANK /* like useless */ |
| } M_pos; |
| /* media positioning info. initially UNKN, set back to |
| * unkn whenever end_read op called. |
| */ |
| ix_t M_fmfix; |
| ix_t M_lmfix; |
| ix_t M_pmfix; |
| bool_t M_pmfixvalpr; |
| purp_t M_mfixpurp; |
| bool_t M_flmfixvalpr; |
| /* the indices within the current media object of the first |
| * and last media files seen, as well as previous last. |
| * invalidated whenever purpose changes or media is changed. |
| * previous (pmfix) not valid until second media file seen. |
| */ |
| ix_t M_fsfix; |
| ix_t M_fsoix; |
| ix_t M_fssix; |
| bool_t M_fsfixvalpr; |
| /* index within the current media object of the first |
| * media file that is part of dump being restored, |
| * and indices of the obj and stream containing that mfile. |
| * invalidated on media change. |
| */ |
| }; |
| |
| typedef struct Media Media_t; |
| |
| |
| /* persistent state - mmapped, has linked lists of dump streams, media |
| * objects, and media files. descriptors for each fit into PERS_DESCSZ |
| * bytes, and are allocated from a common free pool. |
| */ |
| |
| /* persistent media file descriptor |
| */ |
| struct pers_file { |
| dh_t f_nexth; |
| /* singly-linked list of files withing object |
| */ |
| dh_t f_parh; |
| /* parent object |
| */ |
| bool_t f_szvalpr; |
| off64_t f_sz; |
| /* if this info came from an inventory (on-line or on-media), |
| * we know the media file size |
| */ |
| bool_t f_dirtriedpr; |
| /* set if attempted to restore dirs from this media file. |
| * says nothing about success or failure. prevents us from |
| * trying to restore dirs from this media file again. |
| */ |
| bool_t f_valpr; |
| /* following three fields are valid |
| */ |
| egrp_t f_firstegrp; |
| /* first extent group in this media file |
| */ |
| egrp_t f_curegrp; |
| /* next extent group to be restored from this media file. |
| * initially equals f_firstegrp. |
| */ |
| drive_mark_t f_curmark; |
| /* drive manager mark for seeking to current extent group |
| */ |
| bool_t f_nondirdonepr; |
| /* TRUE when non-dirs from this media file completely restored, |
| * or as restored as they can be (some or all lost due to |
| * media corruption). |
| */ |
| bool_t f_nondirskippr; |
| /* no non-dirs are needed from this nmedia file (due to |
| * subtree or interactive selections) |
| */ |
| int f_flags; |
| /* mark terminators and inventories |
| */ |
| bool_t f_underheadpr; |
| /* the drive is currently positioned at or in this media file |
| */ |
| }; |
| |
| /* f_flags |
| */ |
| #define PF_INV (1 << 0) |
| #define PF_TERM (1 << 1) |
| |
| typedef struct pers_file pers_file_t; |
| |
| /* persistent media object descriptor |
| */ |
| struct pers_obj { |
| dh_t o_nexth; |
| /* singly-linked list of objects in stream |
| */ |
| dh_t o_parh; |
| /* parent dump stream descriptor |
| */ |
| dh_t o_cldh; |
| /* head of list of pertinent media files contained in |
| * this media object |
| */ |
| bool_t o_idlabvalpr; |
| /* id and label fields are valid |
| */ |
| uuid_t o_id; |
| /* uuid of media object |
| */ |
| label_t o_lab; |
| /* label of media object |
| */ |
| ix_t o_fmfmix; |
| bool_t o_fmfmixvalpr; |
| /* 0-based index into this mobj's mfiles of first |
| * mfile in the mobj that is part of the dump stream. |
| */ |
| ix_t o_fmfsix; |
| bool_t o_fmfsixvalpr; |
| /* 0-based index into this dump stream's mfiles of first |
| * mfile in the mobj that is part of the dump stream. |
| */ |
| bool_t o_lmfknwnpr; |
| /* TRUE if last media file on object is represented in |
| * children list. |
| */ |
| bool_t o_indrivepr; |
| ix_t o_indriveix; |
| /* TRUE if this object is in a drive, and which drive it is |
| * in. |
| */ |
| }; |
| |
| typedef struct pers_obj pers_obj_t; |
| |
| /* media dump stream descriptor |
| */ |
| struct pers_strm { |
| dh_t s_nexth; |
| /* singly-linked list of streams generated by dump |
| */ |
| dh_t s_cldh; |
| /* head of list of mobjs containing this dstrm's mfiles |
| */ |
| bool_t s_lastobjknwnpr; |
| /* TRUE if if last object in the stream is represented in |
| * children list. |
| */ |
| }; |
| |
| typedef struct pers_strm pers_strm_t; |
| |
| /* media descriptor allocation object (for free list) |
| */ |
| union pers_desc { |
| dh_t d_nexth; |
| /* singly-linked free list of descriptors |
| */ |
| pers_file_t d_file; |
| /* media file descriptor overlay; |
| */ |
| pers_obj_t d_obj; |
| /* media object descriptor overlay; |
| */ |
| pers_strm_t d_strm; |
| /* media stream descriptor overlay; |
| */ |
| }; |
| |
| typedef union pers_desc pers_desc_t; |
| |
| #define PERS_DESCSZ 512 |
| /* size of media object, media file, and media stream descriptors. |
| * need to fit integral number into a page, single allocator |
| * used allocate and free all types . |
| */ |
| |
| /* subtree descriptor - the subtree command line arguments are transcribed |
| * into variable-length descriptors and placed in an integral number of |
| * pages after the persistent header, and before the media descriptor free list. |
| */ |
| #define STDESCALIGN 8 |
| |
| struct stdesc { |
| bool_t std_sensepr; |
| /* TRUE if this is a subtree to INCLUDE, FALSE if EXCLUDE |
| */ |
| off_t std_nextoff; |
| /* offset to next descriptor, in bytes relative to this |
| */ |
| char std_path[1]; |
| /* first character of a NULL-terminated string containing the |
| * the relative subtree pathname |
| */ |
| }; |
| |
| typedef struct stdesc stdesc_t; |
| |
| /* byte span descriptor - registers a span of a file restored. |
| */ |
| struct bytespan { |
| off64_t offset; |
| off64_t endoffset; |
| }; |
| |
| typedef struct bytespan bytespan_t; |
| |
| /* partial restore descriptor - Keeps track of different byte spans restored |
| * for a specific inode. Used to sync operations between restore streams. |
| */ |
| struct partial_rest { |
| xfs_ino_t is_ino; |
| /* inode number */ |
| bytespan_t is_bs[STREAM_SIMMAX]; |
| /* each stream could conceivably be writing to a single |
| * file simultaneously if one file spans all device streams. |
| * Need a record for each possible place in the file. |
| */ |
| }; |
| |
| typedef struct partial_rest partial_rest_t; |
| |
| struct stream_context { |
| bstat_t sc_bstat; |
| char sc_path[2 * MAXPATHLEN]; |
| int sc_fd; |
| int sc_hsmflags; |
| |
| /* |
| * we have to set the owner before we set extended attributes otherwise |
| * capabilities will not be restored correctly as setting the owner with |
| * fchmod will strip the capability attribute from the file. Hence we |
| * need to do this before restoring xattrs and record it so we don't do |
| * it again on completion of file restoration. |
| */ |
| bool_t sc_ownerset; |
| }; |
| |
| typedef struct stream_context stream_context_t; |
| |
| /* persistent state file header - on-disk format information plus |
| * accumulation state (which spans several sessions) and session state. |
| * the latter two have a valid bit, and their fields are not valid until |
| * the valid bit is set. all elements defined such that a bzero results |
| * in a valid initial state. |
| */ |
| struct pers { |
| /* on-disk format information used to verify that xfsrestore |
| * can make sense of the data in xfsrestorehousekeepingdir |
| * when running in cumulative mode or when resuming a restore. |
| * |
| * for backwards/forwards compatibility, this struct must be |
| * the first field! also any changes to the struct must address |
| * compatibility with other xfsrestore versions. |
| */ |
| struct { |
| size32_t housekeeping_magic; |
| /* used to determine if this struct has been |
| * initialized, and whether the machine's |
| * endianness is the same as the previous |
| * invocation. (data written to xfsrestore's |
| * state directory is not converted to an |
| * endian-neutral format since it only persists |
| * for the life of one or more restore sessions.) |
| */ |
| size32_t housekeeping_version; |
| /* version of the data structures used in the |
| * state files in housekeepingdir. this must be |
| * bumped whenever the on-disk format changes. |
| */ |
| size64_t pagesize; |
| /* headers in the persistent state files |
| * are aligned on page size boundaries, so |
| * this cannot change betweeen invocations. |
| */ |
| } v; |
| |
| /* command line arguments from first session, and session |
| * history. |
| */ |
| struct { |
| bool_t valpr; |
| /* not set until a BASE dump has been identified |
| * and validated for restoral, and an attempt has |
| * been made to load the dump inventory into persistent |
| * state, and the namreg and tree abstractions |
| * have been initialized, and the session history |
| * has been initialized and validated. |
| */ |
| char dstdir[MAXPATHLEN]; |
| /* absolute pathname of the destination directory |
| */ |
| bool_t dstdirisxfspr; |
| /* destination directory is an xfs filesystem; xfs-specific |
| * calls can be made when needed. |
| */ |
| ix_t dumpcnt; |
| /* how many dumps have been applied completedly (A1) |
| */ |
| uuid_t lastdumpid; |
| /* uuid of the last dump completely restored (A1) |
| */ |
| label_t lastdumplab; |
| /* label of the last dump completely restored (A1) |
| */ |
| bool_t cumpr; |
| /* is a cumulative restore (-r) |
| */ |
| bool_t interpr; |
| /* interactive mode specified on command line (-i) |
| */ |
| bool_t existpr; |
| /* existing files may not be overwritten (-e) |
| */ |
| bool_t changepr; |
| /* only missing or old files may be overwritten (-E) |
| */ |
| bool_t newerpr; |
| time32_t newertime; |
| /* only files older than example may be overwritten (-n) |
| */ |
| bool_t ownerpr; |
| /* attempt to restore owner/group (-o) |
| */ |
| ix_t stcnt; |
| /* how many subtree args (both inclusive and exclusive) |
| * are recorded in the subtree pages (-s) |
| */ |
| bool_t firststsensepr; |
| bool_t firststsenseprvalpr; |
| /* sense of first subtree arg |
| */ |
| ix_t stpgcnt; |
| /* how many pages following the header page are reserved |
| * for the subtree descriptors |
| */ |
| bool_t restoreextattrpr; |
| /* restore extended attributes |
| */ |
| |
| ix_t parrestcnt; |
| /* Count of partialy restored files. Used to speed |
| * up searches in parrest. |
| */ |
| |
| partial_rest_t parrest[STREAM_SIMMAX * 2 - 2]; |
| /* record of bytes restored to partially restored files. |
| * Max possible is two per stream except the first |
| * drive will never finish another drives file and the |
| * last drive will never leave a file for another to |
| * complete. |
| */ |
| } a; |
| |
| /* session state. |
| */ |
| struct { |
| bool_t valpr; |
| /* until this is true, a resume will ignore (and bzero) |
| * this structure. validate just prior to applying |
| * the directory dump, and after all fields marked (A2) |
| * are correct. invalidate as soon as the session is |
| * complete, and atomically update all fields marked |
| * (A1) at the same time. dirattr abstraction must be |
| * initialized prior to setting this. |
| */ |
| time32_t accumtime; |
| /* for measuring elapsed time of restore |
| */ |
| uuid_t dumpid; |
| /* id of dump currently being applied |
| */ |
| label_t dumplab; |
| /* label of the dump being applied (A2) |
| */ |
| time32_t begintime; |
| /* set when session begun and each time resumed |
| */ |
| bool_t stat_valpr; |
| /* the following stats are not valid until the |
| * first media file header has been read. |
| */ |
| uint64_t stat_inocnt; |
| /* number of non-dir inos to restore during session |
| */ |
| uint64_t stat_inodone; |
| /* number of non-dir inos restored so far |
| */ |
| off64_t stat_datacnt; |
| /* bytes of ordinary files to restore during session |
| */ |
| off64_t stat_datadone; |
| /* bytes of ordinary files restored so far |
| */ |
| ix_t descpgcnt; |
| /* number of pages mapped for pers. media descriptors |
| */ |
| dh_t descfreeh; |
| /* linked list of free media descriptor alloc objs (A2) |
| */ |
| dh_t strmheadh; |
| /* head of singly-linked list of stream descriptors (A2) |
| */ |
| bool_t fullinvpr; |
| /* have discovered and incorporated a full inventory |
| * description into pers. may come from online or a |
| * inventory media file. |
| */ |
| bool_t marknorefdonepr; |
| /* have marked tree nodes as unreferenced by directory |
| * entries, and nulled dirattr handles. |
| */ |
| bool_t dirdonepr; |
| /* have applied all directories from a dirdump. |
| */ |
| bool_t adjrefdonepr; |
| /* have adjusted marking of nodes no longer referenced |
| * by directory entries. |
| */ |
| bool_t inomapsanitizedonepr; |
| /* the inomap needs to b sanitized prior to subtree |
| * or interactive selections |
| */ |
| bool_t stdonepr; |
| /* have applied subtree selections |
| */ |
| bool_t interdonepr; |
| /* have completed interactive subtree dialog |
| */ |
| bool_t treepostdonepr; |
| /* all of the above treep ost-processing steps have |
| * been completed. |
| */ |
| /* |
| * nondir restore done here |
| */ |
| bool_t dirattrdonepr; |
| /* directory attributes have been restored and |
| * directory attributes registry has been deleted |
| */ |
| bool_t orphdeltriedpr; |
| /* removed (or tried to remove) orphanage |
| */ |
| bool_t inomapdelpr; |
| /* deleted session ino map |
| */ |
| } s; |
| }; |
| |
| typedef struct pers pers_t; |
| |
| /* transient state. re-generated during each restore session |
| */ |
| |
| struct tran { |
| time32_t t_starttime; |
| /* for measuring elapsed time of restore session |
| */ |
| size64_t t_dircnt; |
| size64_t t_dirdonecnt; |
| size64_t t_direntcnt; |
| /* for displaying stats on directory reconstruction |
| */ |
| size64_t t_vmsz; |
| /* how much vm may be used. recorded here from main, |
| * passed to tree_init() once we have a valid media |
| * file header |
| */ |
| bool_t t_toconlypr; |
| /* just display table of contents; don't restore files |
| */ |
| bool_t t_noinvupdatepr; |
| /* true if inventory is NOT to be updated when on-media |
| * inventory encountered. |
| */ |
| bool_t t_dumpidknwnpr; |
| /* determined during initialization; if false, set during |
| * per-stream init |
| */ |
| bool_t t_dirattrinitdonepr; |
| bool_t t_namreginitdonepr; |
| bool_t t_treeinitdonepr; |
| /* determinied during initialization, used during |
| * per-stream restore |
| */ |
| uuid_t t_reqdumpid; |
| bool_t t_reqdumpidvalpr; |
| /* uuid of the dump as requested on cmd line |
| */ |
| char * t_reqdumplab; |
| bool_t t_reqdumplabvalpr; |
| /* label of the dump as requested on cmd line |
| */ |
| char *t_hkdir; |
| /* absolute pathname of housekeeping directory |
| */ |
| int t_persfd; |
| /* file descriptor of the persistent state file |
| */ |
| size64_t t_dirdumps; |
| /* bitset of streams which contain a directory dump |
| */ |
| bool_t t_truncategenpr; |
| /* force use of truncated generation numbers |
| */ |
| sync_t t_sync1; |
| /* to single-thread attempt to validate command line |
| * selection of dump with online inventory |
| */ |
| sync_t t_sync2; |
| /* to single-thread dump selection by media scan |
| */ |
| sync_t t_sync3; |
| /* to single-thread attempt to apply dirdump to tree |
| */ |
| sync_t t_sync4; |
| /* to single-thread attempt to do tree post-processing |
| * prior to non-directory restore |
| */ |
| sync_t t_sync5; |
| /* to single-thread cleanup after applying non-dir restore |
| */ |
| qlockh_t t_pilockh; |
| /* to establish critical regions while updating pers |
| * inventory |
| */ |
| }; |
| |
| typedef struct tran tran_t; |
| |
| |
| /* declarations of externally defined global symbols *************************/ |
| |
| extern void usage(void); |
| extern bool_t preemptchk(void); |
| extern char *homedir; |
| extern bool_t pipeline; |
| extern bool_t stdoutpiped; |
| extern char *sistr; |
| extern size_t pgsz; |
| extern size_t pgmask; |
| |
| |
| /* forward declarations of locally defined static functions ******************/ |
| |
| static void toconly_cleanup(void); |
| |
| static Media_t *Media_create(ix_t thrdix); |
| static void Media_indir(Media_t *Mediap); |
| static void Media_indir(Media_t *Mediap); |
| static void Media_atnondir(Media_t *Mediap); |
| static rv_t Media_mfile_next(Media_t *Mediap, |
| purp_t purp, |
| sync_t *donesyncp, |
| dh_t *filehp, |
| global_hdr_t **grhdrpp, |
| drive_hdr_t **drhdrpp, |
| media_hdr_t **mrhdrpp, |
| content_hdr_t **crhdrpp, |
| content_inode_hdr_t **scrhdrpp, |
| drive_t **drivepp, |
| filehdr_t *fhdr); |
| static void Media_end(Media_t *Mediap); |
| static bool_t Media_prompt_change(drive_t *drivep, |
| purp_t purp, |
| bag_t *bagp, |
| bool_t knownholespr, |
| bool_t maybeholespr); |
| |
| static bool_t Inv_validate_cmdline(void); |
| static bool_t dumpcompat(bool_t resumepr, |
| ix_t level, |
| uuid_t baseid, |
| bool_t logpr); |
| static bool_t promptdumpmatch(ix_t thrdix, |
| global_hdr_t *grhdrp, |
| media_hdr_t *mrhdrp, |
| content_hdr_t *crhdrp, |
| content_inode_hdr_t *scrhdrp); |
| |
| static void pi_checkpoint(dh_t fileh, |
| drive_mark_t *drivemarkp, |
| xfs_ino_t ino, |
| off64_t off); |
| static bool_t pi_transcribe(inv_session_t *sessp); |
| static dh_t pi_addfile(Media_t *Mediap, |
| global_hdr_t *grhdrp, |
| drive_hdr_t *drhdrp, |
| media_hdr_t *mrhdrp, |
| content_inode_hdr_t *scrhdrp, |
| drive_t * drivep); |
| static void pi_seestrmend(ix_t strmix); |
| static void pi_seeobjstrmend(ix_t strmix, ix_t mediaix); |
| static xfs_ino_t pi_scanfileendino(dh_t fileh); |
| static bool_t pi_alldone(void); |
| static bag_t * pi_neededobjs_dir_alloc(bool_t *knownholesprp, |
| bool_t *maybeholesprp); |
| static bag_t * pi_neededobjs_nondir_alloc(bool_t *knownholesprp, |
| bool_t *maybeholesprp, |
| bool_t showobjindrivepr, |
| bool_t markskippr); |
| static void pi_neededobjs_free(bag_t *bagp); |
| static void pi_bracketneededegrps(dh_t thisfileh, |
| egrp_t *first_egrp, |
| egrp_t *next_egrp); |
| static void pi_update_stats(off64_t sz); |
| static void pi_hiteod(ix_t strmix, ix_t objix); |
| static void pi_hiteom(ix_t strmix, ix_t objix); |
| static void pi_hitnextdump(ix_t strmix, ix_t objix, ix_t lastfileix); |
| static bool_t pi_know_no_more_on_object(purp_t purp, ix_t strmix, ix_t objix); |
| static bool_t pi_know_no_more_beyond_on_object(purp_t purp, |
| ix_t strmix, |
| ix_t objix, |
| ix_t fileix); |
| static void pi_preclean(void); |
| static void pi_driveempty(ix_t driveix); |
| static void pi_note_indrive(ix_t driveix, uuid_t mediaid); |
| static void pi_note_underhead(dh_t thisobjh, dh_t thisfileh); |
| static void pi_lock(void); |
| static void pi_unlock(void); |
| |
| static rv_t applydirdump(drive_t *drivep, |
| dh_t fileh, |
| content_inode_hdr_t *scrhdrp, |
| filehdr_t *fhdrp); |
| static rv_t treepost(char *path1, char *path2); |
| static rv_t applynondirdump(drive_t *drivep, |
| dh_t fileh, |
| content_inode_hdr_t *scrhdrp, |
| char *path1, |
| char *path2, |
| filehdr_t *fhdrp); |
| static rv_t finalize(char *path1, char *path2); |
| static void wipepersstate(void); |
| |
| static rv_t read_filehdr(drive_t *drivep, filehdr_t *fhdrp, bool_t fhcs); |
| static rv_t restore_file(drive_t *drivep, |
| filehdr_t *fhdrp, |
| bool_t ehcs, |
| bool_t ahcs, |
| char *path1, |
| char *path2); |
| static bool_t restore_reg(drive_t *drivep, |
| filehdr_t *fhdrp, |
| rv_t *rvp, |
| char *path); |
| static bool_t restore_extent_group(drive_t *drivep, |
| filehdr_t *fhdrp, |
| char *path, |
| int fd, |
| bool_t ehcs, |
| rv_t *rvp); |
| static bool_t restore_complete_reg(stream_context_t*); |
| static bool_t restore_spec(filehdr_t *fhdrp, rv_t *rvp, char *path); |
| static bool_t restore_symlink(drive_t *drivep, |
| filehdr_t *fhdrp, |
| rv_t *rvp, |
| char *path, |
| char *scratchpath, |
| bool_t ehcs); |
| static rv_t read_extenthdr(drive_t *drivep, extenthdr_t *ehdrp, bool_t ehcs); |
| static rv_t read_dirent(drive_t *drivep, |
| direnthdr_t *dhdrp, |
| size_t direntbufsz, |
| bool_t dhcs); |
| static rv_t discard_padding(size_t sz, drive_t *drivep); |
| static rv_t restore_extent(filehdr_t *fhdrp, |
| extenthdr_t *ehdrp, |
| int fd, |
| char *path, |
| drive_t *drivep, |
| off64_t *bytesreadp); |
| static bool_t askinvforbaseof(uuid_t baseid, inv_session_t *sessp); |
| static void addobj(bag_t *bagp, |
| uuid_t *objidp, |
| label_t objlabel, |
| bool_t indrivepr, |
| ix_t indriveix); |
| static size_t cntobj(bag_t *bagp); |
| static bool_t gapneeded(egrp_t *firstegrpp, egrp_t *lastegrpp); |
| static char * ehdr_typestr(int32_t type); |
| static int egrpcmp(egrp_t *egrpap, egrp_t *egrpbp); |
| static void display_dump_label(bool_t lockpr, |
| int mllevel, |
| char *introstr, |
| global_hdr_t *grhdrp, |
| media_hdr_t *mrhdrp, |
| content_hdr_t *crhdrp, |
| content_inode_hdr_t *scrhdrp); |
| static void display_needed_objects(purp_t purp, |
| bag_t *bagp, |
| bool_t knownholespr, |
| bool_t maybeholespr); |
| static void set_mcflag(ix_t thrdix); |
| static void clr_mcflag(ix_t thrdix); |
| static void pi_show(char *introstring); |
| static void pi_show_nomloglock(void); |
| |
| static bool_t extattr_init(size_t drivecnt); |
| static char * get_extattrbuf(ix_t which); |
| static rv_t restore_extattr(drive_t *drivep, |
| filehdr_t *fhdrp, |
| char *path, |
| bool_t ahcs, |
| bool_t isdirpr, |
| bool_t onlydoreadpr, |
| dah_t dah); |
| static bool_t restore_dir_extattr_cb(char *path, dah_t dah); |
| static bool_t restore_dir_extattr_cb_cb(extattrhdr_t *ahdrp, void *ctxp); |
| static void setextattr(char *path, extattrhdr_t *ahdrp); |
| static void partial_reg(ix_t d_index, xfs_ino_t ino, off64_t fsize, |
| off64_t offset, off64_t sz); |
| static bool_t partial_check (xfs_ino_t ino, off64_t fsize); |
| static bool_t partial_check2 (partial_rest_t *isptr, off64_t fsize); |
| static int quotafilecheck(char *type, char *dstdir, char *quotafile); |
| |
| /* definition of locally defined global variables ****************************/ |
| |
| bool_t content_media_change_needed; |
| bool_t restore_rootdir_permissions; |
| bool_t need_fixrootdir; |
| char *media_change_alert_program = NULL; |
| size_t perssz; |
| |
| |
| /* definition of locally defined static variables *****************************/ |
| |
| static pers_t *persp; /* mapped at init, ok to use */ |
| static tran_t *tranp; /* allocated at init, ok to use */ |
| static pers_desc_t *descp = 0; /* mapped on the fly; don't use! (see macros) */ |
| static char *hkdirname = "xfsrestorehousekeepingdir"; |
| static char *persname = "state"; |
| static char *perspath = 0; |
| static bool_t mcflag[STREAM_SIMMAX]; /* media change flag */ |
| |
| |
| /* definition of locally defined global functions ****************************/ |
| |
| bool_t |
| content_init(int argc, char *argv[], size64_t vmsz) |
| { |
| char *dstdir; /* abs. path to destination dir */ |
| bool_t cumpr; /* cmd line cumulative restore specification */ |
| bool_t resumepr;/* cmd line resumed restore specification */ |
| bool_t existpr; /* cmd line overwrite inhibit specification */ |
| bool_t newerpr; /* cmd line overwrite inhibit specification */ |
| time32_t newertime = 0; |
| bool_t changepr;/* cmd line overwrite inhibit specification */ |
| bool_t interpr; /* cmd line interactive mode requested */ |
| bool_t ownerpr; /* cmd line chown/chmod requested */ |
| bool_t restoreextattrpr; /* cmd line restore extended attr spec */ |
| bool_t sesscpltpr; /* force completion of prev interrupted session */ |
| ix_t stcnt; /* cmd line number of subtrees requested */ |
| bool_t firststsensepr; |
| bool_t firststsenseprvalpr; |
| ix_t stsz; /* bytes required to record subtree selections */ |
| ix_t stpgcnt; /* pages required to hold subtree selections */ |
| ix_t newstpgcnt;/* pages required to hold subtree selections */ |
| ix_t descpgcnt; /* pages allocated for persistent descriptors */ |
| struct stat statbuf; |
| pid_t pid; |
| int c; |
| bool_t ok; |
| int rval; |
| bool_t fullpr; |
| |
| /* Calculate the size needed for the persistent inventory |
| */ |
| for (perssz = pgsz; perssz < sizeof(pers_t); perssz += pgsz) |
| ; |
| |
| /* sanity checks |
| */ |
| assert(sizeof(pers_desc_t) <= PERS_DESCSZ); |
| assert(PERS_DESCSZ <= pgsz); |
| assert(!(pgsz % PERS_DESCSZ)); |
| assert(sizeof(extattrhdr_t) == EXTATTRHDR_SZ); |
| |
| assert(!(perssz % pgsz)); |
| |
| assert(SYNC_INIT == 0); |
| |
| mlog(MLOG_NITTY, |
| "sizeof( pers_desc_t ) == %d, pgsz == %d, perssz == %d \n", |
| sizeof(pers_desc_t), pgsz, perssz); |
| |
| /* allocate transient state |
| */ |
| tranp = (tran_t *)calloc(1, sizeof(tran_t)); |
| assert(tranp); |
| |
| /* allocate a qlock for establishing pi critical regions |
| */ |
| tranp->t_pilockh = qlock_alloc(QLOCK_ORD_PI); |
| |
| /* record vmsz; will be used later to init tree abstraction |
| */ |
| tranp->t_vmsz = vmsz; |
| |
| /* record the start time for stats display |
| */ |
| tranp->t_starttime = time(0); |
| |
| /* get command line options |
| */ |
| cumpr = BOOL_FALSE; |
| resumepr = BOOL_FALSE; |
| existpr = BOOL_FALSE; |
| newerpr = BOOL_FALSE; |
| changepr = BOOL_FALSE; |
| ownerpr = BOOL_FALSE; |
| restoreextattrpr = BOOL_TRUE; |
| sesscpltpr = BOOL_FALSE; |
| stcnt = 0; |
| firststsensepr = firststsenseprvalpr = BOOL_FALSE; |
| stsz = 0; |
| interpr = BOOL_FALSE; |
| restore_rootdir_permissions = BOOL_FALSE; |
| need_fixrootdir = BOOL_FALSE; |
| optind = 1; |
| opterr = 0; |
| while ((c = getopt(argc, argv, GETOPT_CMDSTRING)) != EOF) { |
| switch (c) { |
| case GETOPT_TOC: |
| tranp->t_toconlypr = BOOL_TRUE; |
| break; |
| case GETOPT_CUMULATIVE: |
| cumpr = BOOL_TRUE; |
| break; |
| case GETOPT_RESUME: |
| resumepr = BOOL_TRUE; |
| break; |
| case GETOPT_EXISTING: |
| existpr = BOOL_TRUE; |
| break; |
| case GETOPT_NEWER: |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (stat(optarg, &statbuf)) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "unable to get status of -%c argument %s:" |
| " %s\n"), |
| c, |
| optarg, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| newerpr = BOOL_TRUE; |
| newertime = statbuf.st_mtime; |
| break; |
| case GETOPT_CHANGED: |
| changepr = BOOL_TRUE; |
| break; |
| case GETOPT_OWNER: |
| ownerpr = BOOL_TRUE; |
| break; |
| case GETOPT_WORKSPACE: |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (optarg[0] != '/') { |
| tranp->t_hkdir = path_reltoabs(optarg, |
| homedir); |
| if (!tranp->t_hkdir) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument %s is an " |
| "invalid pathname\n"), |
| c, |
| optarg); |
| usage(); |
| return BOOL_FALSE; |
| } |
| mlog(MLOG_DEBUG, |
| "alternate workspace path converted " |
| "from %s to %s\n", |
| optarg, |
| tranp->t_hkdir); |
| } else { |
| tranp->t_hkdir = optarg; |
| } |
| rval = stat(tranp->t_hkdir, &statbuf); |
| if (rval) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "cannot stat -%c argument %s (%s): %s\n"), |
| c, |
| optarg, |
| tranp->t_hkdir, |
| strerror(errno)); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if ((statbuf.st_mode & S_IFMT) != S_IFDIR) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument %s (%s) " |
| "is not a directory\n"), |
| c, |
| optarg, |
| tranp->t_hkdir); |
| usage(); |
| return BOOL_FALSE; |
| } |
| |
| break; |
| case GETOPT_DUMPLABEL: |
| if (tranp->t_reqdumplabvalpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "too many -%c arguments: " |
| "\"-%c %s\" already given\n"), |
| c, |
| c, |
| tranp->t_reqdumplab); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (strlen(optarg) |
| > |
| sizeofmember(pers_t, s.dumplab)) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument %s too long: max is %d\n"), |
| c, |
| optarg, |
| sizeofmember(pers_t, s.dumplab)); |
| usage(); |
| return BOOL_FALSE; |
| } |
| tranp->t_reqdumplab = optarg; |
| tranp->t_reqdumplabvalpr = BOOL_TRUE; |
| break; |
| case GETOPT_SESSIONID: |
| if (tranp->t_reqdumpidvalpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "too many -%c arguments\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (uuid_parse(optarg, tranp->t_reqdumpid) < 0) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument not a valid uuid\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| tranp->t_reqdumpidvalpr = BOOL_TRUE; |
| break; |
| case GETOPT_SUBTREE: |
| case GETOPT_NOSUBTREE: |
| if (!optarg |
| || |
| optarg[0] == 0 |
| || |
| optarg[0] == '-') { |
| mlog(MLOG_NORMAL, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (optarg[0] == '/') { |
| mlog(MLOG_NORMAL, _( |
| "-%c argument must be relative\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| stcnt++; |
| if (!firststsenseprvalpr) { |
| if (c == GETOPT_SUBTREE) { |
| firststsensepr = BOOL_TRUE; |
| } else { |
| firststsensepr = BOOL_FALSE; |
| } |
| firststsenseprvalpr = BOOL_TRUE; |
| } |
| stsz += sizeof(stdesc_t) |
| + |
| strlen(optarg) |
| + |
| (STDESCALIGN - 1); |
| stsz &= ~(STDESCALIGN - 1); |
| break; |
| case GETOPT_INTERACTIVE: |
| if (!dlog_allowed()) { |
| mlog(MLOG_NORMAL, _( |
| "-%c unavailable: no /dev/tty\n"), |
| GETOPT_INTERACTIVE); |
| return BOOL_FALSE; |
| } |
| interpr = BOOL_TRUE; |
| break; |
| case GETOPT_NOINVUPDATE: |
| tranp->t_noinvupdatepr = BOOL_TRUE; |
| break; |
| case GETOPT_SETDM: |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c option no longer supported\n"), |
| GETOPT_SETDM); |
| usage(); |
| return BOOL_FALSE; |
| case GETOPT_ALERTPROG: |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| media_change_alert_program = optarg; |
| break; |
| case GETOPT_NOEXTATTR: |
| restoreextattrpr = BOOL_FALSE; |
| break; |
| case GETOPT_SESSCPLT: |
| sesscpltpr = BOOL_TRUE; |
| break; |
| case GETOPT_SMALLWINDOW: |
| /* obsolete */ |
| break; |
| case GETOPT_ROOTPERM: |
| restore_rootdir_permissions = BOOL_TRUE; |
| break; |
| case GETOPT_FMT2COMPAT: |
| tranp->t_truncategenpr = BOOL_TRUE; |
| break; |
| case GETOPT_FIXROOTDIR: |
| need_fixrootdir = BOOL_TRUE; |
| break; |
| } |
| } |
| |
| /* command line option error checking |
| */ |
| if (cumpr && tranp->t_toconlypr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c and -%c option cannot be used together\n"), |
| GETOPT_TOC, |
| GETOPT_CUMULATIVE); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (resumepr && tranp->t_toconlypr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c and -%c option cannot be used together\n"), |
| GETOPT_TOC, |
| GETOPT_RESUME); |
| usage(); |
| return BOOL_FALSE; |
| } |
| |
| /* assume all streams contain a directory dump. streams will remove |
| * themselves from this bitset if they do not contain a directory dump. |
| */ |
| assert(drivecnt <= sizeof(tranp->t_dirdumps) * NBBY); |
| tranp->t_dirdumps = (1ULL << drivecnt) - 1; |
| |
| /* the user may specify stdin as the restore source stream, |
| * by a single dash ('-') with no option letter. This must |
| * appear between the last lettered argument and the destination |
| * directory pathname. |
| */ |
| if (optind < argc && !strcmp(argv[optind ], "-")) { |
| optind++; |
| } |
| |
| /* the last argument must be the destination directory. not |
| * required if table-of-contents display, or if a resumed restore |
| * or a delta restore. |
| */ |
| if (!tranp->t_toconlypr) { |
| if (optind >= argc) { |
| dstdir = 0; |
| } else { |
| if (argv[optind][0] != '/') { |
| dstdir = path_reltoabs(argv[optind], |
| homedir); |
| if (!dstdir) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "destination directory %s " |
| "invalid pathname\n"), |
| argv[optind]); |
| usage(); |
| return BOOL_FALSE; |
| } |
| mlog(MLOG_DEBUG, |
| "restore destination path converted " |
| "from %s to %s\n", |
| argv[optind], |
| dstdir); |
| } else { |
| dstdir = argv[optind]; |
| } |
| rval = stat(dstdir, &statbuf); |
| if (rval) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "cannot stat destination directory %s: " |
| "%s\n"), |
| dstdir, |
| strerror(errno)); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if ((statbuf.st_mode & S_IFMT) != S_IFDIR) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "specified destination %s " |
| "is not a directory\n"), |
| dstdir); |
| usage(); |
| return BOOL_FALSE; |
| } |
| } |
| } else { |
| if (optind < argc) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "do not specify destination directory if " |
| "contents only restore invoked (-%c option)\n"), |
| GETOPT_TOC); |
| usage(); |
| return BOOL_FALSE; |
| } |
| dstdir = "."; |
| } |
| |
| /* generate a full pathname for the housekeeping dir. |
| * the housekeeping dir will by default be placed in the |
| * destination directory, unless this is a toc, in which case |
| * it will be placed in the current directory. in either case, an |
| * alternate directory may be specified on the command line. |
| * if this is toconly, modify the housekeeping dir's name with |
| * the pid. |
| */ |
| if (!tranp->t_hkdir) { |
| if (tranp->t_toconlypr) { |
| tranp->t_hkdir = homedir; |
| } else { |
| if (!dstdir) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "destination directory " |
| "not specified\n")); |
| usage(); |
| return BOOL_FALSE; |
| } else { |
| tranp->t_hkdir = dstdir; |
| } |
| } |
| } |
| if (tranp->t_toconlypr) { |
| pid = getpid(); |
| } else { |
| pid = 0; |
| } |
| tranp->t_hkdir = open_pathalloc(tranp->t_hkdir, hkdirname, pid); |
| |
| /* if this is a table-of-contents only restore, register an |
| * exit handler to get rid of the housekeeping directory and |
| * its contents. NOTE: needs several tran fields initialized! |
| */ |
| if (tranp->t_toconlypr) { |
| atexit(toconly_cleanup); |
| } |
| |
| /* create housekeeping dir if not present |
| */ |
| rval = mkdir(tranp->t_hkdir, S_IRWXU); |
| if (rval && errno != EEXIST) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "unable to create %s: %s\n"), |
| tranp->t_hkdir, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| |
| /* build a full pathname to pers. state file |
| */ |
| assert(!perspath); |
| perspath = open_pathalloc(tranp->t_hkdir, persname, 0); |
| |
| /* open, creating if non-existent |
| */ |
| tranp->t_persfd = open(perspath, |
| O_CREAT | O_RDWR, |
| S_IRUSR | S_IWUSR); |
| if (tranp->t_persfd < 0) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "could not open/create persistent state file %s: %s\n"), |
| perspath, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| |
| /* temporarily mmap just the header, and validate the command line |
| * arguments. three cases: no dumps applied so far, or one or more |
| * dumps applied completely, or restore session was interrupted |
| */ |
| persp = (pers_t *) mmap_autogrow(perssz, tranp->t_persfd, 0); |
| |
| if (persp == (pers_t *)-1) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "could not map persistent state file hdr %s: %s\n"), |
| perspath, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| |
| /* but first setup or verify the on-disk format information |
| */ |
| if (!persp->a.valpr) { |
| /* this is the first restore session |
| */ |
| persp->v.housekeeping_magic = HOUSEKEEPING_MAGIC; |
| persp->v.housekeeping_version = HOUSEKEEPING_VERSION; |
| persp->v.pagesize = pgsz; |
| |
| } else { |
| /* cumulative or resuming a restore, verify the header |
| */ |
| if (persp->v.housekeeping_magic != HOUSEKEEPING_MAGIC) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "%s format corrupt or wrong endianness " |
| "(0x%x, expected 0x%x)\n"), |
| hkdirname, |
| persp->v.housekeeping_magic, |
| HOUSEKEEPING_MAGIC); |
| return BOOL_FALSE; |
| } |
| if (persp->v.housekeeping_version != HOUSEKEEPING_VERSION) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "%s format version differs from previous " |
| "restore (%u, expected %u)\n"), |
| hkdirname, |
| persp->v.housekeeping_version, |
| HOUSEKEEPING_VERSION); |
| return BOOL_FALSE; |
| } |
| if (persp->v.pagesize != pgsz) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "%s format differs from previous " |
| "restore due to page size change " |
| "(was %lu, now %lu)\n"), |
| hkdirname, |
| persp->v.pagesize, |
| pgsz); |
| return BOOL_FALSE; |
| } |
| } |
| |
| if (!persp->a.valpr) { |
| if (!dstdir) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "destination directory not specified\n")); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (strlen(dstdir) >= sizeofmember(pers_t, a.dstdir)) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "destination directory pathname too long: " |
| "max is %d characters\n"), |
| sizeofmember(pers_t, a.dstdir) - 1); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (resumepr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c option invalid: there is no " |
| "interrupted restore to resume\n"), |
| GETOPT_RESUME); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (sesscpltpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c option invalid: there is no " |
| "interrupted restore to force completion of\n"), |
| GETOPT_SESSCPLT); |
| usage(); |
| return BOOL_FALSE; |
| } |
| } else if (!persp->s.valpr) { |
| if (!cumpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "must rm -rf %s prior to noncumulative restore\n"), |
| tranp->t_hkdir); |
| return BOOL_FALSE; |
| } |
| if (resumepr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c option invalid: there is no " |
| "interrupted restore to resume\n"), |
| GETOPT_RESUME); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (sesscpltpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c option invalid: there is no " |
| "interrupted restore to force completion of\n"), |
| GETOPT_SESSCPLT); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (existpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating " |
| "cumulative restore\n"), |
| GETOPT_EXISTING); |
| return BOOL_FALSE; |
| } |
| if (newerpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating " |
| "cumulative restore\n"), |
| GETOPT_NEWER); |
| return BOOL_FALSE; |
| } |
| if (changepr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating " |
| "cumulative restore\n"), |
| GETOPT_CHANGED); |
| return BOOL_FALSE; |
| } |
| if (ownerpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating " |
| "cumulative restore\n"), |
| GETOPT_OWNER); |
| return BOOL_FALSE; |
| } |
| if (stcnt) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c and -%c valid only when initiating " |
| "cumulative restore\n"), |
| GETOPT_SUBTREE, |
| GETOPT_NOSUBTREE); |
| return BOOL_FALSE; |
| } |
| if (tranp->t_truncategenpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating " |
| "cumulative restore\n"), |
| GETOPT_FMT2COMPAT); |
| return BOOL_FALSE; |
| } |
| } else { |
| if (!resumepr && !sesscpltpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c option required to resume " |
| "or " |
| "-%c option required to force completion of " |
| "previously " |
| "interrupted restore session\n"), |
| GETOPT_RESUME, |
| GETOPT_SESSCPLT); |
| return BOOL_FALSE; |
| } |
| if (tranp->t_reqdumplabvalpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating restore\n"), |
| GETOPT_DUMPLABEL); |
| return BOOL_FALSE; |
| } |
| if (tranp->t_reqdumpidvalpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating restore\n"), |
| GETOPT_SESSIONID); |
| return BOOL_FALSE; |
| } |
| if (existpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating restore\n"), |
| GETOPT_EXISTING); |
| return BOOL_FALSE; |
| } |
| if (newerpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating restore\n"), |
| GETOPT_NEWER); |
| return BOOL_FALSE; |
| } |
| if (changepr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating restore\n"), |
| GETOPT_CHANGED); |
| return BOOL_FALSE; |
| } |
| if (ownerpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating restore\n"), |
| GETOPT_OWNER); |
| return BOOL_FALSE; |
| } |
| if (interpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating restore\n"), |
| GETOPT_INTERACTIVE); |
| return BOOL_FALSE; |
| } |
| if (stcnt) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c and -%c valid only when initiating restore\n"), |
| GETOPT_SUBTREE, |
| GETOPT_NOSUBTREE); |
| return BOOL_FALSE; |
| } |
| if (tranp->t_truncategenpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c valid only when initiating restore\n"), |
| GETOPT_FMT2COMPAT); |
| return BOOL_FALSE; |
| } |
| } |
| |
| if (persp->a.valpr) { |
| if (!restoreextattrpr && |
| persp->a.restoreextattrpr != restoreextattrpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c cannot reset flag from previous restore\n"), |
| GETOPT_NOEXTATTR); |
| return BOOL_FALSE; |
| } |
| } |
| |
| /* force owner option if root |
| */ |
| ownerpr = (geteuid() == 0) ? BOOL_TRUE : ownerpr; |
| |
| /* force completion of interrupted restore if asked to do so |
| */ |
| if (sesscpltpr) { |
| char *path1; |
| char *path2; |
| rv_t rv; |
| int rval; |
| |
| path1 = (char *)calloc(1, 2 * MAXPATHLEN); |
| assert(path1); |
| path2 = (char *)calloc(1, 2 * MAXPATHLEN); |
| assert(path2); |
| assert(persp->a.valpr); |
| assert(persp->s.valpr); |
| rval = chdir(persp->a.dstdir); |
| if (rval) { |
| mlog(MLOG_NORMAL, _( |
| "chdir %s failed: %s\n"), |
| persp->a.dstdir, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| ok = dirattr_init(tranp->t_hkdir, BOOL_TRUE, (uint64_t)0); |
| if (!ok) { |
| return BOOL_FALSE; |
| } |
| ok = namreg_init(tranp->t_hkdir, BOOL_TRUE, (uint64_t)0); |
| if (!ok) { |
| return BOOL_FALSE; |
| } |
| ok = inomap_sync_pers(tranp->t_hkdir); |
| if (!ok) { |
| return BOOL_FALSE; |
| } |
| |
| /* This is only a full restore if we're doing a level |
| * 0 restore. |
| */ |
| if (persp->a.dumpcnt == 0) { |
| fullpr = BOOL_TRUE; |
| } else { |
| fullpr = BOOL_FALSE; |
| } |
| |
| ok = tree_sync(tranp->t_hkdir, |
| persp->a.dstdir, |
| tranp->t_toconlypr, |
| fullpr, |
| persp->a.dstdirisxfspr); |
| if (!ok) { |
| return BOOL_FALSE; |
| } |
| rv = finalize(path1, path2); |
| free((void *)path1); |
| free((void *)path2); |
| switch (rv) { |
| case RV_OK: |
| break; |
| case RV_ERROR: |
| return EXIT_ERROR; |
| case RV_INTR: |
| return EXIT_NORMAL; |
| case RV_CORE: |
| default: |
| return EXIT_FAULT; |
| } |
| } |
| |
| /* for the three cases, calculate old and new mapping params |
| * and wipe partial state |
| */ |
| if (!persp->a.valpr) { |
| stpgcnt = 0; |
| newstpgcnt = (stsz + pgmask) / pgsz; |
| descpgcnt = 0; |
| memset((void *)&persp->a, 0, |
| sizeof(pers_t) - offsetofmember(pers_t, a)); |
| } else if (!persp->s.valpr) { |
| stpgcnt = persp->a.stpgcnt; |
| newstpgcnt = stpgcnt; |
| descpgcnt = 0; |
| memset((void *)&persp->s, 0, sizeofmember(pers_t, s)); |
| } else { |
| stpgcnt = persp->a.stpgcnt; |
| newstpgcnt = stpgcnt; |
| descpgcnt = persp->s.descpgcnt; |
| assert(resumepr); |
| mlog(MLOG_VERBOSE, _( |
| "resuming restore previously begun %s\n"), |
| ctimennl(&persp->s.begintime)); |
| persp->s.begintime = time(0); |
| } |
| |
| /* unmap temp mapping of hdr, truncate, and remap hdr/subtrees |
| */ |
| rval = munmap((void *)persp, perssz); |
| assert(!rval); |
| rval = ftruncate(tranp->t_persfd, (off_t)perssz |
| + |
| (off_t)(stpgcnt + descpgcnt) |
| * |
| (off_t)pgsz); |
| assert(!rval); |
| stpgcnt = newstpgcnt; |
| persp = (pers_t *) mmap_autogrow(perssz + stpgcnt * pgsz, |
| tranp->t_persfd, 0); |
| if (persp == (pers_t *)-1) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "could not map persistent state file %s: %s\n"), |
| perspath, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| |
| /* if first restore session, record cmd line args and subtrees |
| * and start time. |
| */ |
| if (!persp->a.valpr) { |
| stdesc_t *stdescp; |
| |
| strcpy(persp->a.dstdir, dstdir); |
| persp->a.dstdirisxfspr = platform_test_xfs_path(dstdir); |
| if (cumpr) { |
| persp->a.cumpr = cumpr; |
| } |
| if (interpr) { |
| persp->a.interpr = interpr; |
| } |
| if (existpr) { |
| persp->a.existpr = existpr; |
| } |
| if (changepr) { |
| persp->a.changepr = changepr; |
| } |
| if (ownerpr) { |
| persp->a.ownerpr = ownerpr; |
| } |
| if (newerpr) { |
| persp->a.newerpr = newerpr; |
| persp->a.newertime = newertime; |
| } |
| if (!persp->a.dstdirisxfspr) { |
| restoreextattrpr = BOOL_FALSE; |
| } |
| persp->a.restoreextattrpr = restoreextattrpr; |
| persp->a.stcnt = stcnt; |
| persp->a.firststsensepr = firststsensepr; |
| persp->a.firststsenseprvalpr = firststsenseprvalpr; |
| persp->a.stpgcnt = stpgcnt; |
| optind = 1; |
| opterr = 0; |
| stdescp = (stdesc_t *)((char *)persp + perssz); |
| while ((c = getopt(argc, argv, GETOPT_CMDSTRING)) != EOF) { |
| size_t stdsz; |
| switch (c) { |
| case GETOPT_SUBTREE: |
| case GETOPT_NOSUBTREE: |
| stdescp->std_sensepr = (c == GETOPT_SUBTREE) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| stdsz = sizeof(stdesc_t) |
| + |
| strlen(optarg) |
| + |
| (STDESCALIGN - 1); |
| stdsz &= ~(STDESCALIGN - 1); |
| assert(stdsz <= (size_t)OFFMAX); |
| stdescp->std_nextoff = (off_t)stdsz; |
| strcpy(stdescp->std_path, optarg); |
| stdescp = (stdesc_t *) |
| ((char *)stdescp + stdsz); |
| stcnt--; |
| break; |
| } |
| } |
| assert(stcnt == 0); |
| } |
| |
| /* initialize the local extattr abstraction. must be done even if |
| * we don't intend to restore extended attributes |
| */ |
| ok = extattr_init(drivecnt); |
| if (!ok) { |
| return BOOL_FALSE; |
| } |
| |
| /* effectively initialize libhandle on this filesystem by |
| * allocating a file system handle. this needs to be done |
| * before any open_by_handle() calls (and possibly other |
| * libhandle calls). |
| */ |
| if (persp->a.dstdirisxfspr) { |
| void *fshanp; |
| size_t fshlen=0; |
| |
| if(path_to_fshandle(persp->a.dstdir, &fshanp, &fshlen)) { |
| mlog(MLOG_NORMAL, |
| _("unable to construct a file " |
| "system handle for %s: %s\n"), |
| persp->a.dstdir, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| /* libhandle has it cached, release this copy */ |
| free_handle(fshanp, fshlen); |
| } |
| |
| /* map in pers. inv. descriptors, if any. NOTE: this ptr is to be |
| * referenced ONLY via the macros provided; the descriptors will be |
| * occasionally remapped, causing the ptr to change. |
| */ |
| assert(!descp); |
| if (descpgcnt) { |
| descp = (pers_desc_t *) mmap_autogrow(descpgcnt * pgsz, |
| tranp->t_persfd, |
| (off_t)perssz |
| + |
| (off_t)(stpgcnt * pgsz)); |
| if (descp == (pers_desc_t *)-1) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "could not map persistent state file inv %s: " |
| "%s (%d)\n"), |
| perspath, |
| strerror(errno), |
| errno); |
| descp = 0; |
| return BOOL_FALSE; |
| } |
| pi_preclean(); |
| } |
| |
| /* if resuming an interrupted restore, indicate we know the id |
| * of the dump session being restored. otherwise, it will be determined |
| * during coordination of per-drive threads. |
| */ |
| if (persp->a.valpr && persp->s.valpr) { |
| persp->s.begintime = time(0); |
| tranp->t_dumpidknwnpr = BOOL_TRUE; |
| } |
| |
| /* sync up with the directory attributes registry. |
| * starts fresh with each dump session restored. |
| * determine if full init needed instead. |
| */ |
| if (persp->a.valpr && persp->s.valpr) { |
| ok = dirattr_init(tranp->t_hkdir, BOOL_TRUE, (uint64_t)0); |
| if (!ok) { |
| return BOOL_FALSE; |
| } |
| tranp->t_dirattrinitdonepr = BOOL_TRUE; |
| } |
| |
| /* sync up with the name registry. created by the |
| * first session, retained by subsequent sessions. |
| * determine if full init needed instead. |
| */ |
| if (persp->a.valpr) { |
| ok = namreg_init(tranp->t_hkdir, BOOL_TRUE, (uint64_t)0); |
| if (!ok) { |
| return BOOL_FALSE; |
| } |
| tranp->t_namreginitdonepr = BOOL_TRUE; |
| } |
| |
| /* sync up with the inomap abstraction. created anew with each fresh |
| * restore session, but persistent after tree updated with dirdump. |
| * determine if full init needed instead. |
| */ |
| ok = inomap_sync_pers(tranp->t_hkdir); |
| if (!ok) { |
| return BOOL_FALSE; |
| } |
| |
| /* sync up with the tree abstraction. created by the |
| * first session, retained by subsequent sessions. |
| * don't call tree_init() from here; can only be called |
| * when a valid media file header is at hand. |
| */ |
| if (persp->a.valpr) { |
| /* This is only a full restore if we're doing a level |
| * 0 restore. |
| */ |
| if (persp->a.dumpcnt == 0) { |
| fullpr = BOOL_TRUE; |
| } else { |
| fullpr = BOOL_FALSE; |
| } |
| |
| ok = tree_sync(tranp->t_hkdir, |
| persp->a.dstdir, |
| tranp->t_toconlypr, |
| fullpr, |
| persp->a.dstdirisxfspr); |
| if (!ok) { |
| return BOOL_FALSE; |
| } |
| tranp->t_treeinitdonepr = BOOL_TRUE; |
| } |
| |
| /* set media change flags to FALSE; |
| */ |
| { |
| ix_t ix; |
| ix_t endix = sizeof(mcflag) |
| / |
| sizeof(mcflag[0]); |
| for (ix = 0; ix < endix; ix++) { |
| mcflag[ix] = BOOL_FALSE; |
| } |
| } |
| content_media_change_needed = BOOL_FALSE; |
| |
| pi_show(" at initialization"); |
| return BOOL_TRUE; |
| } |
| |
| /* stream thread entry point - returns exit code |
| */ |
| int |
| content_stream_restore(ix_t thrdix) |
| { |
| dh_t fileh; |
| Media_t *Mediap; /* local media abstraction */ |
| char *path1; |
| char *path2; |
| drive_t *drivep; |
| int dcaps; |
| global_hdr_t *grhdrp; |
| drive_hdr_t *drhdrp; |
| media_hdr_t *mrhdrp; |
| content_hdr_t *crhdrp; |
| content_inode_hdr_t *scrhdrp; |
| stream_context_t *strctxp; |
| filehdr_t fhdr; /* save hdr terminating dir restore */ |
| uuid_t lastdumprejectedid; |
| rv_t rv; |
| bool_t ok; |
| int rval; |
| |
| /* allocate two path buffers |
| */ |
| path1 = (char *)calloc(1, 2 * MAXPATHLEN); |
| assert(path1); |
| path2 = (char *)calloc(1, 2 * MAXPATHLEN); |
| assert(path2); |
| |
| /* set the current directory to dstdir. the tree abstraction |
| * depends on the current directory being the root of the |
| * destination file system. |
| */ |
| rval = chdir(persp->a.dstdir); |
| if (rval) { |
| mlog(MLOG_NORMAL, _( |
| "chdir %s failed: %s\n"), |
| persp->a.dstdir, |
| strerror(errno)); |
| return mlog_exit(EXIT_ERROR, RV_ERROR); |
| } |
| |
| /* set my file creation mask to zero, to avoid modifying the |
| * dumped mode bits |
| */ |
| (void)umask(0); |
| |
| /* initialize the Media abstraction |
| */ |
| Mediap = Media_create(thrdix); |
| |
| /* |
| * initialize the stream context |
| */ |
| strctxp = (stream_context_t *)calloc(1, sizeof(stream_context_t)); |
| if (!strctxp) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, |
| _("malloc of stream context failed (%d bytes): %s\n"), |
| sizeof(stream_context_t), |
| strerror(errno)); |
| return mlog_exit(EXIT_ERROR, RV_ERROR); |
| } |
| strctxp->sc_fd = -1; |
| Mediap->M_drivep->d_strmcontextp = (void *)strctxp; |
| |
| /* if we don't know the dump session id to restore, |
| * first see if command line options can be validated |
| * against the online inventory to identify it. only |
| * one stream needs to do this; the others just wait. |
| * side-effect of validation is to incorporate the online |
| * inventory into the persistent state. |
| */ |
| if (tranp->t_dumpidknwnpr) { |
| tranp->t_sync1 = SYNC_DONE; |
| } |
| while (tranp->t_sync1 != SYNC_DONE) { |
| lock(); |
| if (tranp->t_sync1 == SYNC_BUSY) { |
| unlock(); |
| sleep(1); |
| if (cldmgr_stop_requested()) { |
| return mlog_exit(EXIT_NORMAL, RV_INTR); |
| } |
| continue; |
| } |
| if (tranp->t_sync1 == SYNC_DONE) { |
| unlock(); |
| continue; |
| } |
| tranp->t_sync1 = SYNC_BUSY; |
| unlock(); |
| mlog(MLOG_DEBUG, |
| "checking and validating command line dump id/label\n"); |
| ok = Inv_validate_cmdline(); |
| /* side-effect - searches for and incorporates online inv |
| * into pi, and makes persp->s.dumpid valid. |
| */ |
| if (ok == BOOL_ERROR) { |
| return mlog_exit(EXIT_ERROR, RV_OPT); |
| } |
| tranp->t_dumpidknwnpr = ok; |
| tranp->t_sync1 = SYNC_DONE; |
| } |
| |
| /* if we still don't know the session to restore, search the |
| * media for a match either to the command line arguments or |
| * until the operator selects a media file from the desired |
| * dump. |
| */ |
| if (tranp->t_dumpidknwnpr) { |
| tranp->t_sync2 = SYNC_DONE; |
| } |
| uuid_clear(lastdumprejectedid); |
| if (tranp->t_sync2 != SYNC_DONE) { |
| mlog(MLOG_VERBOSE, _( |
| "searching media for dump\n")); |
| } |
| while (tranp->t_sync2 != SYNC_DONE) { |
| bool_t matchpr; |
| inv_session_t *sessp; |
| bool_t resumepr; |
| ix_t level; |
| uuid_t *baseidp; |
| |
| rv = Media_mfile_next(Mediap, |
| PURP_SEARCH, |
| &tranp->t_sync2, |
| 0, |
| &grhdrp, |
| &drhdrp, |
| &mrhdrp, |
| &crhdrp, |
| &scrhdrp, |
| &drivep, |
| &fhdr); |
| switch (rv) { |
| case RV_OK: |
| break; |
| case RV_DONE: |
| case RV_NOMORE: |
| continue; |
| case RV_INTR: |
| case RV_QUIT: |
| case RV_DRIVE: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, rv); |
| case RV_CORE: |
| default: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| dcaps = drivep->d_capabilities; |
| |
| lock(); |
| while (tranp->t_sync2 == SYNC_BUSY) { |
| unlock(); |
| sleep(1); |
| if (cldmgr_stop_requested()) { |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, RV_INTR); |
| } |
| lock(); |
| } |
| if (tranp->t_sync2 == SYNC_DONE) { |
| unlock(); |
| continue; |
| } |
| tranp->t_sync2 = SYNC_BUSY; |
| |
| unlock(); |
| mlog(MLOG_DEBUG, |
| "dump found: checking\n"); |
| matchpr = BOOL_FALSE; |
| resumepr = (scrhdrp->cih_dumpattr & CIH_DUMPATTR_RESUME); |
| assert(scrhdrp->cih_level >= 0); |
| level = (ix_t)scrhdrp->cih_level; |
| baseidp = resumepr |
| ? |
| &scrhdrp->cih_resume_id |
| : |
| &scrhdrp->cih_last_id; |
| if (tranp->t_reqdumpidvalpr) { |
| if (uuid_compare(tranp->t_reqdumpid, |
| grhdrp->gh_dumpid) == 0) { |
| matchpr = BOOL_TRUE; |
| display_dump_label(BOOL_TRUE, /* lock */ |
| MLOG_VERBOSE, _( |
| "found dump matching " |
| "specified id:\n"), |
| grhdrp, |
| mrhdrp, |
| crhdrp, |
| scrhdrp); |
| } |
| } else if (tranp->t_reqdumplabvalpr) { |
| if (!strncmp(tranp->t_reqdumplab, |
| grhdrp->gh_dumplabel, |
| sizeof(grhdrp->gh_dumplabel))) { |
| matchpr = BOOL_TRUE; |
| display_dump_label(BOOL_TRUE, /* lock */ |
| MLOG_VERBOSE, _( |
| "found dump matching " |
| "specified label:\n"), |
| grhdrp, |
| mrhdrp, |
| crhdrp, |
| scrhdrp); |
| } |
| } else if (dumpcompat(resumepr, |
| level, |
| *baseidp, |
| BOOL_FALSE)) { |
| if (uuid_compare(lastdumprejectedid, |
| grhdrp->gh_dumpid) == 0) { |
| matchpr = BOOL_FALSE; |
| } else { |
| if (dlog_allowed() |
| && |
| ((dcaps & DRIVE_CAP_FILES) |
| || |
| (dcaps & DRIVE_CAP_REMOVABLE) |
| || |
| drivecnt > 1)) { |
| matchpr = promptdumpmatch(thrdix, |
| grhdrp, |
| mrhdrp, |
| crhdrp, |
| scrhdrp); |
| } else { |
| matchpr = BOOL_TRUE; |
| display_dump_label(BOOL_TRUE,/* lock */ |
| MLOG_VERBOSE, _( |
| "dump " |
| "description: \n"), |
| grhdrp, |
| mrhdrp, |
| crhdrp, |
| scrhdrp); |
| } |
| } |
| } |
| if (cldmgr_stop_requested()) { |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, RV_INTR); |
| } |
| if (!matchpr) { |
| Media_end(Mediap); |
| uuid_copy(lastdumprejectedid, grhdrp->gh_dumpid); |
| tranp->t_sync2 = SYNC_INIT; |
| if (!dlog_allowed() |
| || |
| (!(dcaps & DRIVE_CAP_FILES) |
| && |
| !(dcaps & DRIVE_CAP_REMOVABLE))) { |
| return mlog_exit(EXIT_NORMAL, RV_QUIT); |
| } |
| continue; |
| } |
| if (!dumpcompat(resumepr, level, *baseidp, BOOL_TRUE)) { |
| Media_end(Mediap); |
| return mlog_exit(EXIT_ERROR, RV_COMPAT); |
| } |
| strncpyterm(persp->s.dumplab, |
| grhdrp->gh_dumplabel, |
| sizeof(persp->s.dumplab)); |
| sessp = 0; |
| |
| /* don't look at the online inventory if the input is piped |
| */ |
| if (!drivep->d_isnamedpipepr |
| && |
| !drivep->d_isunnamedpipepr) { |
| ok = inv_get_session_byuuid(NULL, |
| &grhdrp->gh_dumpid, |
| &sessp); |
| if (ok && sessp) { |
| mlog(MLOG_VERBOSE, _( |
| "using online session inventory\n")); |
| persp->s.fullinvpr = pi_transcribe(sessp); |
| inv_free_session(&sessp); |
| } |
| } |
| fileh = pi_addfile(Mediap, |
| grhdrp, |
| drhdrp, |
| mrhdrp, |
| scrhdrp, |
| drivep); |
| /* done here because Media_mfile_next doesn't know |
| * if this is a match |
| */ |
| if (fileh == DH_NULL) { |
| return mlog_exit(EXIT_FAULT, RV_ERROR); |
| } |
| uuid_copy(persp->s.dumpid, grhdrp->gh_dumpid); |
| persp->s.begintime = time(0); |
| tranp->t_dumpidknwnpr = BOOL_TRUE; |
| tranp->t_sync2 = SYNC_DONE; |
| } |
| |
| /* all drives coordinate in attempt to apply session dir dump. |
| * only one actually completes. |
| */ |
| if (persp->s.dirdonepr) { |
| tranp->t_sync3 = SYNC_DONE; |
| } |
| if (tranp->t_sync3 != SYNC_DONE) { |
| mlog(MLOG_VERBOSE, _( |
| "searching media for directory dump\n")); |
| } |
| while (tranp->t_sync3 != SYNC_DONE) { |
| rv = Media_mfile_next(Mediap, |
| PURP_DIR, |
| &tranp->t_sync3, |
| &fileh, |
| &grhdrp, |
| &drhdrp, |
| &mrhdrp, |
| &crhdrp, |
| &scrhdrp, |
| &drivep, |
| &fhdr); |
| switch (rv) { |
| case RV_OK: |
| break; |
| case RV_DONE: |
| case RV_NOMORE: |
| continue; |
| case RV_INTR: |
| case RV_QUIT: |
| case RV_DRIVE: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, rv); |
| case RV_CORE: |
| default: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| dcaps = drivep->d_capabilities; |
| assert(fileh != DH_NULL); |
| lock(); |
| if (tranp->t_sync3 == SYNC_BUSY) { |
| unlock(); |
| mlog(MLOG_TRACE, |
| "waiting for directories to be restored\n"); |
| lock(); |
| } |
| while (tranp->t_sync3 == SYNC_BUSY) { |
| unlock(); |
| #if DEBUG_DUMPSTREAMS |
| { |
| static int count[STREAM_MAX] = {0}; |
| int streamix = stream_getix(pthread_self()); |
| if (++(count[streamix]) == 30) { |
| mlog(MLOG_TRACE, |
| "still waiting for dirs to be restored\n"); |
| count[streamix] = 0; |
| } |
| } |
| #endif |
| sleep(1); |
| if (cldmgr_stop_requested()) { |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, RV_INTR); |
| } |
| lock(); |
| } |
| if (tranp->t_sync3 == SYNC_DONE) { |
| unlock(); |
| continue; |
| } |
| if (!(scrhdrp->cih_dumpattr & CIH_DUMPATTR_DIRDUMP)) { |
| /* if no streams have a directory dump, issue a |
| * message and exit. first set SYNC_BUSY to prevent |
| * other threads from coming through here and issuing |
| * the same message. |
| */ |
| tranp->t_dirdumps &= ~(1ULL << thrdix); |
| if (!tranp->t_dirdumps) { |
| tranp->t_sync3 = SYNC_BUSY; |
| } |
| unlock(); |
| if (!tranp->t_dirdumps) { |
| mlog(MLOG_VERBOSE | MLOG_ERROR, _( |
| "no directory dump found\n")); |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, RV_ERROR); |
| } |
| sleep(1); |
| if (cldmgr_stop_requested()) { |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, RV_INTR); |
| } |
| continue; |
| } |
| tranp->t_sync3 = SYNC_BUSY; |
| unlock(); |
| if (!tranp->t_dirattrinitdonepr) { |
| mlog(MLOG_TRACE, |
| "initializing directory attributes registry\n"); |
| mlog(MLOG_NITTY, |
| "content_stream_restore: dircnt %llu\n", |
| scrhdrp->cih_inomap_dircnt); |
| ok = dirattr_init(tranp->t_hkdir, |
| BOOL_FALSE, |
| scrhdrp->cih_inomap_dircnt); |
| if (!ok) { |
| Media_end(Mediap); |
| return mlog_exit(EXIT_ERROR, RV_ERROR); |
| } |
| tranp->t_dirattrinitdonepr = BOOL_TRUE; |
| } |
| |
| if (!tranp->t_namreginitdonepr) { |
| mlog(MLOG_TRACE, |
| "initializing directory entry name registry\n"); |
| ok = namreg_init(tranp->t_hkdir, |
| BOOL_FALSE, |
| scrhdrp->cih_inomap_dircnt |
| + |
| scrhdrp->cih_inomap_nondircnt); |
| if (!ok) { |
| Media_end(Mediap); |
| return mlog_exit(EXIT_ERROR, RV_ERROR); |
| } |
| tranp->t_namreginitdonepr = BOOL_TRUE; |
| } |
| |
| if (!tranp->t_treeinitdonepr) { |
| bool_t fullpr; |
| |
| fullpr = (scrhdrp->cih_level |
| == |
| 0) |
| && |
| !(scrhdrp->cih_dumpattr |
| & |
| CIH_DUMPATTR_RESUME); |
| |
| mlog(MLOG_TRACE, |
| "initializing directory hierarchy image\n"); |
| ok = tree_init(tranp->t_hkdir, |
| persp->a.dstdir, |
| tranp->t_toconlypr, |
| persp->a.ownerpr, |
| scrhdrp->cih_rootino, |
| scrhdrp->cih_inomap_firstino, |
| scrhdrp->cih_inomap_lastino, |
| scrhdrp->cih_inomap_dircnt, |
| scrhdrp->cih_inomap_nondircnt, |
| tranp->t_vmsz, |
| fullpr, |
| persp->a.dstdirisxfspr, |
| grhdrp->gh_version, |
| tranp->t_truncategenpr); |
| if (!ok) { |
| Media_end(Mediap); |
| return mlog_exit(EXIT_ERROR, RV_ERROR); |
| } |
| tranp->t_treeinitdonepr = BOOL_TRUE; |
| |
| } else { |
| ok = tree_check_dump_format(grhdrp->gh_version); |
| if (!ok) { |
| Media_end(Mediap); |
| return mlog_exit(EXIT_ERROR, RV_ERROR); |
| } |
| } |
| |
| /* commit the session and accumulative state |
| */ |
| persp->s.valpr = BOOL_TRUE; |
| persp->a.valpr = BOOL_TRUE; |
| |
| mlog(MLOG_VERBOSE, _( |
| "reading directories\n")); |
| win_locks_off(); /* we are single threaded here */ |
| rv = applydirdump(drivep, fileh, scrhdrp, &fhdr); |
| win_locks_on(); |
| mlog(MLOG_TRACE, |
| "number of mmap calls for windows = %lu\n", win_getnum_mmaps()); |
| switch (rv) { |
| case RV_OK: |
| DH2F(fileh)->f_dirtriedpr = BOOL_TRUE; |
| Media_atnondir(Mediap); |
| tranp->t_sync3 = SYNC_DONE; |
| break; |
| case RV_CORRUPT: |
| Media_indir(Mediap); |
| DH2F(fileh)->f_dirtriedpr = BOOL_TRUE; |
| tranp->t_sync3 = SYNC_INIT; |
| break; |
| case RV_INTR: |
| case RV_DRIVE: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, rv); |
| case RV_CORE: |
| default: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| } |
| |
| /* now let one thread do all tree post-processing prior to |
| * non-dir restore |
| */ |
| if (persp->s.treepostdonepr) { |
| tranp->t_sync4 = SYNC_DONE; |
| } |
| while (tranp->t_sync4 != SYNC_DONE) { |
| lock(); |
| if (tranp->t_sync4 == SYNC_BUSY) { |
| unlock(); |
| mlog(MLOG_TRACE, |
| "waiting for directory post-processing " |
| "to complete\n"); |
| lock(); |
| } |
| while (tranp->t_sync4 == SYNC_BUSY) { |
| unlock(); |
| #if DEBUG_DUMPSTREAMS |
| { |
| static int count[STREAM_MAX] = {0}; |
| int streamix = stream_getix(pthread_self()); |
| if (++(count[streamix]) == 30) { |
| mlog(MLOG_NORMAL, |
| "still waiting for dirs post-processing\n"); |
| count[streamix] = 0; |
| } |
| } |
| #endif |
| sleep(1); |
| if (cldmgr_stop_requested()) { |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, RV_INTR); |
| } |
| lock(); |
| } |
| if (tranp->t_sync4 == SYNC_DONE) { |
| unlock(); |
| continue; |
| } |
| tranp->t_sync4 = SYNC_BUSY; |
| unlock(); |
| mlog(MLOG_VERBOSE, _( |
| "directory post-processing\n")); |
| win_locks_off(); /* we are single threaded here */ |
| rv = treepost(path1, path2); |
| win_locks_on(); |
| switch (rv) { |
| case RV_OK: |
| break; |
| case RV_ERROR: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_ERROR, RV_ERROR); |
| case RV_INTR: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_INTERRUPT, RV_INTR); |
| case RV_CORE: |
| default: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| |
| /* now that we have a tree and inomap, scan the |
| * pi to see what media files can be skipped. |
| * this func has cancer: too many flags and |
| * side-effects! |
| */ |
| { |
| bool_t dummyknownholespr; |
| bool_t dummymaybeholespr; |
| bag_t *bagp = pi_neededobjs_nondir_alloc(&dummyknownholespr, |
| &dummymaybeholespr, |
| BOOL_FALSE, |
| BOOL_TRUE); |
| if (bagp) { |
| pi_neededobjs_free(bagp); |
| bagp = 0; |
| } |
| } |
| |
| /* release exclusion |
| */ |
| tranp->t_sync4 = SYNC_DONE; |
| } |
| |
| /* now all are free to do concurrent non-dir restore! |
| * apply media files until there are no more, or we are interrupted |
| */ |
| for (;;) { |
| mlog(MLOG_DEBUG, |
| "getting next media file for non-dir restore\n"); |
| rv = Media_mfile_next(Mediap, |
| PURP_NONDIR, |
| 0, |
| &fileh, |
| &grhdrp, |
| &drhdrp, |
| &mrhdrp, |
| &crhdrp, |
| &scrhdrp, |
| &drivep, |
| &fhdr); |
| if (rv == RV_NOMORE) { |
| break; |
| } |
| switch (rv) { |
| case RV_OK: |
| break; |
| case RV_INTR: |
| case RV_QUIT: |
| case RV_DRIVE: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, rv); |
| case RV_CORE: |
| default: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| dcaps = drivep->d_capabilities; |
| assert(fileh > DH_NULL); |
| if (tranp->t_toconlypr) { |
| mlog(MLOG_VERBOSE, _( |
| "reading non-directory files\n")); |
| } else { |
| mlog(MLOG_VERBOSE, _( |
| "restoring non-directory files\n")); |
| } |
| mlog(MLOG_TRACE, |
| "media file %u in " |
| "object %u " |
| "of stream %u\n", |
| mrhdrp->mh_mediafileix, |
| mrhdrp->mh_mediaix, |
| drhdrp->dh_driveix); |
| mlog(MLOG_DEBUG, |
| "file %u in stream, " |
| "file %u in dump %u on object\n", |
| mrhdrp->mh_dumpfileix, |
| mrhdrp->mh_dumpmediafileix, |
| mrhdrp->mh_dumpmediaix); |
| rv = applynondirdump(drivep, |
| fileh, |
| scrhdrp, |
| path1, |
| path2, |
| &fhdr); |
| switch (rv) { |
| case RV_OK: |
| DH2F(fileh)->f_nondirdonepr = BOOL_TRUE; |
| Media_end(Mediap); |
| break; |
| case RV_INTR: |
| case RV_DRIVE: |
| case RV_INCOMPLETE: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_NORMAL, rv); |
| case RV_CORE: |
| default: |
| Media_end(Mediap); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| } |
| |
| /* finally, choose one thread to do final processing |
| * and cleanup. the winner waits, the losers all exit. |
| * once the losers exit, the winner can perform cleanup. |
| */ |
| lock(); |
| if (tranp->t_sync5 == SYNC_BUSY) { |
| unlock(); |
| return mlog_exit(EXIT_NORMAL, RV_DONE); |
| } |
| tranp->t_sync5 = SYNC_BUSY; |
| unlock(); |
| if (drivecnt > 1) { |
| mlog(MLOG_TRACE, |
| "waiting for other streams to exit\n"); |
| } |
| while (cldmgr_otherstreamsremain(thrdix)) { |
| sleep(1); |
| } |
| |
| mlog(MLOG_DEBUG, |
| "tree finalize\n"); |
| rv = finalize(path1, path2); |
| if (rv == RV_OK || rv == RV_INTR) { |
| rval = EXIT_NORMAL; |
| } else if (rv == RV_ERROR) { |
| rval = EXIT_ERROR; |
| } else { |
| rval = EXIT_FAULT; |
| } |
| |
| return mlog_exit(rval, rv); |
| } |
| |
| /* called after all threads have exited. scans state to decide |
| * if interrupted or not. |
| */ |
| bool_t |
| content_complete(void) |
| { |
| bool_t completepr; |
| time_t elapsed; |
| |
| if (!persp) { |
| completepr = BOOL_TRUE; |
| } else if (!persp->a.valpr) { |
| completepr = BOOL_TRUE; |
| } else if (!persp->s.valpr) { |
| completepr = BOOL_TRUE; |
| } else { |
| completepr = BOOL_FALSE; |
| } |
| |
| elapsed = time(0) - tranp->t_starttime; |
| if (persp) { |
| elapsed += persp->s.accumtime; |
| } |
| |
| if (completepr) { |
| if (tranp->t_toconlypr) { |
| mlog(MLOG_VERBOSE, _( |
| "table of contents display complete" |
| ": %ld seconds elapsed" |
| "\n"), |
| elapsed); |
| } else { |
| int found; |
| |
| found = quotafilecheck("user", |
| persp->a.dstdir, |
| CONTENT_QUOTAFILE); |
| found += quotafilecheck("project", |
| persp->a.dstdir, |
| CONTENT_PQUOTAFILE); |
| found += quotafilecheck("group", |
| persp->a.dstdir, |
| CONTENT_GQUOTAFILE); |
| |
| if (found) |
| mlog(MLOG_NORMAL, |
| _("use \'xfs_quota\' to restore quotas\n")); |
| |
| mlog(MLOG_VERBOSE, _( |
| "restore complete" |
| ": %ld seconds elapsed" |
| "\n"), |
| elapsed); |
| } |
| } else if (tranp->t_toconlypr) { |
| mlog(MLOG_VERBOSE | MLOG_NOTE, _( |
| "table of contents display interrupted" |
| ": %ld seconds elapsed" |
| "\n"), |
| elapsed); |
| } else { |
| mlog(MLOG_VERBOSE | MLOG_NOTE, _( |
| "restore interrupted" |
| ": %ld seconds elapsed" |
| ": may resume later using -%c option" |
| "\n"), |
| elapsed, |
| GETOPT_RESUME); |
| } |
| |
| /* accumulate total elapsed time |
| */ |
| if (persp) { |
| persp->s.accumtime = elapsed; |
| } |
| |
| if (!persp->a.valpr) { |
| wipepersstate(); |
| persp = 0; |
| } |
| |
| return completepr; |
| } |
| |
| #define STATLINESZ 160 |
| |
| size_t |
| content_statline(char **linespp[]) |
| { |
| static char statlinebuf[1][STATLINESZ]; |
| static char *statline[1]; |
| size64_t inodone; |
| off64_t datadone; |
| size64_t inocnt; |
| off64_t datacnt; |
| double percent; |
| time_t elapsed; |
| time_t now; |
| struct tm *tmp; |
| ix_t i; |
| |
| /* build and supply the line array |
| */ |
| for (i = 0; i < 1; i++) { |
| statline[i] = &statlinebuf[i][0]; |
| } |
| *linespp = statline; |
| |
| if (!persp->s.stat_valpr) { |
| return 0; |
| } |
| |
| /* calculate the elapsed time |
| */ |
| elapsed = persp->s.accumtime |
| + |
| (time(0) - tranp->t_starttime); |
| |
| /* get local time |
| */ |
| now = time(0); |
| tmp = localtime(&now); |
| |
| if (!persp->s.dirdonepr) { |
| if (!tranp->t_dircnt) { |
| return 0; |
| } |
| |
| percent = (double)tranp->t_dirdonecnt |
| / |
| (double)tranp->t_dircnt; |
| percent *= 100.0; |
| sprintf(statline[0], _( |
| "status at %02d:%02d:%02d: " |
| "%llu/%llu directories reconstructed, " |
| "%.1f%%%% complete, " |
| "%llu directory entries processed, " |
| "%ld seconds elapsed\n"), |
| tmp->tm_hour, |
| tmp->tm_min, |
| tmp->tm_sec, |
| (unsigned long long)tranp->t_dirdonecnt, |
| (unsigned long long)tranp->t_dircnt, |
| percent, |
| (unsigned long long)tranp->t_direntcnt, |
| elapsed); |
| assert(strlen(statline[0]) < STATLINESZ); |
| |
| return 1; |
| } |
| |
| /* get the accumulated totals for non-dir inos and data bytes dumped. |
| * not under lock! |
| */ |
| inodone = persp->s.stat_inodone; |
| datadone = persp->s.stat_datadone; |
| inocnt = persp->s.stat_inocnt; |
| datacnt = persp->s.stat_datacnt; |
| |
| /* calculate percentage of data dumped |
| */ |
| if (datacnt) { |
| percent = (double)datadone |
| / |
| (double)datacnt; |
| percent *= 100.0; |
| } else { |
| percent = 100.0; |
| } |
| if (percent > 100.0) { |
| percent = 100.0; |
| } |
| |
| /* format the status line in a local static buffer (non-re-entrant!) |
| */ |
| sprintf(statline[0], _( |
| "status at %02d:%02d:%02d: %llu/%llu files restored, " |
| "%.1f%%%% complete, " |
| "%ld seconds elapsed\n"), |
| tmp->tm_hour, |
| tmp->tm_min, |
| tmp->tm_sec, |
| (unsigned long long)inodone, |
| (unsigned long long)inocnt, |
| percent, |
| elapsed); |
| assert(strlen(statline[0]) < STATLINESZ); |
| |
| /* return buffer to caller |
| */ |
| return 1; |
| } |
| |
| void |
| content_showinv(void) |
| { |
| pi_show_nomloglock(); |
| } |
| |
| void |
| content_showremainingobjects(void) |
| { |
| bool_t knownholespr = BOOL_FALSE; |
| bool_t maybeholespr = BOOL_FALSE; |
| bag_t *bagp; |
| |
| bagp = pi_neededobjs_nondir_alloc(&knownholespr, |
| &maybeholespr, |
| BOOL_TRUE, |
| BOOL_FALSE); |
| display_needed_objects(PURP_NONDIR, |
| bagp, |
| knownholespr, |
| maybeholespr); |
| if (bagp) { |
| pi_neededobjs_free(bagp); |
| bagp = 0; |
| } |
| } |
| |
| /* dlog_begin already called; this is a second-level dialog. |
| * prompt for each thread currently waiting for confirmation, |
| * as well as an info prompt. |
| */ |
| #define PREAMBLEMAX 3 |
| #define QUERYMAX 36 |
| #define CHOICEMAX 30 |
| #define ACKMAX 3 |
| #define POSTAMBLEMAX 3 |
| #define DLOG_TIMEOUT 300 |
| #define DLOG_TIMEOUT_MEDIA 3600 |
| |
| #define CHOICESTRSZ 10 |
| typedef struct { ix_t thrdix; char choicestr[CHOICESTRSZ]; } cttm_t; |
| |
| char * |
| content_mediachange_query(void) |
| { |
| cttm_t choicetothrdmap[STREAM_SIMMAX]; |
| char *querystr[QUERYMAX]; |
| size_t querycnt; |
| char *choicestr[CHOICEMAX]; |
| size_t choicecnt; |
| char *ackstr[ACKMAX]; |
| size_t ackcnt; |
| size_t maxdrvchoiceix; |
| size_t infoix; |
| size_t nochangeix; |
| size_t responseix; |
| ix_t thrdix; |
| |
| infoix = querycnt = 0; |
| querystr[querycnt++] = |
| _("select a drive to acknowledge media change\n"); |
| choicecnt = 0; |
| maxdrvchoiceix = 0; |
| for (thrdix = 0; thrdix < STREAM_SIMMAX; thrdix++) { |
| if (mcflag[thrdix]) { |
| choicetothrdmap[choicecnt].thrdix = thrdix; |
| sprintf(choicetothrdmap[choicecnt].choicestr, |
| _("drive %u"), |
| (unsigned int)thrdix); |
| choicestr[choicecnt] = |
| choicetothrdmap[choicecnt].choicestr; |
| maxdrvchoiceix = choicecnt; |
| choicecnt++; |
| } |
| } |
| if (persp->s.valpr) { |
| infoix = choicecnt; |
| choicestr[choicecnt++ ] = _("display needed media objects"); |
| } |
| nochangeix = choicecnt; |
| choicestr[choicecnt++ ] = _("continue"); |
| assert(choicecnt <= CHOICEMAX); |
| responseix = dlog_multi_query(querystr, |
| querycnt, |
| choicestr, |
| choicecnt, |
| 0, /* hilitestr */ |
| IXMAX, /* hiliteix */ |
| 0, /* defaultstr */ |
| nochangeix, /* defaultix */ |
| DLOG_TIMEOUT, |
| nochangeix, /* timeout ix */ |
| nochangeix, /* sigint ix */ |
| nochangeix, /* sighup ix */ |
| nochangeix);/* sigquit ix */ |
| if (responseix <= maxdrvchoiceix) { |
| clr_mcflag(choicetothrdmap[responseix].thrdix); |
| return _("media change acknowledged\n"); |
| } |
| if (responseix == infoix) { |
| bool_t knownholespr = BOOL_FALSE; |
| bool_t maybeholespr = BOOL_FALSE; |
| bag_t *bagp = pi_neededobjs_nondir_alloc(&knownholespr, |
| &maybeholespr, |
| BOOL_FALSE, |
| BOOL_FALSE); |
| display_needed_objects(PURP_NONDIR, |
| bagp, |
| knownholespr, |
| maybeholespr); |
| if (bagp) { |
| pi_neededobjs_free(bagp); |
| bagp = 0; |
| } |
| ackcnt = 0; |
| dlog_multi_ack(ackstr, |
| ackcnt); |
| querycnt = 0; |
| choicecnt = 0; |
| nochangeix = choicecnt; |
| choicestr[choicecnt++ ] = _("continue"); |
| assert(choicecnt <= CHOICEMAX); |
| responseix = dlog_multi_query(querystr, |
| querycnt, |
| choicestr, |
| choicecnt, |
| 0, /* hilitestr */ |
| IXMAX, /* hiliteix */ |
| 0, /* defaultstr */ |
| nochangeix, /* defaultix */ |
| DLOG_TIMEOUT, |
| nochangeix, /* timeout ix */ |
| nochangeix, /* sigint ix */ |
| nochangeix, /* sighup ix */ |
| nochangeix);/* sigquit ix */ |
| return _("continuing\n"); |
| } |
| assert(responseix == nochangeix); |
| return _("continuing\n"); |
| } |
| |
| /* definition of locally defined static functions ****************************/ |
| |
| /* does all pre-processing leading up to applying the dirdump, |
| * then applies the dirdump. updates pers progress flags along the way. |
| * does NOT do any post-processing! |
| */ |
| /* ARGSUSED */ |
| static rv_t |
| applydirdump(drive_t *drivep, |
| dh_t fileh, |
| content_inode_hdr_t *scrhdrp, |
| filehdr_t *fhdrp) |
| { |
| bool_t fhcs; |
| bool_t dhcs; |
| bool_t ahcs; |
| |
| fhcs = (scrhdrp->cih_dumpattr & CIH_DUMPATTR_FILEHDR_CHECKSUM) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| dhcs = (scrhdrp->cih_dumpattr & CIH_DUMPATTR_DIRENTHDR_CHECKSUM) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| ahcs = (scrhdrp->cih_dumpattr & CIH_DUMPATTR_EXTATTRHDR_CHECKSUM) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| |
| if (!persp->s.marknorefdonepr) { |
| tree_marknoref(); |
| persp->s.marknorefdonepr = BOOL_TRUE; |
| } |
| |
| if (scrhdrp->cih_dumpattr & CIH_DUMPATTR_NOTSELFCONTAINED) { |
| mlog(MLOG_NORMAL | MLOG_NOTE, _( |
| "dump is not self-contained, " |
| "orphaned files expected if base dump(s) " |
| "was not applied\n")); |
| } |
| |
| if (!persp->s.dirdonepr) { |
| rv_t rv; |
| dah_t dah; |
| |
| char _direntbuf[sizeof(direnthdr_t) |
| + |
| NAME_MAX + 1 |
| + |
| DIRENTHDR_ALIGN]; |
| char *direntbuf = ALIGN_PTR(_direntbuf, DIRENTHDR_ALIGN); |
| size_t direntbufsz = |
| sizeof(_direntbuf) - (direntbuf - _direntbuf); |
| |
| mlog(MLOG_TRACE, |
| "reading the ino map\n"); |
| rv = inomap_restore_pers(drivep, scrhdrp, tranp->t_hkdir); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| |
| tranp->t_dircnt = scrhdrp->cih_inomap_dircnt; |
| tranp->t_dirdonecnt = 0; |
| tranp->t_direntcnt = 0; |
| |
| mlog(MLOG_TRACE, |
| "reading the directories \n"); |
| |
| dah = DAH_NULL; |
| for (;;) { |
| nh_t dirh; |
| |
| /* read the file header |
| */ |
| rv = read_filehdr(drivep, fhdrp, fhcs); |
| if (rv) { |
| return rv; |
| } |
| |
| /* if this is a null file hdr, we're done |
| * reading dirs, and there are no nondirs. |
| * done. |
| */ |
| if (fhdrp->fh_flags & FILEHDR_FLAGS_NULL) { |
| break; |
| } |
| |
| /* if its not a directory, must be the |
| * first non-dir file. done. |
| */ |
| if ((fhdrp->fh_stat.bs_mode & S_IFMT) != S_IFDIR) { |
| break; |
| } |
| |
| /* if stop requested bail out gracefully |
| */ |
| if (cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| |
| /* if in a pipeline, call preemptchk() to |
| * print status reports |
| */ |
| if (pipeline) |
| { |
| mlog(MLOG_DEBUG, |
| "preemptchk( )\n"); |
| preemptchk(); |
| } |
| |
| /* may be an extended attributes file hdr |
| */ |
| if (fhdrp->fh_flags & FILEHDR_FLAGS_EXTATTR) { |
| rv = restore_extattr(drivep, |
| fhdrp, |
| 0, |
| ahcs, |
| BOOL_TRUE, /* isdirpr */ |
| BOOL_FALSE, /* onlydoreadpr */ |
| dah); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| continue; |
| } |
| |
| /* add the directory to the tree. save the |
| * returned tree node id, to associate with |
| * the directory entries. get the dirattr handle, |
| * so that any extattr following will be associated |
| * with that dir. |
| */ |
| dah = DAH_NULL; |
| dirh = tree_begindir(fhdrp, &dah); |
| if (dirh == NH_NULL) |
| return RV_ERROR; |
| |
| /* read the directory entries, and populate the |
| * tree with them. we can tell when we are done |
| * by looking for a null dirent. |
| */ |
| for (;;) { |
| register direnthdr_t *dhdrp = |
| (direnthdr_t *)direntbuf; |
| register size_t namelen; |
| |
| rv = read_dirent(drivep, |
| dhdrp, |
| direntbufsz, |
| dhcs); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| |
| /* if null, we're done with this dir. |
| * break out of this inner loop and |
| * move on th the next dir. |
| */ |
| if (dhdrp->dh_ino == 0) { |
| break; |
| } |
| namelen = strlen(dhdrp->dh_name); |
| assert(namelen <= NAME_MAX); |
| |
| /* add this dirent to the tree. |
| */ |
| rv = tree_addent(dirh, |
| dhdrp->dh_ino, |
| dhdrp->dh_gen, |
| dhdrp->dh_name, |
| namelen); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| tranp->t_direntcnt++; |
| } |
| |
| tree_enddir(dirh); |
| |
| tranp->t_dirdonecnt++; |
| } |
| |
| if ((rv = dirattr_flush()) != RV_OK) { |
| return rv; |
| } |
| |
| if ((rv = namreg_map()) != RV_OK) { |
| return rv; |
| } |
| |
| if (need_fixrootdir) |
| tree_fixroot(); |
| persp->s.dirdonepr = BOOL_TRUE; |
| } |
| |
| mlog(MLOG_VERBOSE, _("%llu directories and %llu entries processed\n"), |
| tranp->t_dirdonecnt, tranp->t_direntcnt); |
| |
| return RV_OK; |
| } |
| |
| /* like applydirdump, but just eats up the inomap/dirdump portion of the |
| * dump, doesn't use it. the first non-dir filehdr_t is copied into supplied |
| * buffer. returns integer error code from drive ops used. |
| */ |
| /* ARGSUSED */ |
| rv_t |
| eatdirdump(drive_t *drivep, |
| dh_t fileh, |
| content_inode_hdr_t *scrhdrp, |
| filehdr_t *fhdrp) |
| { |
| bool_t fhcs; |
| bool_t dhcs; |
| bool_t ahcs; |
| |
| char _direntbuf[sizeof(direnthdr_t) |
| + |
| NAME_MAX + 1 |
| + |
| DIRENTHDR_ALIGN]; |
| char *direntbuf = ALIGN_PTR(_direntbuf, DIRENTHDR_ALIGN); |
| size_t direntbufsz = sizeof(_direntbuf) - (direntbuf - _direntbuf); |
| rv_t rv; |
| |
| fhcs = (scrhdrp->cih_dumpattr & CIH_DUMPATTR_FILEHDR_CHECKSUM) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| dhcs = (scrhdrp->cih_dumpattr & CIH_DUMPATTR_DIRENTHDR_CHECKSUM) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| ahcs = (scrhdrp->cih_dumpattr & CIH_DUMPATTR_EXTATTRHDR_CHECKSUM) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| |
| mlog(MLOG_DEBUG, |
| "discarding ino map\n"); |
| rv = inomap_discard(drivep, scrhdrp); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| |
| mlog(MLOG_DEBUG, |
| "discarding directories \n"); |
| for (;;) { |
| /* read the file header |
| */ |
| rv = read_filehdr(drivep, fhdrp, fhcs); |
| if (rv) { |
| return rv; |
| } |
| |
| /* if this is a null file hdr, we're done |
| * reading dirs, and there are no nondirs. |
| * done. |
| */ |
| if (fhdrp->fh_flags & FILEHDR_FLAGS_NULL) { |
| break; |
| } |
| |
| /* if its not a directory, must be the |
| * first non-dir file. done. |
| */ |
| if ((fhdrp->fh_stat.bs_mode & S_IFMT) != S_IFDIR) { |
| break; |
| } |
| |
| /* if stop requested bail out gracefully |
| */ |
| if (cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| |
| /* may be an extended attributes file hdr |
| */ |
| if (fhdrp->fh_flags & FILEHDR_FLAGS_EXTATTR) { |
| rv = restore_extattr(drivep, |
| fhdrp, |
| 0, |
| ahcs, |
| BOOL_TRUE, /* isdirpr */ |
| BOOL_TRUE, /* onlydoreadpr */ |
| DAH_NULL); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| continue; |
| } |
| |
| /* read the directory entries. |
| * we can tell when we are done |
| * by looking for a null dirent. |
| */ |
| for (;;) { |
| register direnthdr_t *dhdrp = |
| (direnthdr_t *)direntbuf; |
| /* REFERENCED */ |
| register size_t namelen; |
| |
| rv = read_dirent(drivep, |
| dhdrp, |
| direntbufsz, |
| dhcs); |
| if (rv) { |
| return rv; |
| } |
| |
| /* if null, we're done with this dir. |
| * break out of this inner loop and |
| * move on th the next dir. |
| */ |
| if (dhdrp->dh_ino == 0) { |
| break; |
| } |
| namelen = strlen(dhdrp->dh_name); |
| assert(namelen <= NAME_MAX); |
| } |
| } |
| |
| return RV_OK; |
| } |
| |
| /* does all post-processing of the tree required prior to restoral of |
| * the non-directory portion of the dump. only one thread at a time, |
| * so no locking needed. since the steps are interdependent, loops |
| * until no point in doing so. |
| */ |
| static rv_t |
| treepost(char *path1, char *path2) |
| { |
| bool_t ok; |
| |
| #ifdef TREE_CHK |
| /* first scan the tree for corruption |
| */ |
| mlog(MLOG_DEBUG | MLOG_TREE, |
| "checking tree for consistency\n"); |
| if (!tree_chk()) { |
| return RV_CORE; |
| } |
| #endif /* TREE_CHK */ |
| |
| /* adjust ref flags based on what dirs were dumped |
| */ |
| if (!persp->s.adjrefdonepr) { |
| mlog(MLOG_DEBUG | MLOG_TREE, |
| "adjusting dirent ref flags\n"); |
| ok = tree_adjref(); |
| if (!ok) { |
| return RV_INTR; |
| } |
| persp->s.adjrefdonepr = BOOL_TRUE; |
| } |
| |
| /* if a subtree or interactive restore, sanitize the inomap |
| * so only inos selected by subtree or interactive cmds will |
| * be present in inomap. |
| */ |
| if (!persp->s.inomapsanitizedonepr) { |
| if (persp->a.interpr |
| || |
| (persp->a.firststsenseprvalpr |
| && |
| persp->a.firststsensepr)) { |
| inomap_sanitize(); |
| } |
| persp->s.inomapsanitizedonepr = BOOL_TRUE; |
| } |
| |
| /* apply subtree selections |
| */ |
| if (!persp->s.stdonepr) { |
| ix_t stix; |
| stdesc_t *stdescp; |
| |
| mlog(MLOG_DEBUG | MLOG_TREE, |
| "applying subtree selections\n"); |
| |
| /* if first subtree selection is inclusive in sense, |
| * first mark the entire tree as unselected. otherwise, |
| * select all (no subtree selections or first was excluding). |
| */ |
| if ((persp->a.interpr |
| && |
| (!persp->a.firststsenseprvalpr |
| || |
| persp->a.firststsensepr)) |
| || |
| (persp->a.firststsenseprvalpr |
| && |
| persp->a.firststsensepr)) { |
| tree_markallsubtree(BOOL_FALSE); |
| } else { |
| tree_markallsubtree(BOOL_TRUE); |
| } |
| |
| /* now apply all subtree commands from command line |
| */ |
| for (stix = 0, |
| stdescp = (stdesc_t *)((char *)persp + perssz) |
| ; |
| stix < persp->a.stcnt |
| ; |
| stix++, |
| stdescp = (stdesc_t *)((char *)stdescp |
| + |
| stdescp->std_nextoff)) { |
| ok = tree_subtree_parse(stdescp->std_sensepr, |
| stdescp->std_path); |
| if (!ok) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "subtree argument %s invalid\n"), |
| stdescp->std_path); |
| return RV_ERROR; |
| } |
| } |
| persp->s.stdonepr = BOOL_TRUE; |
| } |
| |
| /* next engage interactive subtree selection |
| */ |
| if (!persp->s.interdonepr) { |
| if (persp->a.interpr) { |
| ok = tree_subtree_inter(); |
| if (!ok) { |
| return RV_INTR; |
| } |
| } |
| persp->s.interdonepr = BOOL_TRUE; |
| } |
| |
| ok = tree_post(path1, path2); |
| |
| if (!ok) { |
| return RV_INTR; |
| } |
| |
| ok = tree_extattr(restore_dir_extattr_cb, path1); |
| if (!ok) { |
| return RV_INTR; |
| } |
| |
| persp->s.treepostdonepr = BOOL_TRUE; |
| |
| return RV_OK; |
| } |
| |
| static rv_t |
| applynondirdump(drive_t *drivep, |
| dh_t fileh, |
| content_inode_hdr_t *scrhdrp, |
| char *path1, |
| char *path2, |
| filehdr_t *fhdrp) |
| { |
| rv_t rv = RV_UNKNOWN; |
| bool_t fhcs; |
| bool_t ehcs; |
| bool_t ahcs; |
| egrp_t first_egrp; |
| egrp_t next_egrp; |
| stream_context_t *strctxp = (stream_context_t *)drivep->d_strmcontextp; |
| |
| /* determine if file header and/or extent heade checksums present |
| */ |
| fhcs = (scrhdrp->cih_dumpattr & CIH_DUMPATTR_FILEHDR_CHECKSUM) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| ehcs = (scrhdrp->cih_dumpattr & CIH_DUMPATTR_EXTENTHDR_CHECKSUM) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| ahcs = (scrhdrp->cih_dumpattr & CIH_DUMPATTR_EXTATTRHDR_CHECKSUM) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| |
| /* determine the first and next egrps needed from this media file. |
| * used to decide if stats should be updated |
| */ |
| pi_bracketneededegrps(fileh, &first_egrp, &next_egrp); |
| |
| /* initialize the stream context |
| */ |
| memset(&strctxp->sc_bstat, 0, sizeof(bstat_t)); |
| strctxp->sc_path[0] = '\0'; |
| strctxp->sc_fd = -1; |
| strctxp->sc_ownerset = BOOL_FALSE; |
| |
| |
| for (;;) { |
| drive_ops_t *dop = drivep->d_opsp; |
| drive_mark_t drivemark; |
| bstat_t *bstatp = &fhdrp->fh_stat; |
| bool_t resyncpr = BOOL_FALSE; |
| int rval; |
| |
| /* if a null file header, break |
| */ |
| if (fhdrp->fh_flags & FILEHDR_FLAGS_NULL) { |
| rv = RV_OK; |
| goto applynondirdump_out; |
| } |
| |
| /* if working on a different file than we were previously, |
| * complete the old one and begin the new one. |
| */ |
| if (bstatp->bs_ino != strctxp->sc_bstat.bs_ino) { |
| |
| restore_complete_reg(strctxp); |
| |
| /* start new ino */ |
| memcpy(&strctxp->sc_bstat, bstatp, sizeof(bstat_t)); |
| strctxp->sc_path[0] = '\0'; |
| strctxp->sc_fd = -1; |
| strctxp->sc_ownerset = BOOL_FALSE; |
| |
| rv = restore_file(drivep, fhdrp, ehcs, ahcs, path1, path2); |
| |
| } else if (fhdrp->fh_flags & FILEHDR_FLAGS_EXTATTR) { |
| rv = restore_extattr(drivep, |
| fhdrp, |
| strctxp->sc_path, |
| ahcs, |
| BOOL_FALSE, /* isdirpr */ |
| BOOL_FALSE, /* onlydoreadpr */ |
| DAH_NULL); |
| } else { |
| /* Must be another extent group for the current file */ |
| restore_extent_group(drivep, |
| fhdrp, |
| strctxp->sc_path, |
| strctxp->sc_fd, |
| ehcs, |
| &rv); |
| } |
| |
| switch(rv) { |
| case RV_OK: |
| break; |
| case RV_EOD: |
| rv = RV_OK; |
| goto applynondirdump_out; |
| case RV_CORRUPT: |
| rval = (*dop->do_next_mark)(drivep); |
| if (rval) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "unable to resync media file: " |
| "some portion of dump will NOT " |
| "be restored\n")); |
| rv = RV_OK; /* treat as EOD */ |
| goto applynondirdump_out; |
| } |
| resyncpr = BOOL_TRUE; |
| break; |
| default: |
| goto applynondirdump_out; |
| } |
| |
| /* update stats if appropriate |
| */ |
| if (((bstatp->bs_mode & S_IFMT) == S_IFREG) |
| && |
| !(fhdrp->fh_flags & FILEHDR_FLAGS_EXTATTR) |
| && |
| fhdrp->fh_offset == 0) { |
| egrp_t cur_egrp; |
| cur_egrp.eg_ino = fhdrp->fh_stat.bs_ino; |
| cur_egrp.eg_off = fhdrp->fh_offset; |
| if (cur_egrp.eg_ino > first_egrp.eg_ino) { |
| if (cur_egrp.eg_ino < next_egrp.eg_ino |
| || |
| next_egrp.eg_off > 0) { |
| assert(cur_egrp.eg_ino |
| <= |
| next_egrp.eg_ino); |
| pi_update_stats(bstatp->bs_blocks |
| * |
| (off64_t) |
| bstatp->bs_blksize); |
| } |
| } |
| } |
| |
| do { |
| /* get a mark for the next read, in case we restart here |
| */ |
| (*dop->do_get_mark)(drivep, &drivemark); |
| |
| /* read the file header. |
| */ |
| rv = read_filehdr(drivep, fhdrp, fhcs); |
| switch(rv) { |
| case RV_OK: |
| break; |
| case RV_EOD: |
| rv = RV_OK; |
| goto applynondirdump_out; |
| case RV_CORRUPT: |
| rval = (*dop->do_next_mark)(drivep); |
| if (rval) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "unable to resync media file: " |
| "some portion of dump will NOT " |
| "be restored\n")); |
| rv = RV_OK; /* treat as EOD */ |
| goto applynondirdump_out; |
| } |
| resyncpr = BOOL_TRUE; |
| default: |
| goto applynondirdump_out; |
| } |
| |
| if (resyncpr && rv == RV_OK) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "resynchronization achieved at " |
| "ino %llu offset %lld\n"), |
| fhdrp->fh_stat.bs_ino, |
| fhdrp->fh_offset); |
| resyncpr = BOOL_FALSE; |
| } |
| } while (resyncpr); |
| |
| /* checkpoint into persistent state if not a null file hdr |
| */ |
| if (!(fhdrp->fh_flags & FILEHDR_FLAGS_NULL)) { |
| pi_checkpoint(fileh, |
| &drivemark, |
| fhdrp->fh_stat.bs_ino, |
| fhdrp->fh_offset); |
| } |
| |
| /* if in a pipeline, call preemptchk() to |
| * print status reports |
| */ |
| if (pipeline) |
| { |
| mlog(MLOG_DEBUG, |
| "preemptchk( )\n"); |
| preemptchk(); |
| } |
| } |
| |
| applynondirdump_out: |
| |
| /* We've hit the end of this media file or encountered corruption. |
| * In either case, we may not be back to complete the metadata for |
| * this file, so attempt to complete it now. |
| */ |
| restore_complete_reg(strctxp); |
| |
| return rv; |
| } |
| |
| /* ARGSUSED */ |
| static rv_t |
| finalize(char *path1, char *path2) |
| { |
| bool_t ok; |
| |
| if (!tranp->t_toconlypr) { |
| |
| /* restore directory attributes |
| */ |
| if (!persp->s.dirattrdonepr) {; |
| ok = tree_setattr(path1); |
| if (!ok) { |
| return RV_INTR; |
| } |
| persp->s.dirattrdonepr = BOOL_TRUE; |
| } |
| |
| /* remove orphanage if empty |
| */ |
| if (!persp->s.orphdeltriedpr) {; |
| ok = tree_delorph(); |
| if (!ok) { |
| return RV_INTR; |
| } |
| persp->s.orphdeltriedpr = BOOL_TRUE; |
| } |
| |
| /* delete the persistent ino map |
| */ |
| if (!persp->s.inomapdelpr) { |
| inomap_del_pers(tranp->t_hkdir); |
| persp->s.inomapdelpr = BOOL_TRUE; |
| } |
| } |
| |
| /* at this point, all session-only persistent state has been deleted. |
| * if this is a cumulative restore, just update the pers cum state and |
| * invalidate the pers session state. otherwise, invalidate the |
| * persistent state. content_complete will remove housekeeping dir. |
| */ |
| if (persp->a.cumpr) { |
| /* following must be atomic! |
| */ |
| persp->a.dumpcnt++; |
| uuid_copy(persp->a.lastdumpid, persp->s.dumpid); |
| strcpy(persp->a.lastdumplab, persp->s.dumplab); |
| persp->s.valpr = BOOL_FALSE; |
| } else { |
| persp->a.valpr = BOOL_FALSE; |
| } |
| return RV_OK; |
| } |
| |
| static void |
| toconly_cleanup(void) |
| { |
| if (!tranp) { |
| return; |
| } |
| |
| if (!tranp->t_toconlypr) { |
| return; |
| } |
| |
| if (!tranp->t_hkdir) { |
| return; |
| } |
| |
| if (!strlen(tranp->t_hkdir)) { |
| return; |
| } |
| |
| wipepersstate(); |
| } |
| |
| static void |
| wipepersstate(void) |
| { |
| DIR *dirp; |
| struct dirent64 *direntp; |
| char pathname[MAXPATHLEN]; |
| dirp = opendir(tranp->t_hkdir); |
| if (!dirp) { |
| return; |
| } |
| |
| while ((direntp = readdir64(dirp)) != 0) { |
| /* REFERENCED */ |
| int len; |
| if (!strcmp(direntp->d_name, ".")) { |
| continue; |
| } |
| if (!strcmp(direntp->d_name, "..")) { |
| continue; |
| } |
| len = sprintf(pathname, |
| "%s/%s", |
| tranp->t_hkdir, |
| direntp->d_name); |
| assert(len > 0); |
| assert(len < MAXPATHLEN); |
| (void)unlink(pathname); |
| closedir(dirp); |
| dirp = opendir(tranp->t_hkdir); |
| if (!dirp) { |
| return; |
| } |
| } |
| closedir(dirp); |
| |
| rmdir(tranp->t_hkdir); |
| } |
| |
| /* Inv abstraction ***********************************************************/ |
| |
| /* attempt to validate id or label against online inventory. |
| * sets pers id/label and pers idvalpr etc as side-effect (does NOT set valpr!) |
| */ |
| static bool_t |
| Inv_validate_cmdline(void) |
| { |
| inv_session_t *sessp; |
| bool_t ok; |
| bool_t rok; |
| |
| assert(!persp->s.valpr); |
| |
| ok = BOOL_FALSE; |
| sessp = 0; |
| if (tranp->t_reqdumpidvalpr) { |
| ok = inv_get_session_byuuid(NULL, &tranp->t_reqdumpid, &sessp); |
| } else if (tranp->t_reqdumplabvalpr) { |
| ok = inv_get_session_bylabel(NULL, tranp->t_reqdumplab, &sessp); |
| } |
| rok = BOOL_FALSE; |
| if (ok && sessp) { |
| uuid_t baseid; |
| |
| uuid_clear(baseid); |
| askinvforbaseof(baseid, sessp); |
| if (!dumpcompat(sessp->s_isresumed, |
| (ix_t)(sessp->s_level), |
| baseid, |
| BOOL_TRUE)) { |
| inv_free_session(&sessp); |
| return BOOL_ERROR; |
| } |
| |
| mlog(MLOG_VERBOSE, _( |
| "using online session inventory\n")); |
| persp->s.fullinvpr = pi_transcribe(sessp); |
| if (persp->s.fullinvpr) { |
| strncpyterm(persp->s.dumplab, |
| sessp->s_label, |
| sizeof(persp->s.dumplab)); |
| uuid_copy(persp->s.dumpid, sessp->s_sesid); |
| rok = BOOL_TRUE; |
| } |
| inv_free_session(&sessp); |
| } |
| return rok; |
| } |
| |
| |
| /* Media abstraction *********************************************************/ |
| |
| |
| static Media_t * |
| Media_create(ix_t thrdix) |
| { |
| drive_t *drivep = drivepp[thrdix]; |
| global_hdr_t *grhdrp = drivep->d_greadhdrp; |
| drive_hdr_t *drhdrp = drivep->d_readhdrp; |
| media_hdr_t *mrhdrp = (media_hdr_t *)drhdrp->dh_upper; |
| content_hdr_t *crhdrp = (content_hdr_t *)mrhdrp->mh_upper; |
| content_inode_hdr_t *scrhdrp = |
| (content_inode_hdr_t *)crhdrp->ch_specific; |
| Media_t *Mediap; |
| |
| |
| mlog(MLOG_DEBUG, |
| "Media_create\n"); |
| |
| Mediap = (Media_t *)calloc(1, sizeof(Media_t)); |
| Mediap->M_drivep = drivep; |
| Mediap->M_grhdrp = grhdrp; |
| Mediap->M_drhdrp = drhdrp; |
| Mediap->M_mrhdrp = mrhdrp; |
| Mediap->M_crhdrp = crhdrp; |
| Mediap->M_scrhdrp = scrhdrp; |
| assert(POS_UNKN == 0); |
| |
| return Mediap; |
| } |
| |
| /* these calls allow the Media users to clue Media in to fine position changes |
| * within the current media file |
| */ |
| static void |
| Media_indir(Media_t *Mediap) |
| { |
| mlog(MLOG_DEBUG, |
| "Media_indir\n"); |
| |
| Mediap->M_pos = POS_INDIR; |
| } |
| |
| static void |
| Media_atnondir(Media_t *Mediap) |
| { |
| mlog(MLOG_DEBUG, |
| "Media_atnondir\n"); |
| |
| Mediap->M_pos = POS_ATNONDIR; |
| } |
| |
| /* supplies pertinent media files to the caller. if purpose is search, |
| * returns all media files. otherwise, returns only media files with the |
| * dump ID. smart enough to know that if purpose was search but is now dir, |
| * current media file can be returned again. same for other transitions. |
| * always traverses the media object in a forward direction, beginning with |
| * current media file, wrapping around to beginning of media if necessary. |
| * also supplies fresh hdr pointers and drive manager. in current |
| * implementation these do not change, but will when we use new TLM. does |
| * fine positioning within media file according to purpose of request. |
| * |
| * Note: |
| * The difference between rval and rv. |
| * rval is used for the drive_* functions (e.g. do_begin_read) |
| * and will take on values such as DRIVE_ERROR_*. |
| * However, it also set to 0 for no error and 1 for error. |
| * It is defaulted to 0 for no error. |
| * rv, however, is of type rv_t and is used for functions returning |
| * rv_t and for the result of this function. |
| * It takes on values like RV_OK, RV_EOF. |
| */ |
| static rv_t |
| Media_mfile_next(Media_t *Mediap, |
| purp_t purp, |
| sync_t *donesyncp, |
| dh_t *filehp, |
| global_hdr_t **grhdrpp, |
| drive_hdr_t **drhdrpp, |
| media_hdr_t **mrhdrpp, |
| content_hdr_t **crhdrpp, |
| content_inode_hdr_t **scrhdrpp, |
| drive_t **drivepp, |
| filehdr_t *fhdrp) /*caller-supplied buf if NONDIR purp*/ |
| { |
| drive_t *drivep = Mediap->M_drivep; |
| drive_ops_t *dop = drivep->d_opsp; |
| global_hdr_t *grhdrp = Mediap->M_grhdrp; |
| drive_hdr_t *drhdrp = Mediap->M_drhdrp; |
| media_hdr_t *mrhdrp = Mediap->M_mrhdrp; |
| content_hdr_t *crhdrp = Mediap->M_crhdrp; |
| content_inode_hdr_t *scrhdrp = Mediap->M_scrhdrp; |
| dh_t fileh; |
| int rval = 0; /* no error by default */ |
| rv_t rv; |
| bool_t ok; |
| uuid_t prevmfiledumpid; |
| |
| mlog(MLOG_DEBUG | MLOG_MEDIA, |
| "Media_mfile_next: purp==%d pos==%d\n", |
| purp, |
| Mediap->M_pos); |
| |
| /* pass back hdr and drive ptrs |
| */ |
| *grhdrpp = grhdrp; |
| *drhdrpp = drhdrp; |
| *mrhdrpp = mrhdrp; |
| *crhdrpp = crhdrp; |
| *scrhdrpp = scrhdrp; |
| *drivepp = drivep; |
| |
| /* if ref return for pers mfile desc supplied, pre-zero |
| */ |
| if (filehp) { |
| *filehp = DH_NULL; |
| } |
| |
| /* keep a close eye on the validity of fileh |
| */ |
| fileh = DH_NULL; |
| |
| /* if purpose has changed, invalidate first, last, and previous indices |
| */ |
| if (Mediap->M_flmfixvalpr) { |
| if (purp != Mediap->M_mfixpurp) { |
| Mediap->M_flmfixvalpr = BOOL_FALSE; |
| Mediap->M_pmfixvalpr = BOOL_FALSE; |
| } |
| } |
| |
| /* use a local variable to keep track of dump sessions seen on |
| * media. if not in search mode, each time we see a different |
| * dump session, log a message to keep the user informed. |
| * invalidated each time we change media or rewind. |
| */ |
| uuid_clear(prevmfiledumpid); |
| |
| /* if restore is complete, return indication. be sure to end read |
| * if active. |
| */ |
| if (purp == PURP_NONDIR |
| && |
| pi_alldone()) { |
| if (Mediap->M_pos == POS_ATHDR |
| || |
| Mediap->M_pos == POS_INDIR |
| || |
| Mediap->M_pos == POS_ATNONDIR) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| } |
| return RV_NOMORE; |
| } |
| |
| /* loop searching for an acceptable media file. |
| * change media as necessary. |
| */ |
| for (;;) { |
| bool_t emptypr; /* begin_read says drive empty */ |
| bool_t partofdumppr; |
| bool_t hassomepr; |
| bool_t resumepr; |
| bool_t canseeknextpr; |
| drive_mark_t chkpnt; |
| bool_t fhcs; |
| bag_t *bagp = NULL; |
| bool_t knownholespr; |
| bool_t maybeholespr; |
| xfs_ino_t begino; |
| xfs_ino_t endino; |
| int dcaps = drivep->d_capabilities; |
| dh_t objh = DH_NULL; |
| |
| emptypr = BOOL_FALSE; |
| |
| /* check if no point in going on |
| */ |
| if (cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| if (donesyncp && *donesyncp == SYNC_DONE) { |
| return RV_DONE; |
| } |
| if (purp == PURP_NONDIR |
| && |
| pi_alldone()) { |
| return RV_NOMORE; |
| } |
| |
| /* if we have a useless media object, get another one |
| */ |
| if (Mediap->M_pos == POS_USELESS |
| || |
| Mediap->M_pos == POS_BLANK) { |
| goto newmedia; |
| } |
| |
| /* if the purpose if to search, and we already have |
| * a media file, that media file has already been |
| * searched, so set pos to cause another begin read |
| */ |
| if (purp == PURP_SEARCH) { |
| if (Mediap->M_pos == POS_ATHDR |
| || |
| Mediap->M_pos == POS_INDIR |
| || |
| Mediap->M_pos == POS_ATNONDIR) { |
| Mediap->M_pos = POS_UNKN; |
| } |
| } |
| |
| /* if already have a media file, skip the begin_read |
| */ |
| if (Mediap->M_pos == POS_ATHDR |
| || |
| Mediap->M_pos == POS_INDIR |
| || |
| Mediap->M_pos == POS_ATNONDIR) { |
| goto validate; |
| } |
| |
| /* see if the indices say we've seen all there is to see |
| */ |
| if (Mediap->M_flmfixvalpr) { |
| if (Mediap->M_pos == POS_UNKN) { |
| if (Mediap->M_lmfix + 1 == Mediap->M_fmfix) { |
| goto newmedia; |
| } |
| } |
| if (Mediap->M_pos == POS_END) { |
| if (Mediap->M_fmfix == 0) { |
| goto newmedia; |
| } |
| if (Mediap->M_fsfixvalpr |
| && |
| Mediap->M_fmfix <= Mediap->M_fsfix) { |
| goto newmedia; |
| } |
| } |
| } |
| |
| /* if we are at the end, do a rewind, or get new media |
| * if rewinds not possible. this may take a while, so |
| * afterwards check for interrupt or if someone else |
| * has finished the job. |
| */ |
| if (Mediap->M_pos == POS_END) { |
| if (!(dcaps & DRIVE_CAP_REWIND)) { |
| goto newmedia; |
| } |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "rewinding\n")); |
| (*drivep->d_opsp->do_rewind)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| if (cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| if (donesyncp && *donesyncp == SYNC_DONE) { |
| return RV_DONE; |
| } |
| if (purp == PURP_NONDIR |
| && |
| pi_alldone()) { |
| return RV_NOMORE; |
| } |
| } |
| |
| /* begin a new media file, and determine new position. |
| * bail if catastrophic. also, tell pi about EOD/EOM |
| * if appropriate. |
| */ |
| rval = (*drivep->d_opsp->do_begin_read)(drivep); |
| switch (rval) { |
| case 0: |
| mlog_lock(); |
| mlog(MLOG_VERBOSE | MLOG_NOLOCK | MLOG_MEDIA, _( |
| "examining media file %u\n"), |
| mrhdrp->mh_mediafileix); |
| mlog(MLOG_TRACE | MLOG_NOLOCK | MLOG_MEDIA, |
| "file %u in " |
| "object %u " |
| "of stream %u\n", |
| mrhdrp->mh_mediafileix, |
| mrhdrp->mh_mediaix, |
| drhdrp->dh_driveix); |
| mlog(MLOG_TRACE | MLOG_NOLOCK | MLOG_MEDIA, |
| "file %u in stream, " |
| "file %u of dump %u on object\n", |
| mrhdrp->mh_dumpfileix, |
| mrhdrp->mh_dumpmediafileix, |
| mrhdrp->mh_dumpmediaix); |
| mlog_unlock(); |
| |
| Mediap->M_pos = POS_ATHDR; |
| if (Mediap->M_flmfixvalpr) { |
| Mediap->M_pmfix = Mediap->M_lmfix; |
| Mediap->M_pmfixvalpr = BOOL_TRUE; |
| } |
| |
| pi_note_indrive(drhdrp->dh_driveix, |
| mrhdrp->mh_mediaid); |
| |
| break; |
| case DRIVE_ERROR_EOD: |
| Mediap->M_pos = POS_END; |
| if (Mediap->M_fsfixvalpr) { |
| assert(purp != PURP_SEARCH); |
| pi_hiteod(Mediap->M_fssix, |
| Mediap->M_fsoix); |
| } |
| break; |
| case DRIVE_ERROR_EOM: |
| Mediap->M_pos = POS_END; |
| if (Mediap->M_fsfixvalpr) { |
| assert(purp != PURP_SEARCH); |
| pi_hiteom(Mediap->M_fssix, |
| Mediap->M_fsoix); |
| } |
| break; |
| case DRIVE_ERROR_MEDIA: |
| /* |
| * pv: 766024; tes@engr |
| * The setting of emptypr, in my opinion, should only happen |
| * in the case that the drive does not have a tape online. |
| * This corresponds to a couple of cases in prepare_drive(). |
| * Otherwise, when we go to a newmedia we won't be able to eject |
| * the tape when we want/need to. |
| * This may need to be reviewed in the future. |
| */ |
| emptypr = BOOL_TRUE; /* so don't try to eject */ |
| /* fall through */ |
| case DRIVE_ERROR_FOREIGN: |
| case DRIVE_ERROR_FORMAT: |
| case DRIVE_ERROR_VERSION: |
| case DRIVE_ERROR_EOF: |
| Mediap->M_pos = POS_USELESS; |
| break; |
| case DRIVE_ERROR_BLANK: |
| Mediap->M_pos = POS_BLANK; |
| break; |
| case DRIVE_ERROR_CORRUPTION: |
| Mediap->M_pos = POS_UNKN; |
| continue; |
| case DRIVE_ERROR_STOP: |
| return RV_INTR; |
| case DRIVE_ERROR_DEVICE: |
| return RV_DRIVE; |
| default: |
| return RV_CORE; |
| } |
| |
| validate: |
| /* update the positional indices |
| */ |
| if (Mediap->M_pos == POS_ATHDR |
| || |
| Mediap->M_pos == POS_INDIR |
| || |
| Mediap->M_pos == POS_ATNONDIR) { |
| if (!Mediap->M_flmfixvalpr) { |
| Mediap->M_fmfix = mrhdrp->mh_mediafileix; |
| Mediap->M_mfixpurp = purp; |
| Mediap->M_flmfixvalpr = BOOL_TRUE; |
| } |
| Mediap->M_lmfix = mrhdrp->mh_mediafileix; |
| } |
| |
| /* check for interrupt. be sure to end_read if necessary |
| */ |
| if (cldmgr_stop_requested()) { |
| if (Mediap->M_pos == POS_ATHDR |
| || |
| Mediap->M_pos == POS_INDIR |
| || |
| Mediap->M_pos == POS_ATNONDIR) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| } |
| return RV_INTR; |
| } |
| |
| /* check if another thread has finished job (for this purpose). |
| * don't end_read, we will be back. |
| */ |
| if (donesyncp && *donesyncp == SYNC_DONE) { |
| return RV_DONE; |
| } |
| |
| /* we may be done due to the actions of other threads. |
| * if so, return indicating so |
| */ |
| if (purp == PURP_NONDIR |
| && |
| pi_alldone()) { |
| return RV_NOMORE; |
| } |
| |
| /* if the media object is useless, go get more |
| */ |
| if (Mediap->M_pos == POS_USELESS |
| || |
| Mediap->M_pos == POS_BLANK) { |
| goto newmedia; |
| } |
| |
| /* if we hit the end, this is not a search, and we've |
| * seen at least one media file pertaining to the dump, |
| * ask the inventory if there is any point in examining |
| * the beginning of the object. |
| */ |
| if (Mediap->M_pos == POS_END |
| && |
| purp != PURP_SEARCH |
| && |
| Mediap->M_fsfixvalpr |
| && |
| pi_know_no_more_on_object(purp, |
| Mediap->M_fssix, |
| Mediap->M_fsoix)) { |
| goto newmedia; |
| } |
| |
| /* if we hit the end, go back to the top, where |
| * we will decide if we should rewind or get new media. |
| */ |
| if (Mediap->M_pos == POS_END) { |
| continue; |
| } |
| |
| /* if the purpose is to search, return this media file |
| */ |
| if (purp == PURP_SEARCH) { |
| assert(Mediap->M_pos == POS_ATHDR); |
| return RV_OK; |
| } |
| |
| /* see if this media file is part of the desired dump session |
| */ |
| partofdumppr = (bool_t)(uuid_compare(persp->s.dumpid, |
| grhdrp->gh_dumpid) == 0); |
| if (!partofdumppr) { |
| char gh_string_uuid[UUID_STR_LEN + 1]; |
| char inv_string_uuid[UUID_STR_LEN + 1]; |
| |
| uuid_unparse(grhdrp->gh_dumpid, gh_string_uuid); |
| uuid_unparse(persp->s.dumpid, inv_string_uuid); |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "inventory session uuid (%s) does not match " |
| "the media header's session uuid (%s)\n"), |
| inv_string_uuid, gh_string_uuid); |
| } |
| |
| /* if media file dump id is different from the preceeding |
| * media file, print something useful at TRACE verbosity. |
| */ |
| if (uuid_compare(prevmfiledumpid, |
| grhdrp->gh_dumpid) != 0) { |
| |
| char string_uuid[UUID_STR_LEN + 1]; |
| |
| mlog_lock(); |
| mlog(MLOG_TRACE | MLOG_NOLOCK | MLOG_MEDIA, |
| "dump session label: \"%s\"\n", |
| grhdrp->gh_dumplabel); |
| |
| uuid_unparse(grhdrp->gh_dumpid, string_uuid); |
| mlog(MLOG_TRACE | MLOG_NOLOCK | MLOG_MEDIA, |
| "dump session id: %s\n", |
| string_uuid); |
| mlog(MLOG_TRACE | MLOG_NOLOCK | MLOG_MEDIA, |
| "stream %u, object %u, file %u\n", |
| drhdrp->dh_driveix, |
| mrhdrp->mh_mediaix, |
| mrhdrp->mh_dumpmediafileix); |
| mlog_unlock(); |
| uuid_copy(prevmfiledumpid, grhdrp->gh_dumpid); |
| } |
| |
| /* if this media file is not part of the desired dump session, |
| * and a preceeding media file on this object was part of the |
| * dump, we know we have hit the end of the stream. tell the |
| * persistent inventory. |
| */ |
| if (!partofdumppr |
| && |
| Mediap->M_fsfixvalpr |
| && |
| Mediap->M_lmfix > Mediap->M_fsfix) { |
| pi_hitnextdump(Mediap->M_fssix, |
| Mediap->M_fsoix, |
| Mediap->M_lmfix); |
| } |
| |
| /* if this media file is not part of the desired dump session, |
| * we are doing non-dir, and the preceeding media file on this |
| * object was part of the dump, we know we have hit the end of |
| * the stream. check if we are done. |
| */ |
| if (!partofdumppr |
| && |
| purp == PURP_NONDIR |
| && |
| Mediap->M_fsfixvalpr |
| && |
| Mediap->M_lmfix > Mediap->M_fsfix) { |
| if (pi_alldone()) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| return RV_NOMORE; |
| } |
| } |
| |
| /* if this media file is not part of the desired dump session, |
| * and preceeding media files on this object were, decide if |
| * we need to rewind and look at the beginning of the object. |
| */ |
| if (!partofdumppr |
| && |
| Mediap->M_fsfixvalpr |
| && |
| Mediap->M_fmfix <= Mediap->M_fsfix) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| if (dcaps & DRIVE_CAP_REWIND) { |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "rewinding\n")); |
| (*drivep->d_opsp->do_rewind)(drivep); |
| continue; |
| } else { |
| goto newmedia; |
| } |
| } |
| |
| /* if this media file is not part of the desired dump session, |
| * and the above conditions were not met, then keep looking |
| */ |
| if (!partofdumppr) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| continue; |
| } |
| |
| /* record the index within this media object of the first |
| * media file in the dump stream |
| */ |
| if (!Mediap->M_fsfixvalpr) { |
| Mediap->M_fsfix = |
| mrhdrp->mh_mediafileix |
| - |
| mrhdrp->mh_dumpmediafileix; |
| Mediap->M_fsoix = mrhdrp->mh_mediaix; |
| Mediap->M_fssix = drhdrp->dh_driveix; |
| Mediap->M_fsfixvalpr = BOOL_TRUE; |
| } |
| |
| /* this media file is part of the dump. add it to the |
| * persistent inventory and get a file handle. |
| */ |
| fileh = pi_addfile(Mediap, |
| grhdrp, |
| drhdrp, |
| mrhdrp, |
| scrhdrp, |
| drivep); |
| |
| if (fileh == DH_NULL) { |
| return RV_CORE; |
| } |
| |
| pi_lock(); |
| objh = DH2F(fileh)->f_parh; |
| DH2O(objh)->o_indriveix = drivep->d_index; |
| DH2O(objh)->o_indrivepr = BOOL_TRUE; |
| pi_unlock(); |
| |
| pi_note_underhead(objh, fileh); |
| |
| /* if purp is nondir, we may be done. |
| */ |
| if (purp == PURP_NONDIR && pi_alldone()) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| return RV_NOMORE; |
| } |
| |
| /* check for a wraparound |
| */ |
| if (Mediap->M_flmfixvalpr) { |
| if (Mediap->M_fsfixvalpr |
| && |
| Mediap->M_fmfix <= Mediap->M_fsfix |
| && |
| Mediap->M_lmfix < Mediap->M_fmfix) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| goto newmedia; |
| } |
| if (Mediap->M_pmfixvalpr |
| && |
| Mediap->M_pmfix < Mediap->M_fmfix |
| && |
| Mediap->M_lmfix > Mediap->M_fmfix) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| goto newmedia; |
| } |
| } |
| |
| /* if this media file is an inventory or a terminator, |
| * we have hit the end of the stream. don't tell the persistent |
| * inventory; it already knows because of a pi_addfile. |
| * decide if any preceeding media files are useful and if so |
| * go get them. otherwise get another media object. |
| */ |
| if (MEDIA_TERMINATOR_CHK(mrhdrp) |
| || |
| scrhdrp->cih_mediafiletype |
| == |
| CIH_MEDIAFILETYPE_INVENTORY) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| if (pi_know_no_more_on_object(purp, |
| Mediap->M_fssix, |
| Mediap->M_fsoix)) { |
| goto newmedia; |
| } |
| if (Mediap->M_fmfix > Mediap->M_fsfix |
| && |
| (dcaps & DRIVE_CAP_REWIND)) { |
| pi_note_underhead(objh, DH_NULL); |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "rewinding\n")); |
| (*drivep->d_opsp->do_rewind)(drivep); |
| continue; |
| } |
| goto newmedia; |
| } |
| |
| /* if the purpose is dir, but this media file is not positioned |
| * at the hdr or has already been tried, get another one. |
| * use the persistent inventory to do this intelligently. |
| */ |
| if (purp == PURP_DIR |
| && |
| (Mediap->M_pos != POS_ATHDR |
| || |
| DH2F(fileh)->f_dirtriedpr)) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| if (pi_know_no_more_beyond_on_object(purp, |
| Mediap->M_fssix, |
| Mediap->M_fsoix, |
| mrhdrp->mh_dumpmediafileix)) { |
| if (pi_know_no_more_on_object(purp, |
| Mediap->M_fssix, |
| Mediap->M_fsoix)) { |
| goto newmedia; |
| } |
| if (Mediap->M_fmfix > Mediap->M_fsfix |
| && |
| (dcaps & DRIVE_CAP_REWIND)) { |
| pi_note_underhead(objh, DH_NULL); |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "rewinding\n")); |
| (*drivep->d_opsp->do_rewind)(drivep); |
| continue; |
| } |
| goto newmedia; |
| } else { |
| continue; |
| } |
| } |
| |
| /* if the purpose is dir, give it to the caller |
| */ |
| if (purp == PURP_DIR) { |
| assert(Mediap->M_pos == POS_ATHDR); |
| if (filehp) { |
| assert(fileh != DH_NULL); |
| *filehp = fileh; |
| } |
| return RV_OK; |
| } |
| |
| /* if we made it this far, the purpose is NONDIR and this |
| * is a valid media file from the desired dump session. |
| */ |
| |
| /* see if this media file contains any inodes not yet restored |
| */ |
| assert(fileh != DH_NULL); |
| pi_lock(); |
| assert(DH2F(fileh)->f_valpr); |
| begino = DH2F(fileh)->f_curegrp.eg_ino; |
| endino = pi_scanfileendino(fileh); |
| hassomepr = inomap_rst_needed(begino, endino); |
| |
| /* if we have already given up on this media file or |
| * it doesn't contains anything not yet restored, |
| * or it can be skipped, move on. force the done flag on, |
| * so we don't check it again. |
| */ |
| if (DH2F(fileh)->f_nondirdonepr |
| || |
| DH2F(fileh)->f_nondirskippr |
| || |
| !hassomepr) { |
| if (!DH2F(fileh)->f_nondirskippr) { |
| DH2F(fileh)->f_nondirdonepr = BOOL_TRUE; |
| } |
| pi_unlock(); |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| if (pi_know_no_more_beyond_on_object(purp, |
| Mediap->M_fssix, |
| Mediap->M_fsoix, |
| mrhdrp->mh_dumpmediafileix)) { |
| if (pi_know_no_more_on_object(purp, |
| Mediap->M_fssix, |
| Mediap->M_fsoix)) { |
| goto newmedia; |
| } |
| if (Mediap->M_fmfix > Mediap->M_fsfix |
| && |
| (dcaps & DRIVE_CAP_REWIND)) { |
| pi_note_underhead(objh, DH_NULL); |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "rewinding\n")); |
| (*drivep->d_opsp->do_rewind)(drivep); |
| continue; |
| } |
| goto newmedia; |
| } else { |
| continue; |
| } |
| } |
| |
| /* so the purpose is NONDIR and we like this media file. |
| * be sure we are positioned at the beginning of the |
| * non-dir filehdr not yet restored, and supply to caller. |
| */ |
| |
| /* need to position just after the first |
| * non-dir filehdr_t not yet restored. |
| * may be a problem if we are currently positioned |
| * in the middle of the dir dump and have no |
| * checkpoint to seek to. if at beginning |
| * and no check point, can still get there |
| * by doing dummy read of dirdump. |
| */ |
| assert(fileh != DH_NULL); |
| assert(DH2F(fileh)->f_valpr); |
| resumepr = ((DH2F(fileh)->f_firstegrp.eg_ino |
| != |
| DH2F(fileh)->f_curegrp.eg_ino) |
| || |
| (DH2F(fileh)->f_firstegrp.eg_off |
| != |
| DH2F(fileh)->f_curegrp.eg_off)); |
| chkpnt = DH2F(fileh)->f_curmark; |
| pi_unlock(); |
| fhcs = (scrhdrp->cih_dumpattr |
| & |
| CIH_DUMPATTR_FILEHDR_CHECKSUM) |
| ? |
| BOOL_TRUE |
| : |
| BOOL_FALSE; |
| canseeknextpr = dcaps & DRIVE_CAP_NEXTMARK; |
| switch(Mediap->M_pos) { |
| case POS_ATHDR: |
| case POS_INDIR: |
| if (resumepr) { |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "seeking past portion of media file " |
| "already restored\n")); |
| rval = (*dop->do_seek_mark)(drivep, |
| &chkpnt); |
| if (!rval) { |
| rv_t rv; |
| rv = read_filehdr(drivep, |
| fhdrp, |
| fhcs); |
| if (rv != RV_OK) { |
| rval = 1; |
| } else { |
| mlog(MLOG_TRACE | MLOG_MEDIA, |
| "positioned at unrestored " |
| "portion of media file\n"); |
| } |
| } |
| } else if (canseeknextpr) { |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "seeking past media file " |
| "directory dump\n")); |
| rval = (*dop->do_next_mark)(drivep); |
| if (!rval) { |
| rv_t rv; |
| rv = read_filehdr(drivep, |
| fhdrp, |
| fhcs); |
| if (rv != RV_OK) { |
| rval = 1; |
| } else { |
| mlog(MLOG_TRACE | MLOG_MEDIA, |
| "positioned at " |
| "media file " |
| "non-directory dump\n"); |
| } |
| } |
| } else if (Mediap->M_pos == POS_ATHDR) { |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "seeking past media file " |
| "directory dump\n")); |
| rv = eatdirdump(drivep, |
| fileh, |
| scrhdrp, |
| fhdrp); |
| if (rv != RV_OK) { |
| rval = 1; |
| } else { |
| mlog(MLOG_TRACE | MLOG_MEDIA, |
| "positioned at " |
| "media file " |
| "non-directory dump\n"); |
| } |
| } else { |
| rval = 1; |
| } |
| break; |
| case POS_ATNONDIR: |
| rval = 0; |
| break; |
| default: |
| assert(0); |
| rval = 1; |
| break; |
| } |
| |
| /* if error encountered during fine positioning, |
| * mark file so we won't try it again |
| */ |
| if (rval) { |
| DH2F(fileh)->f_nondirdonepr = BOOL_TRUE; |
| } else { |
| Mediap->M_pos = POS_ATNONDIR; |
| } |
| |
| /* if no error during fine positioning, return. |
| */ |
| if (!rval) { |
| if (filehp) { |
| assert(fileh != DH_NULL); |
| *filehp = fileh; |
| } |
| return RV_OK; |
| } |
| |
| /* an error occurred during fine positioning. any other useful |
| * media files on this object? if so, continue; if not, get |
| * more media. |
| */ |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| fileh = DH_NULL; |
| assert(purp == PURP_NONDIR); |
| if (pi_know_no_more_beyond_on_object(purp, |
| Mediap->M_fssix, |
| Mediap->M_fsoix, |
| mrhdrp->mh_dumpmediafileix)) { |
| if (pi_know_no_more_on_object(purp, |
| Mediap->M_fssix, |
| Mediap->M_fsoix)) { |
| goto newmedia; |
| } |
| if (Mediap->M_fmfix > Mediap->M_fsfix |
| && |
| (dcaps & DRIVE_CAP_REWIND)) { |
| pi_note_underhead(objh, DH_NULL); |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "rewinding\n")); |
| (*drivep->d_opsp->do_rewind)(drivep); |
| continue; |
| } |
| goto newmedia; |
| } else { |
| continue; |
| } |
| /* fall through */ |
| |
| newmedia: |
| /* invalidate prev id, so we log a TRACE msg for first |
| * media file seen on new media |
| */ |
| uuid_clear(prevmfiledumpid); |
| |
| /* if we are searching and some other thread completed |
| * the search, don't pop the media unless it is useless |
| */ |
| if (purp == PURP_SEARCH |
| && |
| Mediap->M_pos != POS_USELESS |
| && |
| Mediap->M_pos != POS_BLANK |
| && |
| donesyncp |
| && |
| *donesyncp == SYNC_DONE) { |
| return RV_DONE; |
| } |
| |
| /* if media not removable, just return |
| */ |
| if ((*dop->do_get_device_class)(drivep) |
| == |
| DEVICE_NONREMOVABLE) |
| { |
| /* if no error has already been detected then don't log |
| a failure */ |
| if (mlog_get_hint() == RV_NONE) |
| mlog_exit_hint(RV_OK); |
| return RV_QUIT; |
| } |
| |
| /* check for an interrupt |
| */ |
| if (cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| |
| /* check if we are done. |
| */ |
| switch(purp) { |
| case PURP_SEARCH: |
| knownholespr = BOOL_TRUE; |
| maybeholespr = BOOL_FALSE; |
| bagp = 0; |
| break; |
| case PURP_DIR: |
| knownholespr = BOOL_FALSE; |
| maybeholespr = BOOL_FALSE; |
| bagp = pi_neededobjs_dir_alloc(&knownholespr, |
| &maybeholespr); |
| break; |
| case PURP_NONDIR: |
| knownholespr = BOOL_FALSE; |
| maybeholespr = BOOL_FALSE; |
| bagp = pi_neededobjs_nondir_alloc(&knownholespr, |
| &maybeholespr, |
| BOOL_FALSE, |
| BOOL_FALSE); |
| break; |
| default: |
| assert(0); |
| } |
| |
| if (!bagp && !knownholespr && !maybeholespr) { |
| /* if PURP_DIR, this may be a problem |
| */ |
| if (purp == PURP_NONDIR) { |
| return RV_NOMORE; |
| } |
| } |
| |
| /* eject media if drive not already empty |
| */ |
| if (!emptypr) { |
| int dcaps = drivep->d_capabilities; |
| if (purp == PURP_SEARCH) { |
| if (Mediap->M_pos == POS_USELESS) { |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "media object not useful\n")); |
| } else if (Mediap->M_pos == POS_BLANK) { |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "media object empty\n")); |
| } else { |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "all media files examined, " |
| "none selected for restoral\n")); |
| } |
| } |
| if (dcaps & DRIVE_CAP_EJECT) { |
| (*dop->do_eject_media)(drivep); |
| } |
| } |
| |
| /* tell the persistent inventory this drive is now empty |
| */ |
| pi_driveempty(drivep->d_index); |
| |
| /* invalidate all positional descriptors |
| */ |
| Mediap->M_pos = POS_UNKN; |
| Mediap->M_flmfixvalpr = BOOL_FALSE; |
| Mediap->M_pmfixvalpr = BOOL_FALSE; |
| Mediap->M_fsfixvalpr = BOOL_FALSE; |
| fileh = DH_NULL; |
| |
| |
| /* ask for a media change: supply a list of media objects |
| * which may contain useful media files |
| */ |
| if (dlog_allowed()) { |
| /* If an alert program has been specified, run it. |
| */ |
| if (media_change_alert_program != NULL) |
| system(media_change_alert_program); |
| |
| if (drivecnt > 1 && !stdoutpiped) { |
| ix_t thrdix = drivep->d_index; |
| if (bagp) { |
| pi_neededobjs_free(bagp); |
| bagp = 0; |
| } |
| assert(sistr); |
| mlog(MLOG_NORMAL | MLOG_NOTE | MLOG_MEDIA, _( |
| "please change media: " |
| "type %s to confirm media change\n"), |
| sistr); |
| set_mcflag(thrdix); |
| while (mcflag[thrdix]) { |
| sleep(2); |
| if (cldmgr_stop_requested()) { |
| clr_mcflag(thrdix); |
| return RV_INTR; |
| } |
| if (purp == PURP_NONDIR |
| && |
| pi_alldone()) { |
| clr_mcflag(thrdix); |
| return RV_NOMORE; |
| } |
| } |
| ok = BOOL_TRUE; |
| } else { |
| ok = Media_prompt_change(drivep, |
| purp, |
| bagp, |
| knownholespr, |
| maybeholespr); |
| } |
| } else { |
| ok = BOOL_FALSE; |
| } |
| if (bagp) { |
| pi_neededobjs_free(bagp); |
| bagp = 0; |
| } |
| if (cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| if (!ok) { |
| return RV_QUIT; |
| } |
| } |
| /* NOTREACHED */ |
| } |
| |
| /* figures out and calls if needed do_end_read(). |
| */ |
| static void |
| Media_end(Media_t *Mediap) |
| { |
| drive_t *drivep = Mediap->M_drivep; |
| drive_ops_t *dop = drivep->d_opsp; |
| |
| mlog(MLOG_DEBUG | MLOG_MEDIA, |
| "Media_end: pos==%d\n", |
| Mediap->M_pos); |
| |
| if (Mediap->M_pos == POS_ATHDR |
| || |
| Mediap->M_pos == POS_INDIR |
| || |
| Mediap->M_pos == POS_ATNONDIR) { |
| (*dop->do_end_read)(drivep); |
| Mediap->M_pos = POS_UNKN; |
| } |
| } |
| |
| /* Persistent inventory operators *******************************************/ |
| |
| /* the persistent inventory is an mmap()ed file containing a hierarchical |
| * representation of all the media files generated by a dump session. it |
| * is useful for asking questions about how much of the dump remains to |
| * be restored. |
| * |
| * the top of the hierarchy is a linked list of streams. each of these contains |
| * a linked list of objects, which in turn each contain a linked list of files. |
| * each stream descriptor also has a flag indicating the last object in the |
| * stream is represented. each file descriptor contains a flag indicating the |
| * last file in that object is represented. the object descriptor also contains |
| * two indices; one indicating where in the dump stream the first media file |
| * in that object lies, and where in the object the first media file pertaining |
| * to the dump stream lies. each file descriptor describes the first extent |
| * group in that media file, and the next extent group in the media file to be |
| * restored. the file descriptor also contains flags indicating if an attempt |
| * (successful or unsuccessful) to use the file for a directory dump, and |
| * a similar flag for non-dir. |
| * |
| * all media files generated during the dump are represented, including |
| * terminators and inventories. |
| */ |
| static void |
| pi_lock(void) |
| { |
| qlock_lock(tranp->t_pilockh); |
| } |
| |
| static void |
| pi_unlock(void) |
| { |
| qlock_unlock(tranp->t_pilockh); |
| } |
| |
| /* sets check point in media file descriptor |
| */ |
| static void |
| pi_checkpoint(dh_t fileh, drive_mark_t *drivemarkp, xfs_ino_t ino, off64_t off) |
| { |
| pi_lock(); |
| DH2F(fileh)->f_curmark = *drivemarkp; |
| DH2F(fileh)->f_curegrp.eg_ino = ino; |
| DH2F(fileh)->f_curegrp.eg_off = off; |
| pi_unlock(); |
| } |
| |
| /* lock must be held by caller |
| */ |
| static bool_t |
| pi_allocdesc(dh_t *deschp) |
| { |
| dh_t desch; |
| |
| if (persp->s.descfreeh == DH_NULL) { |
| ix_t stpgcnt = persp->a.stpgcnt; |
| ix_t olddescpgcnt = persp->s.descpgcnt; |
| ix_t newdescpgcnt = olddescpgcnt + DAU; |
| ix_t descppg = pgsz / PERS_DESCSZ; |
| ix_t descix; |
| /* REFERENCED */ |
| int rval; |
| |
| /* first unmap if any existing descriptors |
| */ |
| if (descp) { |
| assert(olddescpgcnt > 0); |
| rval = munmap((void *)descp, |
| olddescpgcnt * pgsz); |
| assert(!rval); |
| descp = 0; |
| } else { |
| assert(olddescpgcnt == 0); |
| } |
| |
| /* remap with DAU more pages of descriptors |
| */ |
| assert(stpgcnt <= (ix_t)INTGENMAX); |
| assert(newdescpgcnt > 0); |
| descp = (pers_desc_t *) mmap_autogrow(newdescpgcnt * pgsz, |
| tranp->t_persfd, |
| (off_t)perssz |
| + |
| (off_t)(stpgcnt * pgsz)); |
| if (descp == (pers_desc_t *)(-1)) { |
| pi_unlock(); |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_MEDIA, _( |
| "could not remap persistent state file " |
| "inv %s: %d (%s)\n"), |
| perspath, |
| errno, |
| strerror(errno)); |
| pi_lock(); |
| descp = 0; |
| return BOOL_FALSE; |
| } |
| persp->s.descfreeh = (dh_t)(olddescpgcnt * pgsz + 1); |
| for (descix = 0, desch = persp->s.descfreeh |
| ; |
| descix < (DAU * descppg) - 1 |
| ; |
| descix++, desch += PERS_DESCSZ) { |
| DH2D(desch)->d_nexth = desch + PERS_DESCSZ; |
| } |
| DH2D(desch)->d_nexth = DH_NULL; |
| persp->s.descpgcnt = newdescpgcnt; |
| } |
| |
| desch = persp->s.descfreeh; |
| persp->s.descfreeh = DH2D(desch)->d_nexth; |
| memset((void *)DH2D(desch), 0, sizeof(pers_desc_t)); |
| assert(desch != DH_NULL); |
| *deschp = desch; |
| return BOOL_TRUE; |
| } |
| |
| /* inserts the indexed file into the given stream. ensures that all |
| * previous files are represented as well. if dmfix is not valid, only |
| * adds objects. |
| */ |
| static dh_t |
| pi_insertfile(ix_t drivecnt, |
| ix_t driveix, |
| ix_t mediaix, |
| bool_t idlabvalpr, |
| uuid_t *mediaidp, |
| label_t medialabel, |
| bool_t previdlabvalpr, |
| uuid_t *prevmediaidp, |
| label_t prevmedialabel, |
| bool_t mfixvalpr, |
| ix_t mediafileix, |
| bool_t dmfixvalpr, |
| ix_t dumpmediafileix, |
| bool_t dfixvalpr, |
| ix_t dumpfileix, |
| bool_t egrpvalpr, |
| xfs_ino_t startino, |
| off64_t startoffset, |
| int flags, |
| bool_t fileszvalpr, |
| off64_t filesz) |
| { |
| ix_t strmix; |
| dh_t strmh; |
| ix_t objix; |
| dh_t objh; |
| dh_t prevobjh; |
| ix_t fileix; |
| dh_t fileh; |
| dh_t prevfileh; |
| bool_t ok; |
| |
| pi_lock(); |
| |
| /* first alloc stream descriptors if needed |
| */ |
| if (persp->s.strmheadh == DH_NULL) { |
| for (strmix = 0; strmix < drivecnt; strmix++) { |
| ok = pi_allocdesc(&strmh); |
| if (!ok) { |
| pi_unlock(); |
| return DH_NULL; |
| } |
| DH2S(strmh)->s_nexth = persp->s.strmheadh; |
| persp->s.strmheadh = strmh; |
| } |
| } |
| |
| /* get handle to this stream |
| */ |
| for (strmix = 0, |
| strmh = persp->s.strmheadh |
| ; |
| strmix < driveix |
| ; |
| strmix++, |
| strmh = DH2S(strmh)->s_nexth) |
| ; |
| assert(strmh != DH_NULL); |
| |
| /* get handle to this object by walking/constructing this stream's |
| * object list, up to the desired object |
| */ |
| objh = prevobjh = DH_NULL; |
| for (objix = 0; objix <= mediaix; objix++) { |
| prevobjh = objh; |
| if (objix == 0) { |
| objh = DH2S(strmh)->s_cldh; |
| } else { |
| objh = DH2O(prevobjh)->o_nexth; |
| } |
| if (objh == DH_NULL) { |
| ok = pi_allocdesc(&objh); |
| if (!ok) { |
| pi_unlock(); |
| return DH_NULL; |
| } |
| DH2O(objh)->o_parh = strmh; |
| if (objix == 0) { |
| DH2S(strmh)->s_cldh = objh; |
| } else { |
| DH2O(prevobjh)->o_nexth = objh; |
| } |
| } |
| } |
| |
| /* update the object fields if not yet valid |
| */ |
| if (idlabvalpr |
| && |
| ! DH2O(objh)->o_idlabvalpr) { |
| uuid_copy(DH2O(objh)->o_id, *mediaidp); |
| strncpy(DH2O(objh)->o_lab, |
| medialabel, |
| sizeof(DH2O(objh)->o_lab)); |
| DH2O(objh)->o_idlabvalpr = BOOL_TRUE; |
| } |
| if (mfixvalpr |
| && |
| dmfixvalpr |
| && |
| ! DH2O(objh)->o_fmfmixvalpr) { |
| DH2O(objh)->o_fmfmix = mediafileix - dumpmediafileix; |
| DH2O(objh)->o_fmfmixvalpr = BOOL_TRUE; |
| } |
| if (dfixvalpr |
| && |
| dmfixvalpr |
| && |
| ! DH2O(objh)->o_fmfsixvalpr) { |
| DH2O(objh)->o_fmfsix = dumpfileix - dumpmediafileix; |
| DH2O(objh)->o_fmfsixvalpr = BOOL_TRUE; |
| } |
| |
| /* record the previous object's id and label if not yet valid |
| */ |
| if (prevobjh != DH_NULL |
| && |
| previdlabvalpr |
| && |
| ! DH2O(prevobjh)->o_idlabvalpr) { |
| uuid_copy(DH2O(prevobjh)->o_id, *prevmediaidp); |
| strncpy(DH2O(prevobjh)->o_lab, |
| prevmedialabel, |
| sizeof(DH2O(prevobjh)->o_lab)); |
| DH2O(prevobjh)->o_idlabvalpr = BOOL_TRUE; |
| } |
| |
| /* if the dump file and dump media file indices are valid, |
| * and the previous object has at least one media file with its |
| * dump file index valid, can infer the index of the last media |
| * file on the previous dump object. |
| */ |
| if (DH2O(objh)->o_fmfsixvalpr |
| && |
| prevobjh != DH_NULL |
| && |
| DH2O(prevobjh)->o_fmfsixvalpr |
| && |
| ! DH2O(prevobjh)->o_lmfknwnpr) { |
| size_t prevmfcnt; |
| assert(DH2O(objh)->o_fmfsix > DH2O(prevobjh)->o_fmfsix); |
| prevmfcnt = DH2O(objh)->o_fmfsix - DH2O(prevobjh)->o_fmfsix; |
| pi_unlock(); |
| assert(mediaix > 0); |
| (void)pi_insertfile(drivecnt, |
| driveix, |
| mediaix - 1, |
| BOOL_FALSE, |
| 0, |
| 0, |
| BOOL_FALSE, |
| 0, |
| 0, |
| BOOL_FALSE, |
| 0, |
| BOOL_TRUE, |
| prevmfcnt - 1, |
| BOOL_TRUE, |
| DH2O(objh)->o_fmfsix - 1, |
| 0, |
| 0, |
| 0, |
| 0, |
| BOOL_FALSE, |
| (off64_t)0); |
| pi_seeobjstrmend(strmix, mediaix - 1); |
| pi_lock(); |
| } |
| |
| /* if don't know dump stream media file index, can't add any media files |
| */ |
| if (!dmfixvalpr) { |
| pi_unlock(); |
| pi_show(" after pi_insertfile no media file ix"); |
| return DH_NULL; |
| } |
| |
| /* get handle to this file by walking/constructing this object's |
| * file list, up to the desired file |
| */ |
| fileh = DH_NULL; |
| for (fileix = 0; fileix <= dumpmediafileix; fileix++) { |
| prevfileh = fileh; |
| if (fileix == 0) { |
| fileh = DH2O(objh)->o_cldh; |
| } else { |
| fileh = DH2F(prevfileh)->f_nexth; |
| } |
| if (fileh == DH_NULL) { |
| ok = pi_allocdesc(&fileh); |
| if (!ok) { |
| pi_unlock(); |
| return DH_NULL; |
| } |
| DH2F(fileh)->f_parh = objh; |
| if (fileix == 0) { |
| DH2O(objh)->o_cldh = fileh; |
| } else { |
| DH2F(prevfileh)->f_nexth = fileh; |
| } |
| } |
| } |
| |
| /* update the media file fields not yet valid |
| */ |
| if (egrpvalpr && !DH2F(fileh)->f_valpr) { |
| assert(!(DH2F(fileh)->f_flags & PF_INV)); |
| assert(!(DH2F(fileh)->f_flags & PF_TERM)); |
| DH2F(fileh)->f_firstegrp.eg_ino = startino; |
| DH2F(fileh)->f_firstegrp.eg_off = startoffset; |
| DH2F(fileh)->f_curegrp = DH2F(fileh)->f_firstegrp; |
| DH2F(fileh)->f_valpr = BOOL_TRUE; |
| } |
| |
| /* set flags |
| */ |
| DH2F(fileh)->f_flags = flags; |
| |
| /* if we know the file size, |
| * update it |
| */ |
| if (fileszvalpr) { |
| DH2F(fileh)->f_sz = filesz; |
| DH2F(fileh)->f_szvalpr = BOOL_TRUE; |
| } |
| |
| pi_unlock(); |
| pi_show(" after pi_insertfile"); |
| return fileh; |
| } |
| |
| /* add pers file desc if not already present. will automatically |
| * update/alloc pers obj and strm descriptors. If given a session inventory, |
| * attempt to incorporate into pi. also, initializes completion stats. |
| */ |
| /* ARGSUSED */ |
| static dh_t |
| pi_addfile(Media_t *Mediap, |
| global_hdr_t *grhdrp, |
| drive_hdr_t *drhdrp, |
| media_hdr_t *mrhdrp, |
| content_inode_hdr_t *scrhdrp, |
| drive_t * drivep) |
| { |
| dh_t fileh; |
| |
| if (!persp->s.stat_valpr) { |
| persp->s.stat_inocnt = scrhdrp->cih_inomap_nondircnt; |
| persp->s.stat_inodone = 0; |
| assert(scrhdrp->cih_inomap_datasz <= OFF64MAX); |
| persp->s.stat_datacnt = (off64_t)scrhdrp->cih_inomap_datasz; |
| persp->s.stat_datadone = 0; |
| persp->s.stat_valpr = BOOL_TRUE; |
| } |
| |
| /* if we see a terminator, we know we have seen the end of |
| * a stream. |
| */ |
| if (MEDIA_TERMINATOR_CHK(mrhdrp)) { |
| fileh = pi_insertfile(drhdrp->dh_drivecnt, |
| drhdrp->dh_driveix, |
| mrhdrp->mh_mediaix, |
| BOOL_TRUE, |
| &mrhdrp->mh_mediaid, |
| mrhdrp->mh_medialabel, |
| BOOL_TRUE, |
| &mrhdrp->mh_prevmediaid, |
| mrhdrp->mh_prevmedialabel, |
| BOOL_TRUE, |
| mrhdrp->mh_mediafileix, |
| BOOL_TRUE, |
| mrhdrp->mh_dumpmediafileix, |
| BOOL_TRUE, |
| mrhdrp->mh_dumpfileix, |
| BOOL_FALSE, |
| (xfs_ino_t)0, |
| (off64_t)0, |
| PF_TERM, |
| BOOL_FALSE, |
| (off64_t)0); |
| if (fileh == DH_NULL) { |
| return DH_NULL; |
| } |
| pi_seestrmend(drhdrp->dh_driveix); |
| pi_seeobjstrmend(drhdrp->dh_driveix, mrhdrp->mh_mediaix); |
| return fileh; |
| } |
| |
| /* data file |
| */ |
| if (scrhdrp->cih_mediafiletype == CIH_MEDIAFILETYPE_DATA) { |
| /* tell the inventory about this media file |
| */ |
| fileh = pi_insertfile(drhdrp->dh_drivecnt, |
| drhdrp->dh_driveix, |
| mrhdrp->mh_mediaix, |
| BOOL_TRUE, |
| &mrhdrp->mh_mediaid, |
| mrhdrp->mh_medialabel, |
| BOOL_TRUE, |
| &mrhdrp->mh_prevmediaid, |
| mrhdrp->mh_prevmedialabel, |
| BOOL_TRUE, |
| mrhdrp->mh_mediafileix, |
| BOOL_TRUE, |
| mrhdrp->mh_dumpmediafileix, |
| BOOL_TRUE, |
| mrhdrp->mh_dumpfileix, |
| BOOL_TRUE, |
| scrhdrp->cih_startpt.sp_ino, |
| scrhdrp->cih_startpt.sp_offset, |
| 0, |
| BOOL_FALSE, |
| (off64_t)0); |
| if (fileh == DH_NULL) { |
| return DH_NULL; |
| } |
| assert(drhdrp->dh_drivecnt > 0); |
| if (drhdrp->dh_driveix < drhdrp->dh_drivecnt - 1) { |
| /* if this is not in the last stream, we know |
| * there is at least one other media file in |
| * the following stream, and we know its start pt |
| */ |
| (void)pi_insertfile(drhdrp->dh_drivecnt, |
| drhdrp->dh_driveix + 1, |
| 0, |
| BOOL_FALSE, |
| 0, |
| 0, |
| BOOL_FALSE, |
| 0, |
| 0, |
| BOOL_FALSE, |
| 0, |
| BOOL_TRUE, |
| 0, |
| BOOL_FALSE, |
| 0, |
| BOOL_TRUE, |
| scrhdrp->cih_endpt.sp_ino, |
| scrhdrp->cih_endpt.sp_offset, |
| 0, |
| BOOL_FALSE, |
| (off64_t)0); |
| } |
| if (!(drivep->d_capabilities & DRIVE_CAP_FILES)) { |
| /* if drive does not support multiple files, |
| * we know this is end of object and stream |
| */ |
| pi_seestrmend(drhdrp->dh_driveix); |
| pi_seeobjstrmend(drhdrp->dh_driveix, mrhdrp->mh_mediaix); |
| } |
| |
| return fileh; |
| } |
| |
| /* inventory file |
| */ |
| if (scrhdrp->cih_mediafiletype == CIH_MEDIAFILETYPE_INVENTORY) { |
| fileh = pi_insertfile(drhdrp->dh_drivecnt, |
| drhdrp->dh_driveix, |
| mrhdrp->mh_mediaix, |
| BOOL_TRUE, |
| &mrhdrp->mh_mediaid, |
| mrhdrp->mh_medialabel, |
| BOOL_TRUE, |
| &mrhdrp->mh_prevmediaid, |
| mrhdrp->mh_prevmedialabel, |
| BOOL_TRUE, |
| mrhdrp->mh_mediafileix, |
| BOOL_TRUE, |
| mrhdrp->mh_dumpmediafileix, |
| BOOL_TRUE, |
| mrhdrp->mh_dumpfileix, |
| BOOL_FALSE, |
| (xfs_ino_t)0, |
| (off64_t)0, |
| PF_INV, |
| BOOL_FALSE, |
| (off64_t)0); |
| if (fileh == DH_NULL) { |
| return DH_NULL; |
| } |
| pi_seestrmend(drhdrp->dh_driveix); |
| pi_seeobjstrmend(drhdrp->dh_driveix, |
| mrhdrp->mh_mediaix); |
| if (drhdrp->dh_driveix < drhdrp->dh_drivecnt - 1) { |
| (void)pi_insertfile(drhdrp->dh_drivecnt, |
| drhdrp->dh_driveix + 1, |
| 0, |
| BOOL_FALSE, |
| 0, |
| 0, |
| BOOL_FALSE, |
| 0, |
| 0, |
| BOOL_FALSE, |
| 0, |
| BOOL_TRUE, |
| 0, |
| BOOL_FALSE, |
| 0, |
| BOOL_TRUE, |
| scrhdrp->cih_endpt.sp_ino, |
| scrhdrp->cih_endpt.sp_offset, |
| 0, |
| BOOL_FALSE, |
| (off64_t)0); |
| } |
| if (!persp->s.fullinvpr |
| && |
| Mediap->M_pos == POS_ATHDR) { |
| size_t bufszincr; |
| size_t bufsz; |
| size_t buflen; |
| char *bufp; |
| inv_session_t *sessp; |
| invt_sessinfo_t sessinfo; |
| bool_t ok; |
| bool_t donepr; |
| |
| /* read inventory into buffer |
| */ |
| bufszincr = IBPGINCR * PGSZ; |
| /* use 4096, no need to be correlated |
| * with system page size |
| */ |
| bufsz = bufszincr; |
| buflen = 0; |
| bufp = (char *)malloc(bufsz); |
| |
| /* need to read until we hit EOF/EOD. that's the only |
| * way to know how big the inventory is. mark the Media |
| * current media file as no longer at hdr. |
| */ |
| Mediap->M_pos = POS_ATNONDIR; |
| donepr = BOOL_FALSE; |
| while (!donepr) { |
| int nread; |
| drive_ops_t *dop = drivep->d_opsp; |
| int rval = 0; |
| nread = read_buf(bufp + buflen, |
| bufszincr, |
| (void *)drivep, |
| (rfp_t)dop->do_read, |
| (rrbfp_t)dop->do_return_read_buf, |
| &rval); |
| switch(rval) { |
| case 0: |
| assert(nread == (int)bufszincr); |
| buflen += (size_t)nread; |
| bufsz += bufszincr; |
| bufp = (char *)realloc((void *)bufp, |
| bufsz); |
| assert(bufp); |
| continue; |
| case DRIVE_ERROR_EOD: |
| case DRIVE_ERROR_EOF: |
| buflen += (size_t)nread; |
| donepr = BOOL_TRUE; |
| break; |
| default: |
| free((void *)bufp); |
| return fileh; |
| } |
| } |
| |
| /* ask inventory to convert buffer into session |
| * desc. |
| */ |
| sessp = 0; |
| ok = BOOL_FALSE; |
| /* extract the session information from the buffer */ |
| if (buflen && |
| stobj_unpack_sessinfo(bufp, buflen, &sessinfo)) { |
| stobj_convert_sessinfo(&sessp, &sessinfo); |
| ok = BOOL_TRUE; |
| } |
| |
| if (!ok || !sessp) { |
| mlog(MLOG_DEBUG | MLOG_WARNING | MLOG_MEDIA, _( |
| "on-media session " |
| "inventory corrupt\n")); |
| } else { |
| /* if root, update online inventory. |
| */ |
| if (!geteuid() |
| && |
| !tranp->t_noinvupdatepr) { |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "incorporating on-media session " |
| "inventory into online " |
| "inventory\n")); |
| inv_put_sessioninfo(&sessinfo); |
| } |
| |
| /* convert into pi format |
| */ |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, |
| "using on-media session inventory\n"); |
| persp->s.fullinvpr = pi_transcribe(sessp); |
| inv_free_session(&sessp); |
| } |
| free((void *)bufp); |
| } |
| return fileh; |
| } |
| |
| assert(0); |
| return DH_NULL; |
| } |
| |
| /* translate a session inventory into a pi |
| */ |
| static bool_t |
| pi_transcribe(inv_session_t *sessp) |
| { |
| ix_t strmcnt; |
| ix_t strmix; |
| |
| /* traverse inventory, transcribing into pers inv. |
| */ |
| strmcnt = (size_t)sessp->s_nstreams; |
| for (strmix = 0; strmix < strmcnt; strmix++) { |
| inv_stream_t *strmp; |
| size_t fileix; |
| size_t filecnt; |
| uuid_t lastobjid; |
| label_t lastobjlabel; |
| ix_t mediaix; |
| size_t dumpmediafileix; |
| |
| strmp = &sessp->s_streams[strmix]; |
| filecnt = strmp->st_nmediafiles; |
| uuid_clear(lastobjid); |
| lastobjlabel[0] = 0; |
| mediaix = 0; |
| dumpmediafileix = 0; |
| |
| /* insert all media files from this stream. note that |
| * the media object representation is inverted |
| */ |
| for (fileix = 0; fileix < filecnt; fileix++) { |
| inv_mediafile_t *filep; |
| bool_t fileszvalpr; |
| |
| filep = &strmp->st_mediafiles[fileix]; |
| if (uuid_compare(filep->m_moid, |
| lastobjid) != 0) { |
| dumpmediafileix = 0; |
| if (fileix != 0) { |
| pi_seeobjstrmend(strmix, mediaix); |
| mediaix++; |
| } |
| } |
| |
| fileszvalpr = BOOL_TRUE; |
| |
| (void)pi_insertfile(strmcnt, |
| strmix, |
| mediaix, |
| BOOL_TRUE, |
| &filep->m_moid, |
| filep->m_label, |
| BOOL_TRUE, |
| &lastobjid, |
| lastobjlabel, |
| BOOL_TRUE, |
| filep->m_mfile_index, |
| BOOL_TRUE, |
| dumpmediafileix, |
| BOOL_TRUE, |
| fileix, |
| filep->m_isinvdump |
| ? |
| BOOL_FALSE |
| : |
| BOOL_TRUE, |
| filep->m_startino, |
| filep->m_startino_off, |
| filep->m_isinvdump |
| ? |
| PF_INV |
| : |
| 0, |
| fileszvalpr, |
| filep->m_size); |
| uuid_copy(lastobjid, filep->m_moid); |
| strncpy(lastobjlabel, |
| filep->m_label, |
| sizeof(lastobjlabel)); |
| dumpmediafileix++; |
| } |
| pi_seestrmend(strmix); |
| pi_seeobjstrmend(strmix, mediaix); |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| /* clean up pers. inv: initially no media objects in drives. flags may |
| * be set from previously interrupted invocation. |
| */ |
| static void |
| pi_preclean(void) |
| { |
| dh_t strmh; |
| dh_t objh; |
| dh_t fileh; |
| |
| for (strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL |
| ; |
| strmh = DH2S(strmh)->s_nexth) { |
| for (objh = DH2S(strmh)->s_cldh |
| ; |
| objh != DH_NULL |
| ; |
| objh = DH2O(objh)->o_nexth) { |
| DH2O(objh)->o_indrivepr = BOOL_FALSE; |
| for (fileh = DH2O(objh)->o_cldh |
| ; |
| fileh != DH_NULL |
| ; |
| fileh = DH2F(fileh)->f_nexth) { |
| DH2F(fileh)->f_underheadpr = BOOL_FALSE; |
| } |
| } |
| } |
| } |
| |
| /* tell pi no media objects are in this drive |
| */ |
| static void |
| pi_driveempty(ix_t driveix) |
| { |
| dh_t strmh; |
| dh_t objh; |
| dh_t fileh; |
| |
| pi_lock(); |
| |
| for (strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL |
| ; |
| strmh = DH2S(strmh)->s_nexth) { |
| for (objh = DH2S(strmh)->s_cldh |
| ; |
| objh != DH_NULL |
| ; |
| objh = DH2O(objh)->o_nexth) { |
| if (DH2O(objh)->o_indrivepr |
| && |
| DH2O(objh)->o_indriveix == driveix) { |
| DH2O(objh)->o_indrivepr = BOOL_FALSE; |
| for (fileh = DH2O(objh)->o_cldh |
| ; |
| fileh != DH_NULL |
| ; |
| fileh = DH2F(fileh)->f_nexth) { |
| DH2F(fileh)->f_underheadpr = |
| BOOL_FALSE; |
| } |
| } |
| } |
| } |
| |
| pi_unlock(); |
| } |
| |
| /* tell pi this media object is in the drive |
| */ |
| static void |
| pi_note_indrive(ix_t driveix, uuid_t media_id) |
| { |
| dh_t strmh; |
| dh_t objh; |
| |
| pi_lock(); |
| |
| for (strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL |
| ; |
| strmh = DH2S(strmh)->s_nexth) { |
| for (objh = DH2S(strmh)->s_cldh |
| ; |
| objh != DH_NULL |
| ; |
| objh = DH2O(objh)->o_nexth) { |
| if (DH2O(objh)->o_idlabvalpr |
| && |
| uuid_compare(DH2O(objh)->o_id, media_id) == 0) { |
| DH2O(objh)->o_indrivepr = BOOL_TRUE; |
| DH2O(objh)->o_indriveix = driveix; |
| goto done; |
| } |
| } |
| } |
| |
| done: |
| pi_unlock(); |
| } |
| |
| /* tell pi this media file is under the head of the drive containing the object |
| */ |
| static void |
| pi_note_underhead(dh_t thisobjh, dh_t thisfileh) |
| { |
| dh_t fileh; |
| |
| if (thisobjh == DH_NULL) { |
| return; |
| } |
| |
| pi_lock(); |
| |
| if (thisfileh != DH_NULL) { |
| DH2F(thisfileh)->f_underheadpr = BOOL_TRUE; |
| } |
| |
| for (fileh = DH2O(thisobjh)->o_cldh |
| ; |
| fileh != DH_NULL |
| ; |
| fileh = DH2F(fileh)->f_nexth) { |
| if (fileh != thisfileh) { |
| DH2F(fileh)->f_underheadpr = BOOL_FALSE; |
| } |
| } |
| |
| pi_unlock(); |
| } |
| |
| /* mark the pi stream indicating all objects in that stream are known. |
| */ |
| static void |
| pi_seestrmend(ix_t strmix) |
| { |
| ix_t ix; |
| dh_t strmh; |
| |
| pi_lock(); |
| |
| /* get handle to the indexed stream |
| */ |
| for (ix = 0, |
| strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL && ix < strmix |
| ; |
| ix++, |
| strmh = DH2S(strmh)->s_nexth) |
| ; |
| |
| /* if an empty stream (can happen when dump interrupted), |
| * nothing need be done, so return |
| */ |
| if (strmh == DH_NULL) { |
| pi_unlock(); |
| return; |
| } |
| |
| /* set stream flag and object and file counts |
| */ |
| DH2S(strmh)->s_lastobjknwnpr = BOOL_TRUE; |
| |
| pi_unlock(); |
| pi_show(" after pi_seestrmend"); |
| } |
| |
| /* mark pi indicating all media files in object are known |
| */ |
| static void |
| pi_seeobjstrmend(ix_t strmix, ix_t mediaix) |
| { |
| ix_t ix; |
| dh_t strmh; |
| dh_t objh; |
| |
| pi_lock(); |
| |
| /* get handle to the indexed stream |
| */ |
| for (ix = 0, |
| strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL && ix < strmix |
| ; |
| ix++, |
| strmh = DH2S(strmh)->s_nexth) |
| ; |
| |
| /* if an empty stream (can happen when dump interrupted), |
| * nothing need be done, so return |
| */ |
| if (strmh == DH_NULL) { |
| pi_unlock(); |
| return; |
| } |
| |
| |
| /* get handle to indexed object in stream |
| */ |
| for (ix = 0, |
| objh = DH2S(strmh)->s_cldh |
| ; |
| objh != DH_NULL && ix < mediaix |
| ; |
| ix++, |
| objh = DH2O(objh)->o_nexth) |
| ; |
| |
| /* if an empty object (can happen when dump interrupted), |
| * nothing need be done, so return |
| */ |
| if (objh == DH_NULL) { |
| pi_unlock(); |
| return; |
| } |
| |
| |
| /* set object flag |
| */ |
| DH2O(objh)->o_lmfknwnpr = BOOL_TRUE; |
| |
| pi_unlock(); |
| pi_show(" after pi_seeobjstrmend"); |
| } |
| |
| /* scans pi to determine ino of last file wholly or partially contained on |
| * this mfile. must err on the high side if partial info. |
| * NOTE: assumes caller locks pi! |
| */ |
| static xfs_ino_t |
| pi_scanfileendino(dh_t fileh) |
| { |
| dh_t strmh; |
| ix_t mode = 0; |
| |
| assert(fileh != DH_NULL); |
| |
| /* traverse the pi tree, looking for the next media file after |
| */ |
| for (strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL |
| ; |
| strmh = DH2S(strmh)->s_nexth) { |
| dh_t objh; |
| |
| for (objh = DH2S(strmh)->s_cldh |
| ; |
| objh != DH_NULL |
| ; |
| objh = DH2O(objh)->o_nexth) { |
| dh_t nexth; |
| |
| for (nexth = DH2O(objh)->o_cldh |
| ; |
| nexth != DH_NULL |
| ; |
| nexth = DH2F(nexth)->f_nexth) { |
| |
| switch(mode) { |
| case 0: |
| if (nexth == fileh) { |
| mode = 1; |
| } |
| break; |
| default: |
| if (DH2F(nexth)->f_valpr) { |
| xfs_ino_t ino; |
| |
| assert(!(DH2F(nexth)->f_flags & PF_INV)); |
| assert(!(DH2F(nexth)->f_flags & PF_TERM)); |
| if (DH2F(nexth)->f_firstegrp.eg_off) { |
| ino = DH2F(nexth)->f_firstegrp.eg_ino; |
| return ino; |
| } else { |
| assert(DH2F(nexth)->f_firstegrp.eg_ino > 0); |
| ino = DH2F(nexth)->f_firstegrp.eg_ino - 1; |
| return ino; |
| } |
| } |
| break; |
| } |
| } |
| } |
| } |
| return INO64MAX; |
| } |
| |
| /* used to detemine range of extent groups still to be restored |
| * from media file. *--o |
| */ |
| static void |
| pi_bracketneededegrps(dh_t thisfileh, egrp_t *first_egrp, egrp_t *next_egrp) |
| { |
| dh_t strmh; |
| bool_t thisfoundpr = BOOL_FALSE; |
| dh_t prech = DH_NULL; |
| dh_t follh = DH_NULL; |
| |
| |
| assert(thisfileh != DH_NULL); |
| |
| /* traverse the pi tree, looking for fileh |
| */ |
| pi_lock(); |
| assert(DH2F(thisfileh)->f_valpr); |
| |
| for (strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL |
| ; |
| strmh = DH2S(strmh)->s_nexth) { |
| dh_t objh; |
| |
| for (objh = DH2S(strmh)->s_cldh |
| ; |
| objh != DH_NULL |
| ; |
| objh = DH2O(objh)->o_nexth) { |
| dh_t fileh; |
| |
| for (fileh = DH2O(objh)->o_cldh |
| ; |
| fileh != DH_NULL |
| ; |
| fileh = DH2F(fileh)->f_nexth) { |
| if (!thisfoundpr) { |
| if (fileh == thisfileh) { |
| thisfoundpr = BOOL_TRUE; |
| } else if (DH2F(fileh)->f_valpr) { |
| assert(!(DH2F(fileh)->f_flags & PF_INV)); |
| assert(!(DH2F(fileh)->f_flags & PF_TERM)); |
| prech = fileh; |
| } |
| } else if (DH2F(fileh)->f_valpr) { |
| assert(!(DH2F(fileh)->f_flags & PF_INV)); |
| assert(!(DH2F(fileh)->f_flags & PF_TERM)); |
| assert(follh == DH_NULL); |
| follh = fileh; |
| goto done; |
| } |
| } |
| } |
| } |
| done: |
| |
| assert(thisfoundpr); |
| |
| /* initially the lower bracket is this file descriptor's |
| * current egrp. this catches the case where a previous restore |
| * session was interrupted while restoring this media file. |
| */ |
| *first_egrp = DH2F(thisfileh)->f_curegrp; |
| |
| /* if the closest valid preceeding media file's current egrp is |
| * greater, use it as the lower bracket |
| */ |
| if (prech != DH_NULL |
| && |
| egrpcmp(&DH2F(prech)->f_curegrp, first_egrp) > 0) { |
| *first_egrp = DH2F(prech)->f_curegrp; |
| } |
| |
| /* the upper bracket is initially the end of the world. |
| * if we found a valid following file descriptor describing a |
| * media file which has already been at least restored, use |
| * its first egrp as an upper bracket. |
| */ |
| next_egrp->eg_ino = INO64MAX; |
| next_egrp->eg_off = OFF64MAX; |
| if (follh != DH_NULL |
| && |
| egrpcmp(&DH2F(follh)->f_curegrp, &DH2F(follh)->f_firstegrp) |
| > |
| 0) { |
| *next_egrp = DH2F(follh)->f_firstegrp; |
| } |
| |
| pi_unlock(); |
| } |
| |
| static void |
| pi_update_stats(off64_t sz) |
| { |
| pi_lock(); |
| assert(persp->s.stat_valpr); |
| persp->s.stat_inodone++; |
| persp->s.stat_datadone += sz; |
| pi_unlock(); |
| } |
| |
| /* pi_iterator - each invocation of the iterator advances to the next media file |
| * in the dump session, walking the media file hierarchy depth-wise. if |
| * an object's file list is exhausted and the first media file in the next |
| * object is returned and the exhausted object's last media file has not yet |
| * been identified, the return-by-ref flag filemissingpr is set. similarly for |
| * streams, objmissingpr. |
| * NOTE: does not preset missingpr's to FALSE. |
| * NOTE: caller must lock pi. |
| */ |
| |
| struct pi_iter { |
| bool_t initializedpr; |
| dh_t strmh; |
| dh_t objh; |
| dh_t fileh; |
| bool_t donepr; |
| }; |
| |
| typedef struct pi_iter pi_iter_t; |
| |
| static pi_iter_t * |
| pi_iter_alloc(void) |
| { |
| pi_iter_t *iterp; |
| |
| iterp = (pi_iter_t *)calloc(1, sizeof(pi_iter_t)); |
| assert(iterp); |
| return iterp; |
| } |
| |
| static void |
| pi_iter_free(pi_iter_t *iterp) |
| { |
| free((void *)iterp); |
| } |
| |
| static dh_t |
| pi_iter_nextfileh(pi_iter_t *iterp, |
| bool_t *objmissingprp, |
| bool_t *filemissingprp) |
| { |
| assert(!iterp->donepr); |
| |
| if (persp->s.strmheadh == DH_NULL) { |
| iterp->donepr = BOOL_TRUE; |
| return DH_NULL; |
| } |
| |
| if (!iterp->initializedpr) { |
| assert(persp->s.strmheadh != DH_NULL); |
| iterp->strmh = persp->s.strmheadh; |
| iterp->objh = DH2S(iterp->strmh)->s_cldh; |
| if (iterp->objh == DH_NULL) { |
| if (!DH2S(iterp->strmh)->s_lastobjknwnpr) { |
| *objmissingprp = BOOL_TRUE; |
| } |
| } else { |
| iterp->fileh = DH2O(iterp->objh)->o_cldh; |
| if (iterp->fileh == DH_NULL) { |
| if (!DH2O(iterp->objh)->o_lmfknwnpr) { |
| *filemissingprp = BOOL_TRUE; |
| } |
| } |
| } |
| |
| while (iterp->fileh == DH_NULL) { |
| while (iterp->objh == DH_NULL) { |
| if (!DH2S(iterp->strmh)->s_lastobjknwnpr) { |
| *objmissingprp = BOOL_TRUE; |
| } |
| iterp->strmh = DH2S(iterp->strmh)->s_nexth; |
| if (iterp->strmh == DH_NULL) { |
| iterp->donepr = BOOL_TRUE; |
| return DH_NULL; |
| } |
| iterp->objh = DH2S(iterp->strmh)->s_cldh; |
| } |
| iterp->fileh = DH2O(iterp->objh)->o_cldh; |
| if (iterp->fileh == DH_NULL) { |
| if (!DH2O(iterp->objh)->o_lmfknwnpr) { |
| *filemissingprp = BOOL_TRUE; |
| } |
| iterp->objh = DH2O(iterp->objh)->o_nexth; |
| } |
| } |
| iterp->initializedpr = BOOL_TRUE; |
| return iterp->fileh; |
| } |
| |
| iterp->fileh = DH2F(iterp->fileh)->f_nexth; |
| while (iterp->fileh == DH_NULL) { |
| if (!DH2O(iterp->objh)->o_lmfknwnpr) { |
| *filemissingprp = BOOL_TRUE; |
| } |
| iterp->objh = DH2O(iterp->objh)->o_nexth; |
| while (iterp->objh == DH_NULL) { |
| if (!DH2S(iterp->strmh)->s_lastobjknwnpr) { |
| *objmissingprp = BOOL_TRUE; |
| } |
| iterp->strmh = DH2S(iterp->strmh)->s_nexth; |
| if (iterp->strmh == DH_NULL) { |
| iterp->donepr = BOOL_TRUE; |
| return DH_NULL; |
| } |
| iterp->objh = DH2S(iterp->strmh)->s_cldh; |
| } |
| iterp->fileh = DH2O(iterp->objh)->o_cldh; |
| } |
| |
| return iterp->fileh; |
| } |
| |
| /* produces a list of media objects needed. also indicates if we know |
| * some unidentified media objects are needed, and if it is possible |
| * that we need some unidentifed objects, but don't know for sure. |
| * if markskippr is set, set the f_nondirskipr flag if the media file |
| * does not contain any nondirs of interest. |
| */ |
| |
| struct bagobj { |
| bagelem_t bagelem; |
| uuid_t id; |
| label_t label; |
| bool_t indrivepr; |
| ix_t indriveix; |
| }; |
| |
| typedef struct bagobj bagobj_t; |
| |
| static bag_t * |
| pi_neededobjs_nondir_alloc(bool_t *knownholesprp, |
| bool_t *maybeholesprp, |
| bool_t showobjindrivepr, |
| bool_t markskippr) |
| { |
| bag_t *bagp; |
| pi_iter_t *headiterp; |
| pi_iter_t *tailiterp; |
| dh_t headh; |
| dh_t tailh; |
| egrp_t tailegrp; |
| bool_t knownobjmissingpr; |
| bool_t maybeobjmissingpr; |
| bool_t maybefilemissingpr; |
| dh_t lastobjaddedh; |
| int objlistlen; |
| |
| /* no point in proceeding if pi not begun |
| */ |
| if (persp->s.strmheadh == DH_NULL) { |
| *knownholesprp = BOOL_TRUE; |
| *maybeholesprp = BOOL_FALSE; |
| return 0; |
| } |
| |
| /* to hold a list of media object handles: caller must free |
| * using pi_neededobjs_free(). |
| */ |
| bagp = bag_alloc(); |
| |
| /* allocate two iterators to scan pi |
| */ |
| tailiterp = pi_iter_alloc(); |
| headiterp = pi_iter_alloc(); |
| |
| /* set the handle to the last file added to the list to NULL. |
| * this will be updated each time we add an object to the list, |
| * preventing the same object from being added more than once. |
| * this works because the media files for a given object will |
| * always appear contiguous and just once in a pi iteration. |
| */ |
| lastobjaddedh = DH_NULL; |
| objlistlen = 0; |
| |
| /* these will be set TRUE if the tail iterator ever indicates |
| * we crossed an object or stream boundary and did not see a |
| * valid last file or last object respectively. can accumulate |
| * the booleans, since iterator never sets FALSE, just TRUE. |
| */ |
| maybeobjmissingpr = BOOL_FALSE; |
| maybefilemissingpr = BOOL_FALSE; |
| |
| /* this will be set TRUE if we see a needed media file but the |
| * object containing the media file has not been IDed. |
| */ |
| knownobjmissingpr = BOOL_FALSE; |
| |
| tailegrp.eg_ino = 0; |
| tailegrp.eg_off = 0; |
| |
| tailh = DH_NULL; |
| |
| /* lock up the inventory during the scan |
| */ |
| pi_lock(); |
| |
| do { |
| egrp_t headegrp; |
| bool_t foundgappr; |
| |
| /* advance the head until we see the next media file which has |
| * a valid egrp, or until we run out of media files. |
| */ |
| do { |
| bool_t dummyobjmissingpr; |
| bool_t dummyfilemissingpr; |
| headh = pi_iter_nextfileh(headiterp, |
| &dummyobjmissingpr, |
| &dummyfilemissingpr); |
| } while (headh != DH_NULL && !DH2F(headh)->f_valpr); |
| if (headh == DH_NULL) { |
| headegrp.eg_ino = INO64MAX; |
| headegrp.eg_off = OFF64MAX; |
| } else { |
| assert(!(DH2F(headh)->f_flags & PF_INV)); |
| assert(!(DH2F(headh)->f_flags & PF_TERM)); |
| headegrp = DH2F(headh)->f_firstegrp; |
| } |
| |
| /* see if the range of egrps from head up to but not including |
| * tail needed according to ino map |
| */ |
| if (gapneeded(&tailegrp, &headegrp)) { |
| foundgappr = BOOL_TRUE; |
| } else { |
| foundgappr = BOOL_FALSE; |
| } |
| |
| /* now bring tail up to head, adding objects and setting flags |
| * along the way. note special handling of NULL tailh. possible |
| * only first time through: ignore. also, ignore inv and term. |
| */ |
| do { |
| /* if requested, mark media files not needed |
| */ |
| if (markskippr |
| && |
| !foundgappr |
| && |
| tailh != DH_NULL |
| && |
| !(DH2F(tailh)->f_flags & PF_INV) |
| && |
| !(DH2F(tailh)->f_flags & PF_TERM) |
| && |
| !DH2F(tailh)->f_nondirskippr) { |
| DH2F(tailh)->f_nondirskippr = BOOL_TRUE; |
| } |
| |
| /* build up list of needed objects |
| */ |
| if (foundgappr |
| && |
| tailh != DH_NULL |
| && |
| !(DH2F(tailh)->f_flags & PF_INV) |
| && |
| !(DH2F(tailh)->f_flags & PF_TERM) |
| && |
| !DH2F(tailh)->f_nondirdonepr |
| && |
| !DH2F(tailh)->f_nondirskippr) { |
| |
| dh_t objh = DH2F(tailh)->f_parh; |
| |
| if (!DH2O(objh)->o_indrivepr |
| || |
| showobjindrivepr) { |
| if (DH2O(objh)->o_idlabvalpr) { |
| if (objh != lastobjaddedh) { |
| addobj(bagp, |
| &DH2O(objh)->o_id, |
| DH2O(objh)->o_lab, |
| DH2O(objh)->o_indrivepr, |
| DH2O(objh)->o_indriveix); |
| lastobjaddedh = objh; |
| objlistlen++; |
| } |
| } else { |
| knownobjmissingpr = BOOL_TRUE; |
| } |
| } |
| } |
| |
| /* pull the tail up to the next media file |
| */ |
| tailh = pi_iter_nextfileh(tailiterp, |
| &maybeobjmissingpr, |
| &maybefilemissingpr); |
| } while (tailh != headh); |
| |
| tailegrp = headegrp; |
| |
| } while (headh != DH_NULL); |
| |
| pi_unlock(); |
| |
| /* free the iterators |
| */ |
| pi_iter_free(tailiterp); |
| pi_iter_free(headiterp); |
| |
| /* free the bag and return NULL if object list empty |
| */ |
| if (objlistlen == 0) { |
| bag_free(bagp); |
| bagp = 0; |
| } |
| |
| *maybeholesprp = (maybeobjmissingpr || maybefilemissingpr); |
| *knownholesprp = knownobjmissingpr; |
| return bagp; |
| } |
| |
| static bag_t * |
| pi_neededobjs_dir_alloc(bool_t *knownholesprp, bool_t *maybeholesprp) |
| { |
| bag_t *bagp; |
| dh_t fileh; |
| pi_iter_t *iterp; |
| bool_t knownobjmissingpr; |
| bool_t maybeobjmissingpr; |
| bool_t maybefilemissingpr; |
| dh_t lastobjaddedh; |
| int objlistlen; |
| |
| bagp = bag_alloc(); |
| iterp = pi_iter_alloc(); |
| |
| knownobjmissingpr = BOOL_FALSE; |
| maybeobjmissingpr = BOOL_FALSE; |
| maybefilemissingpr = BOOL_FALSE; |
| lastobjaddedh = DH_NULL; |
| objlistlen = 0; |
| |
| pi_lock(); |
| |
| while ((fileh = pi_iter_nextfileh(iterp, |
| &maybeobjmissingpr, |
| &maybefilemissingpr)) |
| != DH_NULL) { |
| if (!DH2F(fileh)->f_dirtriedpr) { |
| dh_t objh = DH2F(fileh)->f_parh; |
| if (!DH2O(objh)->o_indrivepr) { |
| if (DH2O(objh)->o_idlabvalpr) { |
| if (objh != lastobjaddedh) { |
| addobj(bagp, |
| &DH2O(objh)->o_id, |
| DH2O(objh)->o_lab, |
| DH2O(objh)->o_indrivepr, |
| DH2O(objh)->o_indriveix); |
| lastobjaddedh = objh; |
| objlistlen++; |
| } |
| } else { |
| knownobjmissingpr = BOOL_TRUE; |
| } |
| } |
| } |
| } |
| |
| pi_unlock(); |
| |
| pi_iter_free(iterp); |
| |
| if (objlistlen == 0) { |
| bag_free(bagp); |
| bagp = 0; |
| } |
| |
| *maybeholesprp = (maybeobjmissingpr || maybefilemissingpr); |
| *knownholesprp = knownobjmissingpr; |
| return bagp; |
| } |
| |
| static void |
| pi_neededobjs_free(bag_t *bagp) |
| { |
| bagiter_t bagiter; |
| bagobj_t *bagobjp; |
| bagelem_t *bagelemp; |
| size64_t dummykey; |
| void *dummypayloadp; |
| |
| assert(bagp); |
| |
| bagiter_init(bagp, &bagiter); |
| |
| bagobjp = 0; |
| while ((bagelemp = bagiter_next(&bagiter, (void **)&bagobjp))) { |
| bag_remove(bagp, bagelemp, &dummykey, &dummypayloadp); |
| assert(bagobjp); |
| assert(bagobjp == (bagobj_t *)dummypayloadp); |
| free((void *)bagobjp); |
| bagobjp = 0; |
| } |
| |
| bag_free(bagp); |
| } |
| |
| /* a macro predicate to indicate if we know we are done. if we are not |
| * done or don't know, returns FALSE. |
| */ |
| static bool_t |
| pi_alldone(void) |
| { |
| bag_t *bagp; |
| bool_t knownholespr; |
| bool_t maybeholespr; |
| size_t cnt; |
| |
| knownholespr = BOOL_FALSE; |
| maybeholespr = BOOL_FALSE; |
| bagp = pi_neededobjs_nondir_alloc(&knownholespr, |
| &maybeholespr, |
| BOOL_TRUE, |
| BOOL_FALSE); |
| if (bagp) { |
| cnt = cntobj(bagp); |
| pi_neededobjs_free(bagp); |
| } else { |
| cnt = 0; |
| } |
| |
| if (cnt || knownholespr || maybeholespr) { |
| return BOOL_FALSE; |
| } else { |
| return BOOL_TRUE; |
| } |
| } |
| |
| /* tells the persistent inventory we hit end-of-data while examining the |
| * object specified by the index param. this tells us we've seen the end |
| * of the stream as well as the end of the object. |
| */ |
| static void |
| pi_hiteod(ix_t strmix, ix_t objix) |
| { |
| ix_t ix; |
| dh_t strmh; |
| dh_t objh; |
| size_t objcnt; |
| ix_t lastobjix; |
| |
| pi_lock(); |
| |
| /* get handle to the indexed stream |
| */ |
| for (ix = 0, |
| strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL && ix < strmix |
| ; |
| ix++, |
| strmh = DH2S(strmh)->s_nexth) |
| ; |
| assert(strmh != DH_NULL); |
| |
| /* get index to last object in stream |
| */ |
| for (objcnt = 0, objh = DH2S(strmh)->s_cldh |
| ; |
| objh != DH_NULL |
| ; |
| objh = DH2O(objh)->o_nexth, objcnt++) |
| ; |
| assert(objcnt != 0); |
| lastobjix = objcnt - 1; |
| |
| pi_unlock(); |
| |
| /* can't possibly happen, but check for case where pi indicates |
| * other media objects beyond this one. |
| */ |
| if (objix != lastobjix) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "hit EOD at stream %u object %u, " |
| "yet inventory indicates last object index is %u\n"), |
| strmix, |
| objix, |
| lastobjix); |
| } else { |
| pi_seestrmend(strmix); |
| } |
| |
| pi_seeobjstrmend(strmix, lastobjix); |
| } |
| |
| /* tells the persistent inventory we hit end-of-media while examining the |
| * object specified by the index param. this tells us we've seen the end |
| * of the object. |
| */ |
| static void |
| pi_hiteom(ix_t strmix, ix_t objix) |
| { |
| pi_seeobjstrmend(strmix, objix); |
| } |
| |
| static void |
| pi_hitnextdump(ix_t strmix, ix_t objix, ix_t lastfileix) |
| { |
| ix_t ix; |
| dh_t strmh; |
| dh_t objh; |
| size_t objcnt; |
| ix_t lastobjix; |
| |
| pi_lock(); |
| |
| /* get handle to the indexed stream |
| */ |
| for (ix = 0, |
| strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL && ix < strmix |
| ; |
| ix++, |
| strmh = DH2S(strmh)->s_nexth) |
| ; |
| assert(strmh != DH_NULL); |
| |
| /* get index to last object in stream |
| */ |
| for (objcnt = 0, objh = DH2S(strmh)->s_cldh |
| ; |
| objh != DH_NULL |
| ; |
| objh = DH2O(objh)->o_nexth, objcnt++) |
| ; |
| assert(objcnt != 0); |
| lastobjix = objcnt - 1; |
| |
| pi_unlock(); |
| |
| /* can't possibly happen, but check for case where pi indicates |
| * other media objects beyond this one. |
| */ |
| if (objix != lastobjix) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "hit next dump at stream %u object %u file %u, " |
| "yet inventory indicates last object index is %u\n"), |
| strmix, |
| objix, |
| lastfileix, |
| lastobjix); |
| } else { |
| pi_seestrmend(strmix); |
| } |
| |
| pi_seeobjstrmend(strmix, lastobjix); |
| } |
| |
| /* returns TRUE if pi is certain no more useful media files remaining |
| * on object. if any doubt, such as not knowing the last media file on |
| * the object, returns FALSE. |
| */ |
| static bool_t |
| pi_know_no_more_on_object(purp_t purp, ix_t strmix, ix_t objix) |
| { |
| ix_t ix; |
| dh_t strmh; |
| dh_t objh; |
| dh_t fileh; |
| |
| assert(purp == PURP_DIR || purp == PURP_NONDIR); |
| |
| pi_lock(); |
| |
| /* get handle to the indexed stream |
| */ |
| for (ix = 0, |
| strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL && ix < strmix |
| ; |
| ix++, |
| strmh = DH2S(strmh)->s_nexth) |
| ; |
| assert(strmh != DH_NULL); |
| |
| /* get handle to indexed object |
| */ |
| for (ix = 0, objh = DH2S(strmh)->s_cldh |
| ; |
| objh != DH_NULL && ix < objix |
| ; |
| ix++, |
| objh = DH2O(objh)->o_nexth) |
| ; |
| assert(objh != DH_NULL); |
| |
| /* if don't know last media file on object, return FALSE |
| */ |
| if (!DH2O(objh)->o_lmfknwnpr) { |
| pi_unlock(); |
| return BOOL_FALSE; |
| } |
| |
| /* check all media files on object. if any are not marked done, |
| * return FALSE. |
| */ |
| for (fileh = DH2O(objh)->o_cldh |
| ; |
| fileh != DH_NULL |
| ; |
| fileh = DH2F(fileh)->f_nexth) { |
| if (DH2F(fileh)->f_flags & PF_INV) { |
| continue; |
| } |
| if (DH2F(fileh)->f_flags & PF_TERM) { |
| continue; |
| } |
| if (purp == PURP_DIR) { |
| if (!DH2F(fileh)->f_dirtriedpr) { |
| pi_unlock(); |
| return BOOL_FALSE; |
| } |
| } else { |
| if (!DH2F(fileh)->f_nondirskippr |
| && |
| !DH2F(fileh)->f_nondirdonepr) { |
| pi_unlock(); |
| return BOOL_FALSE; |
| } |
| } |
| } |
| |
| pi_unlock(); |
| return BOOL_TRUE; |
| } |
| |
| static bool_t |
| pi_know_no_more_beyond_on_object(purp_t purp, |
| ix_t strmix, |
| ix_t objix, |
| ix_t fileix) |
| { |
| ix_t ix; |
| dh_t strmh; |
| dh_t objh; |
| dh_t fileh; |
| |
| assert(purp == PURP_DIR || purp == PURP_NONDIR); |
| |
| pi_lock(); |
| |
| /* get handle to the indexed stream |
| */ |
| for (ix = 0, |
| strmh = persp->s.strmheadh |
| ; |
| strmh != DH_NULL && ix < strmix |
| ; |
| ix++, |
| strmh = DH2S(strmh)->s_nexth) |
| ; |
| assert(strmh != DH_NULL); |
| |
| /* get handle to indexed object |
| */ |
| for (ix = 0, |
| objh = DH2S(strmh)->s_cldh |
| ; |
| objh != DH_NULL && ix < objix |
| ; |
| ix++, |
| objh = DH2O(objh)->o_nexth) |
| ; |
| assert(objh != DH_NULL); |
| |
| /* if don't know last media file on object, return FALSE |
| */ |
| if (!DH2O(objh)->o_lmfknwnpr) { |
| pi_unlock(); |
| return BOOL_FALSE; |
| } |
| |
| /* check all files on object after indexed file. if any are not marked |
| * done, return FALSE. skip inventory and terminator files. |
| */ |
| for (ix = 0, |
| fileh = DH2O(objh)->o_cldh |
| ; |
| fileh != DH_NULL |
| ; |
| ix++, |
| fileh = DH2F(fileh)->f_nexth) { |
| if (ix <= fileix) { |
| continue; |
| } |
| if (DH2F(fileh)->f_flags & PF_INV) { |
| continue; |
| } |
| if (DH2F(fileh)->f_flags & PF_TERM) { |
| continue; |
| } |
| if (purp == PURP_DIR) { |
| if (!DH2F(fileh)->f_dirtriedpr) { |
| pi_unlock(); |
| return BOOL_FALSE; |
| } |
| } else { |
| if (!DH2F(fileh)->f_nondirdonepr |
| && |
| !DH2F(fileh)->f_nondirskippr) { |
| pi_unlock(); |
| return BOOL_FALSE; |
| } |
| } |
| } |
| |
| pi_unlock(); |
| return BOOL_TRUE; |
| } |
| |
| /* indicates if the given extent group range is called for by the |
| * ino map. *---o (endpoint not inclusive) |
| */ |
| static bool_t |
| gapneeded(egrp_t *firstegrpp, egrp_t *lastegrpp) |
| { |
| xfs_ino_t endino; |
| |
| if (firstegrpp->eg_ino > lastegrpp->eg_ino) { |
| return BOOL_FALSE; |
| } |
| |
| if (firstegrpp->eg_ino == lastegrpp->eg_ino |
| && |
| firstegrpp->eg_off > lastegrpp->eg_off) { |
| return BOOL_FALSE; |
| } |
| |
| if (lastegrpp->eg_off > 0 || lastegrpp->eg_ino == 0) { |
| endino = lastegrpp->eg_ino; |
| } else { |
| endino = lastegrpp->eg_ino - 1; |
| } |
| |
| if (!inomap_rst_needed(firstegrpp->eg_ino, endino)) { |
| return BOOL_FALSE; |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| static void |
| addobj(bag_t *bagp, |
| uuid_t *idp, |
| label_t label, |
| bool_t indrivepr, |
| ix_t indriveix) |
| { |
| bagobj_t *bagobjp; |
| |
| bagobjp = (bagobj_t *)calloc(1, sizeof(bagobj_t)); |
| assert(bagobjp); |
| uuid_copy(bagobjp->id, *idp); |
| strncpy(bagobjp->label, |
| label, |
| sizeof(bagobjp->label)); |
| bagobjp->indrivepr = indrivepr; |
| bagobjp->indriveix = indriveix; |
| bag_insert(bagp, |
| &bagobjp->bagelem, |
| (size64_t)0, |
| (void *)bagobjp); |
| } |
| |
| static size_t |
| cntobj(bag_t *bagp) |
| { |
| bagiter_t bagiter; |
| bagobj_t *bagobjp; |
| size_t cnt; |
| |
| assert(bagp); |
| |
| bagiter_init(bagp, &bagiter); |
| cnt = 0; |
| bagobjp = 0; /* keep lint happy */ |
| while (bagiter_next(&bagiter, (void **)&bagobjp)) { |
| cnt++; |
| bagobjp = 0; /* keep lint happy */ |
| } |
| |
| return cnt; |
| } |
| |
| |
| /* misc. static functions ***************************************************/ |
| |
| /* queries inventory for the base of the given session. if the given session |
| * was a resumed dump, then must be last dump of same level. otherwise, |
| * must be last dump of a lesser level |
| */ |
| static bool_t |
| askinvforbaseof(uuid_t baseid, inv_session_t *sessp) |
| { |
| ix_t level; |
| bool_t resumedpr; |
| inv_idbtoken_t invtok; |
| inv_session_t *basesessp; |
| bool_t ok; |
| |
| level = (ix_t)sessp->s_level; |
| resumedpr = sessp->s_isresumed; |
| |
| /* don't look for base if level 0 and not resumed |
| */ |
| if (level == 0 && !resumedpr) { |
| return BOOL_TRUE; |
| } |
| |
| /* open the inventory for this file system |
| */ |
| invtok = inv_open(INV_BY_UUID, |
| INV_SEARCH_ONLY, |
| (void *)&sessp->s_fsid); |
| if (invtok == INV_TOKEN_NULL) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "unable to open inventory to validate dump\n")); |
| return BOOL_FALSE; |
| } |
| |
| /* get the base session |
| */ |
| if (resumedpr) { |
| ok = inv_lastsession_level_equalto(&sessp->s_fsid, |
| invtok, |
| (u_char_t)level, |
| &basesessp); |
| } else { |
| ok = inv_lastsession_level_lessthan(&sessp->s_fsid, |
| invtok, |
| (u_char_t)level, |
| &basesessp); |
| } |
| if (!ok) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "unable to find base dump in inventory " |
| "to validate dump\n")); |
| return BOOL_FALSE; |
| } |
| |
| /* close the inventory |
| */ |
| ok = inv_close(invtok); |
| assert(ok); |
| |
| /* return id of base session |
| */ |
| uuid_copy(baseid, basesessp->s_sesid); |
| |
| /* free the base session descriptor |
| */ |
| inv_free_session(&basesessp); |
| |
| return BOOL_TRUE; |
| } |
| |
| static bool_t |
| dumpcompat(bool_t resumepr, ix_t level, uuid_t baseid, bool_t logpr) |
| { |
| if (persp->a.cumpr) { |
| if (persp->a.dumpcnt == 0) { |
| if (resumepr) { |
| if (logpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "cumulative restores must begin with " |
| "an initial (not resumed) " |
| "level 0 dump\n")); |
| } |
| return BOOL_FALSE; |
| } |
| if (level > 0) { |
| if (logpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "cumulative restores must begin with " |
| "a level 0 dump\n")); |
| } |
| return BOOL_FALSE; |
| } |
| } else { |
| if (resumepr) { |
| if (uuid_compare(persp->a.lastdumpid, |
| baseid) != 0) { |
| if (logpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "selected resumed dump " |
| "not a resumption of " |
| "previously applied dump\n")); |
| } |
| return BOOL_FALSE; |
| } |
| } else { |
| if (uuid_compare(persp->a.lastdumpid, |
| baseid) != 0) { |
| if (logpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "selected dump not based on " |
| "previously applied dump\n")); |
| } |
| return BOOL_FALSE; |
| } |
| } |
| } |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| /* prompts for a new media object. supplies list of media objects still |
| * needed, and indicates if there are or may be unidentified media objects |
| * still needed/available |
| */ |
| static bool_t |
| Media_prompt_change(drive_t *drivep, |
| purp_t purp, |
| bag_t *bagp, |
| bool_t knownholespr, |
| bool_t maybeholespr) |
| { |
| fold_t fold; |
| char question[100]; |
| char *preamblestr[PREAMBLEMAX]; |
| size_t preamblecnt; |
| char *querystr[QUERYMAX]; |
| size_t querycnt; |
| char *choicestr[CHOICEMAX]; |
| size_t choicecnt; |
| char *ackstr[ACKMAX]; |
| size_t ackcnt; |
| char *postamblestr[POSTAMBLEMAX]; |
| size_t postamblecnt; |
| ix_t doix; |
| ix_t dontix; |
| ix_t invstatix; |
| ix_t neededix; |
| ix_t responseix; |
| ix_t sigintix; |
| |
| retry: |
| fold_init(fold, _("change media dialog"), '='); |
| preamblecnt = 0; |
| preamblestr[preamblecnt++ ] = "\n"; |
| preamblestr[preamblecnt++] = fold; |
| preamblestr[preamblecnt++ ] = "\n\n"; |
| assert(preamblecnt <= PREAMBLEMAX); |
| dlog_begin(preamblestr, preamblecnt); |
| |
| /* query: ask if media changed or declined |
| */ |
| if (drivecnt > 1) { |
| sprintf(question, _( |
| "please change media in " |
| "drive %u\n"), |
| (unsigned int)drivep->d_index); |
| } else { |
| sprintf(question, _( |
| "please change media in " |
| "drive\n")); |
| } |
| querycnt = 0; |
| querystr[querycnt++] = question; |
| assert(querycnt <= QUERYMAX); |
| choicecnt = 0; |
| dontix = choicecnt; |
| choicestr[choicecnt++ ] = _("media change declined"); |
| if (purp != PURP_SEARCH) { |
| invstatix = choicecnt; |
| choicestr[choicecnt++ ] = _("display media inventory status"); |
| neededix = choicecnt; |
| choicestr[choicecnt++ ] = _("list needed media objects"); |
| } else { |
| invstatix = IXMAX; |
| neededix = IXMAX; |
| } |
| doix = choicecnt; |
| choicestr[choicecnt++ ] = _("media changed"); |
| assert(choicecnt <= CHOICEMAX); |
| sigintix = IXMAX - 1; |
| |
| responseix = dlog_multi_query(querystr, |
| querycnt, |
| choicestr, |
| choicecnt, |
| 0, /* hilitestr */ |
| IXMAX, /* hiliteix */ |
| 0, /* defaultstr */ |
| doix, /* defaultix */ |
| DLOG_TIMEOUT_MEDIA, |
| dontix, /* timeout ix */ |
| sigintix, /* sigint ix */ |
| dontix, /* sighup ix */ |
| dontix); /* sigquit ix */ |
| ackcnt = 0; |
| if (responseix == doix) { |
| ackstr[ackcnt++ ] = _("examining new media\n"); |
| } else if (responseix == dontix) { |
| ackstr[ackcnt++ ] = _("media change aborted\n"); |
| } else if (responseix == invstatix) { |
| ackstr[ackcnt++ ] = "\n"; |
| assert(ackcnt <= ACKMAX); |
| dlog_multi_ack(ackstr, |
| ackcnt); |
| pi_show_nomloglock(); |
| postamblecnt = 0; |
| fold_init(fold, _("end dialog"), '-'); |
| postamblestr[postamblecnt++ ] = "\n"; |
| postamblestr[postamblecnt++] = fold; |
| postamblestr[postamblecnt++ ] = "\n\n"; |
| assert(postamblecnt <= POSTAMBLEMAX); |
| dlog_end(postamblestr, |
| postamblecnt); |
| goto retry; |
| } else if (responseix == neededix) { |
| ackstr[ackcnt++ ] = "\n"; |
| assert(ackcnt <= ACKMAX); |
| dlog_multi_ack(ackstr, |
| ackcnt); |
| display_needed_objects(purp, |
| bagp, |
| knownholespr, |
| maybeholespr); |
| postamblecnt = 0; |
| fold_init(fold, "end dialog", '-'); |
| postamblestr[postamblecnt++ ] = "\n"; |
| postamblestr[postamblecnt++] = fold; |
| postamblestr[postamblecnt++ ] = "\n\n"; |
| assert(postamblecnt <= POSTAMBLEMAX); |
| dlog_end(postamblestr, |
| postamblecnt); |
| goto retry; |
| } else { |
| assert(responseix == sigintix); |
| ackstr[ackcnt++ ] = _("keyboard interrupt\n"); |
| } |
| |
| assert(ackcnt <= ACKMAX); |
| dlog_multi_ack(ackstr, |
| ackcnt); |
| |
| postamblecnt = 0; |
| fold_init(fold, _("end dialog"), '-'); |
| postamblestr[postamblecnt++ ] = "\n"; |
| postamblestr[postamblecnt++] = fold; |
| postamblestr[postamblecnt++ ] = "\n\n"; |
| assert(postamblecnt <= POSTAMBLEMAX); |
| dlog_end(postamblestr, |
| postamblecnt); |
| |
| if (responseix == sigintix) { |
| if (cldmgr_stop_requested()) { |
| return BOOL_FALSE; |
| } |
| sleep(1); /* to allow main thread to begin dialog */ |
| mlog(MLOG_NORMAL | MLOG_BARE, |
| ""); /* to block until main thread dialog complete */ |
| sleep(1); /* to allow main thread to request children die */ |
| if (cldmgr_stop_requested()) { |
| return BOOL_FALSE; |
| } |
| mlog(MLOG_DEBUG, |
| "retrying media change dialog\n"); |
| goto retry; |
| } |
| |
| return responseix == doix; |
| } |
| |
| /* prompts the operator, asking if the current media file header describes |
| * the dump to be restored |
| */ |
| static bool_t |
| promptdumpmatch(ix_t thrdix, |
| global_hdr_t *grhdrp, |
| media_hdr_t *mrhdrp, |
| content_hdr_t *crhdrp, |
| content_inode_hdr_t *scrhdrp) |
| { |
| fold_t fold; |
| char introstring[80]; |
| char *preamblestr[PREAMBLEMAX]; |
| size_t preamblecnt; |
| char *querystr[QUERYMAX]; |
| size_t querycnt; |
| char *choicestr[CHOICEMAX]; |
| size_t choicecnt; |
| char *ackstr[ACKMAX]; |
| size_t ackcnt; |
| char *postamblestr[POSTAMBLEMAX]; |
| size_t postamblecnt; |
| ix_t doix; |
| ix_t dontix; |
| ix_t responseix; |
| ix_t sigintix; |
| |
| retry: |
| preamblecnt = 0; |
| fold_init(fold, _("dump selection dialog"), '='); |
| preamblestr[preamblecnt++ ] = "\n"; |
| preamblestr[preamblecnt++] = fold; |
| preamblestr[preamblecnt++ ] = "\n\n"; |
| assert(preamblecnt <= PREAMBLEMAX); |
| dlog_begin(preamblestr, preamblecnt); |
| |
| /* display vital stats and ask if this one should be restored |
| */ |
| if (drivecnt > 0) { |
| sprintf(introstring, _( |
| "the following dump has been found" |
| " on drive %u" |
| "\n\n"), |
| (unsigned int)thrdix); |
| } else { |
| sprintf(introstring, _( |
| "the following dump has been found" |
| "\n\n")); |
| } |
| assert(strlen(introstring) < sizeof(introstring)); |
| display_dump_label(BOOL_FALSE, |
| MLOG_NORMAL | MLOG_BARE, |
| introstring, |
| grhdrp, |
| mrhdrp, |
| crhdrp, |
| scrhdrp); |
| |
| querycnt = 0; |
| if (tranp->t_toconlypr) { |
| querystr[querycnt++ ] = _("\nexamine this dump?\n"); |
| } else { |
| querystr[querycnt++] = (persp->a.interpr) ? |
| _("\ninteractively restore from this dump?\n") |
| : _("\nrestore this dump?\n"); |
| } |
| assert(querycnt <= QUERYMAX); |
| choicecnt = 0; |
| dontix = choicecnt; |
| choicestr[choicecnt++ ] = _("skip"); |
| doix = choicecnt; |
| choicestr[choicecnt++] = (persp->a.interpr) ? |
| _("interactively restore\n") : _("restore\n"); |
| assert(choicecnt <= CHOICEMAX); |
| sigintix = IXMAX - 1; |
| |
| responseix = dlog_multi_query(querystr, |
| querycnt, |
| choicestr, |
| choicecnt, |
| 0, /* hilitestr */ |
| IXMAX, /* hiliteix */ |
| 0, /* defaultstr */ |
| doix, /* defaultix */ |
| 0, |
| IXMAX, /* timeout ix */ |
| sigintix, /* sigint ix */ |
| dontix, /* sighup ix */ |
| dontix); /* sigquit ix */ |
| ackcnt = 0; |
| if (responseix == doix) { |
| ackstr[ackcnt++] = (persp->a.interpr) ? |
| _("this dump selected for interactive restoral\n") |
| : _("this dump selected for restoral\n"); |
| } else if (responseix == dontix) { |
| ackstr[ackcnt++ ] = _("dump skipped\n"); |
| } else { |
| assert(responseix == sigintix); |
| ackstr[ackcnt++ ] = _("keyboard interrupt\n"); |
| } |
| |
| assert(ackcnt <= ACKMAX); |
| dlog_multi_ack(ackstr, |
| ackcnt); |
| |
| postamblecnt = 0; |
| fold_init(fold, "end dialog", '-'); |
| postamblestr[postamblecnt++ ] = "\n"; |
| postamblestr[postamblecnt++] = fold; |
| postamblestr[postamblecnt++ ] = "\n\n"; |
| assert(postamblecnt <= POSTAMBLEMAX); |
| dlog_end(postamblestr, |
| postamblecnt); |
| |
| if (responseix == sigintix) { |
| if (cldmgr_stop_requested()) { |
| return BOOL_FALSE; |
| } |
| sleep(1); /* to allow main thread to begin dialog */ |
| mlog(MLOG_NORMAL | MLOG_BARE, |
| ""); /* to block until main thread dialog complete */ |
| sleep(1); /* to allow main thread to request children die */ |
| if (cldmgr_stop_requested()) { |
| return BOOL_FALSE; |
| } |
| mlog(MLOG_DEBUG, |
| "retrying dump selection dialog\n"); |
| goto retry; |
| } |
| |
| return responseix == doix; |
| } |
| |
| /* restore_file - knows how to restore non-directory files |
| * |
| * uses the tree's callback iterator, which will call me for each |
| * link to the specified inode. |
| */ |
| struct cb_context { |
| drive_t *cb_drivep; |
| filehdr_t *cb_fhdrp; |
| rv_t cb_rv; |
| bool_t cb_ehcs; |
| bool_t cb_ahcs; |
| char *cb_path1; |
| char *cb_path2; |
| }; |
| |
| typedef struct cb_context cb_context_t; |
| |
| static bool_t restore_file_cb(void *, bool_t, char *, char *); |
| |
| static rv_t |
| restore_file(drive_t *drivep, |
| filehdr_t *fhdrp, |
| bool_t ehcs, |
| bool_t ahcs, |
| char *path1, |
| char *path2) |
| { |
| rv_t rv; |
| bstat_t *bstatp = &fhdrp->fh_stat; |
| cb_context_t context; |
| |
| /* ask the tree to call me back for each link to this inode. |
| * my callback will restore the file the first time it is |
| * invoked, and create a hard link in subsequent calls. |
| */ |
| context.cb_drivep = drivep; |
| context.cb_fhdrp = fhdrp; |
| context.cb_rv = RV_OK; |
| context.cb_ehcs = ehcs; |
| context.cb_ahcs = ahcs; |
| context.cb_path1 = path1; |
| context.cb_path2 = path2; |
| rv = tree_cb_links(bstatp->bs_ino, |
| bstatp->bs_gen, |
| bstatp->bs_ctime.tv_sec, |
| bstatp->bs_mtime.tv_sec, |
| restore_file_cb, |
| &context, |
| path1, |
| path2); |
| if (context.cb_rv) /* context error result has precedence */ |
| return context.cb_rv; /* this would be set by callback */ |
| else |
| return rv; |
| } |
| |
| /* called for each link to the file described by fhdr. the first |
| * call is detected by noting linkpr is FALSE, and is used to create/ |
| * update the first link to the file, using path1. subsequent calls have |
| * linkpr set false, and should link path1 to path2. if path1 is ever null, |
| * just pull from media: don't create. |
| * if this func returns FALSE, will cause tree_cb_links to abort |
| */ |
| static bool_t |
| restore_file_cb(void *cp, bool_t linkpr, char *path1, char *path2) |
| { |
| cb_context_t *contextp = (cb_context_t *)cp; |
| drive_t *drivep = contextp->cb_drivep; |
| filehdr_t *fhdrp = contextp->cb_fhdrp; |
| bstat_t *bstatp = &fhdrp->fh_stat; |
| rv_t *rvp = &contextp->cb_rv; |
| bool_t ehcs = contextp->cb_ehcs; |
| bool_t ahcs = contextp->cb_ahcs; |
| stream_context_t *strctxp = (stream_context_t *)drivep->d_strmcontextp; |
| |
| int rval; |
| bool_t ok; |
| |
| if (cldmgr_stop_requested()) { |
| *rvp = RV_INTR; |
| return BOOL_FALSE; |
| } |
| |
| if (!linkpr) { |
| if (path1) { |
| /* cache the path for use in restoring attributes |
| * and extended attributes |
| */ |
| strcpy(strctxp->sc_path, path1); |
| } |
| |
| /* call type-specific function to create the file |
| */ |
| switch(bstatp->bs_mode & S_IFMT) { |
| case S_IFREG: |
| ok = restore_reg(drivep, fhdrp, rvp, path1); |
| if (!ok) |
| return ok; |
| if (fhdrp->fh_flags & FILEHDR_FLAGS_EXTATTR) { |
| *rvp = restore_extattr(drivep, |
| fhdrp, |
| path1, |
| ahcs, |
| BOOL_FALSE, /* isdirpr */ |
| BOOL_FALSE, /* onlydoreadpr */ |
| DAH_NULL); |
| } else { |
| ok = restore_extent_group(drivep, |
| fhdrp, |
| path1, |
| strctxp->sc_fd, |
| ehcs, |
| rvp); |
| } |
| return ok; |
| case S_IFBLK: |
| case S_IFCHR: |
| case S_IFIFO: |
| case S_IFSOCK: |
| ok = restore_spec(fhdrp, rvp, path1); |
| return ok; |
| case S_IFLNK: |
| ok = restore_symlink(drivep, |
| fhdrp, |
| rvp, |
| path1, |
| path2, |
| ehcs); |
| return ok; |
| default: |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "ino %llu: unknown file type: %08x\n"), |
| bstatp->bs_ino, |
| bstatp->bs_mode); |
| return BOOL_FALSE; |
| } |
| } else if (!tranp->t_toconlypr) { |
| assert(path1); |
| assert(path2); |
| mlog(MLOG_TRACE, |
| "linking %s to %s\n", |
| path1, |
| path2); |
| rval = unlink(path2); |
| if (rval && errno != ENOENT) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to unlink " |
| "current file prior to linking " |
| "%s to %s:" |
| " %s\n"), |
| path1, |
| path2, |
| strerror(errno)); |
| } else { |
| rval = link(path1, path2); |
| if (rval) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "attempt to " |
| "link %s to %s failed:" |
| " %s\n"), |
| path1, |
| path2, |
| strerror(errno)); |
| } |
| } |
| return BOOL_TRUE; |
| } else { |
| mlog(MLOG_NORMAL | MLOG_BARE, |
| "%s\n", |
| path2); |
| return BOOL_TRUE; |
| } |
| } |
| |
| /* |
| * Set the file owner and strip suid/sgid if necessary. On failure, it will |
| * close the file descriptor, unlink the file and return -1. On success, |
| * it will mark the stream contexts as having set the owner and return 0. |
| */ |
| static int |
| set_file_owner( |
| char *path, |
| int *fdp, |
| stream_context_t *strcxtp) |
| { |
| bstat_t *bstatp = &strcxtp->sc_bstat; |
| mode_t mode = (mode_t)bstatp->bs_mode; |
| int rval; |
| |
| rval = fchown(*fdp, (uid_t)bstatp->bs_uid, (gid_t)bstatp->bs_gid); |
| if (!rval) |
| goto done; |
| |
| mlog(MLOG_VERBOSE | MLOG_WARNING, |
| _("chown (uid=%u, gid=%u) %s failed: %s\n"), |
| bstatp->bs_uid, bstatp->bs_gid, path, strerror(errno)); |
| |
| if (mode & S_ISUID) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, |
| _("stripping setuid bit on %s since chown failed\n"), |
| path); |
| mode &= ~S_ISUID; |
| } |
| |
| if ((mode & (S_ISGID|S_IXGRP)) == (S_ISGID|S_IXGRP)) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, |
| _("stripping setgid bit on %s since chown failed\n"), |
| path); |
| mode &= ~S_ISGID; |
| } |
| |
| if (mode == (mode_t)bstatp->bs_mode) |
| goto done; |
| |
| rval = fchmod(*fdp, mode); |
| if (rval) { |
| mlog(MLOG_VERBOSE | MLOG_ERROR, |
| _("unable to strip setuid/setgid on %s, unlinking file.\n"), |
| path); |
| unlink(path); |
| close(*fdp); |
| *fdp = -1; |
| return -1; |
| } |
| done: |
| strcxtp->sc_ownerset = BOOL_TRUE; |
| return 0; |
| } |
| |
| /* called to begin a regular file. if no path given, or if just toc, |
| * don't actually write, just read. also get into that situation if |
| * cannot prepare destination. fd == -1 signifies no write. *statp |
| * is set to indicate drive errors. returns FALSE if should abort |
| * this iteration. |
| */ |
| static bool_t |
| restore_reg(drive_t *drivep, |
| filehdr_t *fhdrp, |
| rv_t *rvp, |
| char *path) |
| { |
| bstat_t *bstatp = &fhdrp->fh_stat; |
| stream_context_t *strctxp = (stream_context_t *)drivep->d_strmcontextp; |
| int *fdp = &strctxp->sc_fd; |
| int rval; |
| struct fsxattr fsxattr; |
| struct stat64 stat; |
| |
| if (!path) |
| return BOOL_TRUE; |
| |
| if (fhdrp->fh_offset) { |
| if (!tranp->t_toconlypr) { |
| mlog(MLOG_TRACE, |
| "restoring regular file ino %llu %s" |
| " (offset %lld)\n", |
| bstatp->bs_ino, |
| path, |
| fhdrp->fh_offset); |
| } else { |
| mlog(MLOG_NORMAL | MLOG_BARE, |
| _("%s (offset %lld)\n"), |
| path, |
| fhdrp->fh_offset); |
| } |
| } else { |
| if (!tranp->t_toconlypr) { |
| mlog(MLOG_TRACE, |
| "restoring regular file ino %llu %s\n", |
| bstatp->bs_ino, |
| path); |
| } else { |
| mlog(MLOG_NORMAL | MLOG_BARE, |
| "%s\n", |
| path); |
| } |
| } |
| |
| if (tranp->t_toconlypr) |
| return BOOL_TRUE; |
| |
| *fdp = open(path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); |
| if (*fdp < 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, |
| _("open of %s failed: %s: discarding ino %llu\n"), |
| path, |
| strerror(errno), |
| bstatp->bs_ino); |
| return BOOL_TRUE; |
| } |
| |
| rval = fstat64(*fdp, &stat); |
| if (rval != 0) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, |
| _("attempt to stat %s failed: %s\n"), |
| path, |
| strerror(errno)); |
| } else { |
| if (stat.st_size != bstatp->bs_size) { |
| mlog(MLOG_TRACE, |
| "truncating %s from %lld to %lld\n", |
| path, |
| stat.st_size, |
| bstatp->bs_size); |
| |
| rval = ftruncate64(*fdp, bstatp->bs_size); |
| if (rval != 0) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, |
| _("attempt to truncate %s failed: %s\n"), |
| path, |
| strerror(errno)); |
| } |
| } |
| } |
| |
| if (strctxp->sc_ownerset == BOOL_FALSE && persp->a.ownerpr) { |
| rval = set_file_owner(path, fdp, strctxp); |
| if (rval) |
| return BOOL_TRUE; |
| } |
| |
| if (persp->a.dstdirisxfspr) { |
| |
| /* set the extended inode flags, except those which must |
| * be set only after all data has been restored. |
| */ |
| assert(bstatp->bs_extsize >= 0); |
| memset((void *)&fsxattr, 0, sizeof(fsxattr)); |
| fsxattr.fsx_xflags = bstatp->bs_xflags & ~POST_DATA_XFLAGS; |
| fsxattr.fsx_extsize = (uint32_t) bstatp->bs_extsize; |
| fsxattr.fsx_projid = bstat_projid(bstatp); |
| |
| rval = ioctl(*fdp, XFS_IOC_FSSETXATTR, (void *)&fsxattr); |
| if (rval < 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, |
| _("attempt to set extended attributes " |
| "(xflags 0x%x, extsize = 0x%x, projid = 0x%x) " |
| "of %s failed: %s\n"), |
| fsxattr.fsx_xflags, |
| fsxattr.fsx_extsize, |
| fsxattr.fsx_projid, |
| path, |
| strerror(errno)); |
| } |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| /* called to peel a regular file's extent groups from the media. |
| * if no path given, or if just toc, don't actually write, just |
| * read. fd == -1 signifies no write. *rvp is set to indicate |
| * drive errors. returns FALSE if should abort this iteration. |
| */ |
| static bool_t |
| restore_extent_group(drive_t *drivep, |
| filehdr_t *fhdrp, |
| char *path, |
| int fd, |
| bool_t ehcs, |
| rv_t *rvp) |
| { |
| bstat_t *bstatp = &fhdrp->fh_stat; |
| off64_t restoredsz = 0; |
| extenthdr_t ehdr; |
| off64_t bytesread; |
| rv_t rv; |
| |
| /* copy data extents from media to the file |
| */ |
| for (;;) { |
| /* read the extent header |
| */ |
| rv = read_extenthdr(drivep, &ehdr, ehcs); |
| if (rv != RV_OK) { |
| *rvp = rv; |
| return BOOL_FALSE; |
| } |
| mlog(MLOG_NITTY, |
| "read extent hdr type %s offset %lld sz %lld flags %x\n", |
| ehdr_typestr(ehdr.eh_type), |
| ehdr.eh_offset, |
| ehdr.eh_sz, |
| ehdr.eh_flags); |
| |
| /* if we see the specially marked last extent hdr, |
| * we are done. |
| */ |
| if (ehdr.eh_type == EXTENTHDR_TYPE_LAST) { |
| /* For a wholly sparse file, there is no HOLE |
| * record; advance restoredsz to EOF. |
| */ |
| if (!restoredsz) |
| restoredsz = bstatp->bs_size; |
| break; |
| } |
| |
| /* if its an ALIGNment extent, discard the extent. |
| */ |
| if (ehdr.eh_type == EXTENTHDR_TYPE_ALIGN) { |
| size_t sz; |
| assert(ehdr.eh_sz <= INTGENMAX); |
| sz = (size_t)ehdr.eh_sz; |
| rv = discard_padding(sz, drivep); |
| if (rv != RV_OK) { |
| *rvp = rv; |
| return BOOL_FALSE; |
| } |
| continue; |
| } |
| |
| /* Add up extents restored to later check if the file |
| * is done. |
| */ |
| restoredsz += ehdr.eh_sz; /* Increments of block size (usually 512) */ |
| |
| /* Holes do not need to be restored since we now |
| * unlink the file at the start of the restore. |
| */ |
| if (ehdr.eh_type == EXTENTHDR_TYPE_HOLE) { |
| continue; |
| } |
| |
| /* real data |
| */ |
| assert(ehdr.eh_type == EXTENTHDR_TYPE_DATA); |
| bytesread = 0; |
| rv = restore_extent(fhdrp, |
| &ehdr, |
| fd, |
| path, |
| drivep, |
| &bytesread); |
| if (rv != RV_OK) { |
| *rvp = rv; |
| return BOOL_FALSE; |
| } |
| |
| if (cldmgr_stop_requested()) { |
| *rvp = RV_INTR; |
| return BOOL_FALSE; |
| } |
| } |
| |
| /* The extent group has been restored. If the file is not |
| * complete, we may need to co-ordinate with other restore |
| * streams to time the restoration of extended attributes |
| * and certain extended inode flags. Register the portion |
| * of the file completed here in the persistent state. |
| */ |
| if (bstatp->bs_size > restoredsz) { |
| partial_reg(drivep->d_index, |
| bstatp->bs_ino, |
| bstatp->bs_size, |
| fhdrp->fh_offset, |
| restoredsz); |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| /* apply the attributes that can only go on now that all data |
| * and extended attributes have been applied. fd == -1 signifies |
| * no write, due to unknown path or toc only. |
| */ |
| static bool_t |
| restore_complete_reg(stream_context_t *strcxtp) |
| { |
| bstat_t *bstatp = &strcxtp->sc_bstat; |
| char *path = strcxtp->sc_path; |
| int fd = strcxtp->sc_fd; |
| struct utimbuf utimbuf; |
| int rval; |
| |
| // only applies to regular files |
| if (!S_ISREG((strcxtp->sc_bstat.bs_mode))) |
| return BOOL_TRUE; |
| |
| if (fd < 0) |
| return BOOL_TRUE; |
| |
| if (!partial_check(bstatp->bs_ino, bstatp->bs_size)) { |
| close(fd); |
| return BOOL_TRUE; |
| } |
| |
| /* set the access and modification times |
| */ |
| utimbuf.actime = (time32_t)bstatp->bs_atime.tv_sec; |
| utimbuf.modtime = (time32_t)bstatp->bs_mtime.tv_sec; |
| rval = utime(path, &utimbuf); |
| if (rval) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to set access and modification " |
| "times of %s: %s\n"), |
| path, |
| strerror(errno)); |
| } |
| |
| /* set the owner and group (if enabled) |
| */ |
| if (strcxtp->sc_ownerset == BOOL_FALSE && persp->a.ownerpr) { |
| rval = set_file_owner(path, &fd, strcxtp); |
| if (rval) |
| return BOOL_TRUE; |
| } |
| |
| /* set the permissions/mode |
| */ |
| rval = fchmod(fd, (mode_t)bstatp->bs_mode); |
| if (rval) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to set mode of %s: %s\n"), |
| path, |
| strerror(errno)); |
| } |
| |
| /* set any extended inode flags that couldn't be set |
| * prior to restoring the data. |
| */ |
| if (persp->a.dstdirisxfspr && bstatp->bs_xflags & POST_DATA_XFLAGS) { |
| struct fsxattr fsxattr; |
| memset((void *)&fsxattr, 0, sizeof(fsxattr)); |
| fsxattr.fsx_xflags = bstatp->bs_xflags; |
| fsxattr.fsx_extsize = (uint32_t)bstatp->bs_extsize; |
| fsxattr.fsx_projid = bstat_projid(bstatp); |
| |
| rval = ioctl(fd, XFS_IOC_FSSETXATTR, (void *)&fsxattr); |
| if (rval < 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, |
| _("attempt to set extended attributes " |
| "(xflags 0x%x, extsize = 0x%x, projid = 0x%x) " |
| "of %s failed: %s\n"), |
| fsxattr.fsx_xflags, |
| fsxattr.fsx_extsize, |
| fsxattr.fsx_projid, |
| path, |
| strerror(errno)); |
| } |
| } |
| |
| close(fd); |
| return BOOL_TRUE; |
| } |
| |
| /* ARGSUSED */ |
| static bool_t |
| restore_spec(filehdr_t *fhdrp, rv_t *rvp, char *path) |
| { |
| bstat_t *bstatp = &fhdrp->fh_stat; |
| struct utimbuf utimbuf; |
| char *printstr; |
| int rval; |
| |
| if (!path) { |
| return BOOL_TRUE; |
| } |
| |
| switch (bstatp->bs_mode & S_IFMT) { |
| case S_IFBLK: |
| printstr = _("block special file"); |
| break; |
| case S_IFCHR: |
| printstr = _("char special file"); |
| break; |
| case S_IFIFO: |
| printstr = _("named pipe"); |
| break; |
| case S_IFSOCK: |
| printstr = _("UNIX domain socket"); |
| break; |
| default: |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "%s: unknown file type: mode 0x%x ino %llu\n"), |
| path, |
| bstatp->bs_mode, |
| fhdrp->fh_stat.bs_ino); |
| return BOOL_TRUE; |
| } |
| |
| if (!tranp->t_toconlypr) { |
| mlog(MLOG_TRACE, |
| "restoring %s ino %llu %s\n", |
| printstr, |
| fhdrp->fh_stat.bs_ino, |
| path); |
| } else { |
| mlog(MLOG_NORMAL | MLOG_BARE, |
| "%s\n", |
| path); |
| } |
| |
| if (!tranp->t_toconlypr) { |
| if ((bstatp->bs_mode & S_IFMT) == S_IFSOCK) { |
| int sockfd; |
| struct sockaddr_un addr; |
| size_t addrlen; |
| |
| sockfd = socket(AF_UNIX, SOCK_STREAM, 0); |
| if (sockfd < 0) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to create " |
| "%s ino %llu %s: %s: discarding\n"), |
| printstr, |
| fhdrp->fh_stat.bs_ino, |
| path, |
| strerror(errno)); |
| return BOOL_TRUE; |
| } |
| memset((void *)&addr, 0, sizeof(addr)); |
| addr.sun_family = AF_UNIX; |
| if (strlen(path) >= sizeof(addr.sun_path)) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "pathname too long for bind of " |
| "%s ino %llu %s: discarding\n"), |
| printstr, |
| fhdrp->fh_stat.bs_ino, |
| path); |
| (void)close(sockfd); |
| return BOOL_TRUE; |
| } |
| strcpy(addr.sun_path, path); |
| addrlen = strlen(addr.sun_path) |
| + |
| sizeof(addr.sun_family); |
| rval = bind(sockfd, |
| (struct sockaddr *)&addr, |
| (int)addrlen); |
| if (rval < 0) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to bind " |
| "%s ino %llu %s: %s: discarding\n"), |
| printstr, |
| fhdrp->fh_stat.bs_ino, |
| path, |
| strerror(errno)); |
| (void)close(sockfd); |
| return BOOL_TRUE; |
| } |
| (void)close(sockfd); |
| |
| } else { |
| /* create the node |
| */ |
| rval = mknod(path, |
| (mode_t)bstatp->bs_mode, |
| (dev_t)IRIX_DEV_TO_KDEVT(bstatp->bs_rdev)); |
| if (rval && rval != EEXIST) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to create %s " |
| "ino %llu %s: %s: discarding\n"), |
| printstr, |
| fhdrp->fh_stat.bs_ino, |
| path, |
| strerror(errno)); |
| return BOOL_TRUE; |
| } |
| } |
| |
| /* set the owner and group (if enabled) |
| */ |
| if (persp->a.ownerpr) { |
| rval = chown(path, |
| (uid_t)bstatp->bs_uid, |
| (gid_t)bstatp->bs_gid); |
| if (rval) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, |
| _("chown (uid=%u, gid=%u) %s " |
| "failed: %s\n"), |
| bstatp->bs_uid, |
| bstatp->bs_gid, |
| path, |
| strerror(errno)); |
| } |
| } |
| |
| /* set the permissions/mode |
| */ |
| rval = chmod(path, (mode_t)fhdrp->fh_stat.bs_mode); |
| if (rval) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to set mode of %s: %s\n"), |
| path, |
| strerror(errno)); |
| } |
| |
| /* set the access and modification times |
| */ |
| utimbuf.actime = (time32_t)bstatp->bs_atime.tv_sec; |
| utimbuf.modtime = (time32_t)bstatp->bs_mtime.tv_sec; |
| rval = utime(path, &utimbuf); |
| if (rval) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to set access and modification " |
| "times of %s: %s\n"), |
| path, |
| strerror(errno)); |
| } |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| static bool_t |
| restore_symlink(drive_t *drivep, |
| filehdr_t *fhdrp, |
| rv_t *rvp, |
| char *path, |
| char *scratchpath, |
| bool_t ehcs) |
| { |
| bstat_t *bstatp = &fhdrp->fh_stat; |
| drive_ops_t *dop = drivep->d_opsp; |
| extenthdr_t ehdr; |
| char *scratch; |
| int nread; |
| int rval; |
| rv_t rv; |
| mode_t oldumask; |
| |
| if (path) { |
| if (!tranp->t_toconlypr) { |
| mlog(MLOG_TRACE, |
| "restoring symbolic link ino %llu %s\n", |
| bstatp->bs_ino, |
| path); |
| } else { |
| mlog(MLOG_NORMAL | MLOG_BARE, |
| "%s\n", |
| path); |
| } |
| } |
| |
| /* read the extent header |
| */ |
| rv = read_extenthdr(drivep, &ehdr, ehcs); |
| if (rv != RV_OK) { |
| *rvp = rv; |
| return BOOL_FALSE; |
| } |
| |
| /* symlinks always have one extent |
| */ |
| assert(ehdr.eh_type == EXTENTHDR_TYPE_DATA); |
| |
| /* read the link path extent |
| */ |
| if (ehdr.eh_sz < (off64_t)(2 * MAXPATHLEN)) { |
| scratch = scratchpath; |
| } else { |
| scratch = 0; |
| } |
| nread = read_buf(scratch, |
| (size_t)ehdr.eh_sz, |
| (void *)drivep, |
| (rfp_t)dop->do_read, |
| (rrbfp_t)dop->do_return_read_buf, |
| &rval); |
| if (rval) { |
| switch(rval) { |
| case DRIVE_ERROR_EOF: |
| case DRIVE_ERROR_EOD: |
| case DRIVE_ERROR_EOM: |
| case DRIVE_ERROR_MEDIA: |
| *rvp = RV_EOD; |
| break; |
| case DRIVE_ERROR_CORRUPTION: |
| *rvp = RV_CORRUPT; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| *rvp = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| *rvp = RV_CORE; |
| } |
| return BOOL_FALSE; |
| } |
| assert((off64_t)nread == ehdr.eh_sz); |
| if (!scratch) { |
| if (path) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to create symlink ino %llu " |
| "%s: src too long: discarding\n"), |
| bstatp->bs_ino, |
| path); |
| } |
| return BOOL_TRUE; |
| } |
| scratchpath[nread] = 0; |
| if (!tranp->t_toconlypr && path) { |
| /* create the symbolic link |
| */ |
| /* NOTE: There is no direct way to set mode for |
| * sym links. Do it using umask. |
| * No way of setting times for sym links. |
| */ |
| |
| oldumask = umask(((mode_t)(~bstatp->bs_mode)) & 0777); |
| |
| rval = symlink(scratchpath, path); |
| |
| umask(oldumask); |
| |
| if (rval) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to create " |
| "symlink ino %llu %s: %s: discarding\n"), |
| bstatp->bs_ino, |
| path, |
| strerror(errno)); |
| return BOOL_TRUE; |
| } |
| |
| /* set the owner and group (if enabled) |
| */ |
| if (persp->a.ownerpr) { |
| rval = lchown(path, |
| (uid_t)bstatp->bs_uid, |
| (gid_t)bstatp->bs_gid); |
| if (rval) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, |
| _("chown (uid=%u, gid=%u) %s " |
| "failed: %s\n"), |
| bstatp->bs_uid, |
| bstatp->bs_gid, |
| path, |
| strerror(errno)); |
| } |
| } |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| /* ARGSUSED */ |
| static rv_t |
| read_filehdr(drive_t *drivep, filehdr_t *fhdrp, bool_t fhcs) |
| { |
| bstat_t *bstatp = &fhdrp->fh_stat; |
| drive_ops_t *dop = drivep->d_opsp; |
| /* REFERENCED */ |
| int nread; |
| int rval; |
| filehdr_t tmpfh; |
| |
| nread = read_buf((char *)&tmpfh, |
| sizeof(*fhdrp), |
| (void *)drivep, |
| (rfp_t)dop->do_read, |
| (rrbfp_t)dop->do_return_read_buf, |
| &rval); |
| xlate_filehdr(&tmpfh, fhdrp, 1); |
| |
| switch(rval) { |
| case 0: |
| break; |
| case DRIVE_ERROR_EOD: |
| case DRIVE_ERROR_EOF: |
| case DRIVE_ERROR_EOM: |
| case DRIVE_ERROR_MEDIA: |
| return RV_EOD; |
| case DRIVE_ERROR_CORRUPTION: |
| return RV_CORRUPT; |
| case DRIVE_ERROR_DEVICE: |
| return RV_DRIVE; |
| case DRIVE_ERROR_CORE: |
| default: |
| return RV_CORE; |
| } |
| assert((size_t)nread == sizeof(*fhdrp)); |
| |
| mlog(MLOG_NITTY, |
| "read file hdr off %lld flags 0x%x ino %llu mode 0x%08x\n", |
| fhdrp->fh_offset, |
| fhdrp->fh_flags, |
| bstatp->bs_ino, |
| bstatp->bs_mode); |
| |
| if (fhcs) { |
| if (!(fhdrp->fh_flags & FILEHDR_FLAGS_CHECKSUM)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "corrupt file header\n")); |
| return RV_CORRUPT; |
| } |
| if (!is_checksum_valid(fhdrp, FILEHDR_SZ)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "bad file header checksum\n")); |
| return RV_CORRUPT; |
| } |
| } |
| |
| return RV_OK; |
| } |
| |
| /* ARGSUSED */ |
| static rv_t |
| read_extenthdr(drive_t *drivep, extenthdr_t *ehdrp, bool_t ehcs) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| /* REFERENCED */ |
| int nread; |
| int rval; |
| extenthdr_t tmpeh; |
| |
| nread = read_buf((char *)&tmpeh, |
| sizeof(*ehdrp), |
| (void *)drivep, |
| (rfp_t)dop->do_read, |
| (rrbfp_t)dop->do_return_read_buf, |
| &rval); |
| xlate_extenthdr(&tmpeh, ehdrp, 1); |
| |
| switch(rval) { |
| case 0: |
| break; |
| case DRIVE_ERROR_EOD: |
| case DRIVE_ERROR_EOF: |
| case DRIVE_ERROR_EOM: |
| case DRIVE_ERROR_MEDIA: |
| return RV_EOD; |
| case DRIVE_ERROR_CORRUPTION: |
| return RV_CORRUPT; |
| case DRIVE_ERROR_DEVICE: |
| return RV_DRIVE; |
| case DRIVE_ERROR_CORE: |
| default: |
| return RV_CORE; |
| } |
| assert((size_t)nread == sizeof(*ehdrp)); |
| |
| mlog(MLOG_NITTY, |
| "read extent hdr size %lld offset %lld type %d flags %08x\n", |
| ehdrp->eh_sz, |
| ehdrp->eh_offset, |
| ehdrp->eh_type, |
| ehdrp->eh_flags); |
| |
| if (ehcs) { |
| if (!(ehdrp->eh_flags & EXTENTHDR_FLAGS_CHECKSUM)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "corrupt extent header\n")); |
| return RV_CORRUPT; |
| } |
| if (!is_checksum_valid(ehdrp, EXTENTHDR_SZ)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "bad extent header checksum\n")); |
| return RV_CORRUPT; |
| } |
| } |
| |
| return RV_OK; |
| } |
| |
| /* ARGSUSED */ |
| static rv_t |
| read_dirent(drive_t *drivep, |
| direnthdr_t *dhdrp, |
| size_t direntbufsz, |
| bool_t dhcs) |
| { |
| global_hdr_t *grhdrp = drivep->d_greadhdrp; |
| drive_ops_t *dop = drivep->d_opsp; |
| /* REFERENCED */ |
| int nread; |
| int rval; |
| direnthdr_t tmpdh; |
| char *namep; // beginning of name following the direnthdr_t |
| |
| assert(sizeof(direnthdr_t) == DIRENTHDR_SZ); |
| assert(sizeof(direnthdr_v1_t) == DIRENTHDR_SZ); |
| |
| /* read the head of the dirent |
| */ |
| nread = read_buf((char *)&tmpdh, |
| DIRENTHDR_SZ, |
| (void *)drivep, |
| (rfp_t)dop->do_read, |
| (rrbfp_t) |
| dop->do_return_read_buf, |
| &rval); |
| switch(rval) { |
| case 0: |
| break; |
| case DRIVE_ERROR_EOD: |
| case DRIVE_ERROR_EOF: |
| case DRIVE_ERROR_EOM: |
| case DRIVE_ERROR_MEDIA: |
| return RV_EOD; |
| case DRIVE_ERROR_CORRUPTION: |
| return RV_CORRUPT; |
| case DRIVE_ERROR_DEVICE: |
| return RV_DRIVE; |
| case DRIVE_ERROR_CORE: |
| default: |
| return RV_CORE; |
| } |
| assert((size_t)nread == DIRENTHDR_SZ); |
| |
| if (grhdrp->gh_version >= GLOBAL_HDR_VERSION_3) { |
| xlate_direnthdr(&tmpdh, dhdrp, 1); |
| namep = dhdrp->dh_name + sizeof(dhdrp->dh_name); |
| |
| if (dhcs && !is_checksum_valid(dhdrp, DIRENTHDR_SZ)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "bad directory entry header checksum\n")); |
| return RV_CORRUPT; |
| } |
| } else { |
| direnthdr_v1_t dhdr_v1; |
| xlate_direnthdr_v1((direnthdr_v1_t *)&tmpdh, &dhdr_v1, 1); |
| dhdrp->dh_ino = dhdr_v1.dh_ino; |
| dhdrp->dh_gen = BIGGEN2GEN(dhdr_v1.dh_gen); |
| dhdrp->dh_checksum = dhdr_v1.dh_checksum; |
| dhdrp->dh_sz = dhdr_v1.dh_sz; |
| memcpy(dhdrp->dh_name, dhdr_v1.dh_name, sizeof(dhdr_v1.dh_name)); |
| namep = dhdrp->dh_name + sizeof(dhdr_v1.dh_name); |
| |
| if (dhcs && !is_checksum_valid(&dhdr_v1, DIRENTHDR_SZ)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "bad directory entry header checksum\n")); |
| return RV_CORRUPT; |
| } |
| } |
| |
| mlog(MLOG_NITTY, |
| "read dirent hdr ino %llu gen %u size %u\n", |
| dhdrp->dh_ino, |
| (size_t)dhdrp->dh_gen, |
| (size_t)dhdrp->dh_sz); |
| |
| if (dhdrp->dh_sz == 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "corrupt directory entry header\n")); |
| return RV_CORRUPT; |
| } |
| |
| /* if null, return |
| */ |
| if (dhdrp->dh_ino == 0) { |
| assert((size_t)dhdrp->dh_sz == sizeof(direnthdr_t)); |
| return RV_OK; |
| } |
| |
| /* read the remainder of the dirent. |
| */ |
| assert((size_t)dhdrp->dh_sz <= direntbufsz); |
| assert((size_t)dhdrp->dh_sz >= sizeof(direnthdr_t)); |
| assert(!((size_t)dhdrp->dh_sz & (DIRENTHDR_ALIGN - 1))); |
| if ((size_t)dhdrp->dh_sz > sizeof(direnthdr_t)) { |
| size_t remsz = (size_t)dhdrp->dh_sz - sizeof(direnthdr_t); |
| nread = read_buf(namep, |
| remsz, |
| (void *)drivep, |
| (rfp_t)dop->do_read, |
| (rrbfp_t) |
| dop->do_return_read_buf, |
| &rval); |
| switch(rval) { |
| case 0: |
| break; |
| case DRIVE_ERROR_EOD: |
| case DRIVE_ERROR_EOF: |
| case DRIVE_ERROR_EOM: |
| case DRIVE_ERROR_MEDIA: |
| return RV_EOD; |
| case DRIVE_ERROR_CORRUPTION: |
| return RV_CORRUPT; |
| case DRIVE_ERROR_DEVICE: |
| return RV_DRIVE; |
| case DRIVE_ERROR_CORE: |
| default: |
| return RV_CORE; |
| } |
| assert((size_t) nread == remsz); |
| } |
| |
| return RV_OK; |
| } |
| |
| /* ARGSUSED */ |
| static rv_t |
| read_extattrhdr(drive_t *drivep, extattrhdr_t *ahdrp, bool_t ahcs) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| /* REFERENCED */ |
| int nread; |
| int rval; |
| extattrhdr_t tmpah; |
| |
| nread = read_buf((char *)&tmpah, |
| sizeof(*ahdrp), |
| (void *)drivep, |
| (rfp_t)dop->do_read, |
| (rrbfp_t)dop->do_return_read_buf, |
| &rval); |
| xlate_extattrhdr(&tmpah, ahdrp, 1); |
| |
| switch(rval) { |
| case 0: |
| break; |
| case DRIVE_ERROR_EOD: |
| case DRIVE_ERROR_EOF: |
| case DRIVE_ERROR_EOM: |
| case DRIVE_ERROR_MEDIA: |
| return RV_EOD; |
| case DRIVE_ERROR_CORRUPTION: |
| return RV_CORRUPT; |
| case DRIVE_ERROR_DEVICE: |
| return RV_DRIVE; |
| case DRIVE_ERROR_CORE: |
| default: |
| return RV_CORE; |
| } |
| assert((size_t)nread == sizeof(*ahdrp)); |
| |
| mlog(MLOG_NITTY, |
| "read extattr hdr sz %u valoff %u flags 0x%x valsz %u cs 0x%x\n", |
| ahdrp->ah_sz, |
| (uint)ahdrp->ah_valoff, |
| (uint)ahdrp->ah_flags, |
| ahdrp->ah_valsz, |
| ahdrp->ah_checksum); |
| |
| if (ahcs) { |
| if (ahdrp->ah_flags & EXTATTRHDR_FLAGS_CHECKSUM) { |
| if (!is_checksum_valid(ahdrp, EXTATTRHDR_SZ)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "bad extattr header checksum\n")); |
| return RV_CORRUPT; |
| } |
| } else if (ahdrp->ah_flags & EXTATTRHDR_FLAGS_OLD_CHECKSUM) { |
| /* possibly a corrupt header, but most likely an old |
| * header, which cannot be verified due to a bug in how |
| * its checksum was calculated. |
| */ |
| static bool_t warned = BOOL_FALSE; |
| if (!warned) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "ignoring old-style extattr " |
| "header checksums\n")); |
| warned = BOOL_TRUE; |
| } |
| } else { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "corrupt extattr header\n")); |
| return RV_CORRUPT; |
| } |
| } |
| |
| return RV_OK; |
| } |
| |
| static rv_t |
| discard_padding(size_t sz, drive_t *drivep) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| /* REFERENCED */ |
| int nread; |
| int rval; |
| |
| nread = read_buf(0, |
| sz, |
| (void *)drivep, |
| (rfp_t)dop->do_read, |
| (rrbfp_t)dop->do_return_read_buf, |
| &rval); |
| switch(rval) { |
| case 0: |
| assert((size_t)nread == sz); |
| return RV_OK; |
| case DRIVE_ERROR_EOF: |
| case DRIVE_ERROR_EOD: |
| case DRIVE_ERROR_EOM: |
| case DRIVE_ERROR_MEDIA: |
| return RV_EOD; |
| case DRIVE_ERROR_CORRUPTION: |
| return RV_CORRUPT; |
| case DRIVE_ERROR_DEVICE: |
| return RV_DRIVE; |
| case DRIVE_ERROR_CORE: |
| default: |
| return RV_CORE; |
| } |
| } |
| |
| static rv_t |
| restore_extent(filehdr_t *fhdrp, |
| extenthdr_t *ehdrp, |
| int fd, |
| char *path, |
| drive_t *drivep, |
| off64_t *bytesreadp) |
| { |
| bstat_t *bstatp = &fhdrp->fh_stat; |
| drive_ops_t *dop = drivep->d_opsp; |
| off64_t off = ehdrp->eh_offset; |
| off64_t sz = ehdrp->eh_sz; |
| off64_t new_off; |
| |
| *bytesreadp = 0; |
| |
| if (fd != -1) { |
| assert(path); |
| /* seek to the beginning of the extent. |
| * must be on a basic fs blksz boundary. |
| */ |
| assert((off & (off64_t)(BBSIZE - 1)) == 0); |
| new_off = lseek64(fd, off, SEEK_SET); |
| if (new_off < 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "attempt to seek %s to %lld failed: %s: " |
| "not restoring extent off %lld sz %lld\n"), |
| path, |
| off, |
| strerror(errno), |
| off, |
| sz); |
| fd = -1; |
| new_off = off; |
| } |
| assert(new_off == off); |
| } |
| |
| /* move from media to fs. |
| */ |
| while (sz) { |
| char *bufp; |
| size_t req_bufsz; /* requested bufsz */ |
| size_t sup_bufsz; /* supplied bufsz */ |
| int nwritten; |
| int rval; |
| size_t ntowrite; |
| |
| req_bufsz = (size_t)min((off64_t)INTGENMAX, sz); |
| bufp = (*dop->do_read)(drivep, req_bufsz, &sup_bufsz, &rval); |
| if (rval) { |
| rv_t rv; |
| char *reasonstr; |
| switch(rval) { |
| case DRIVE_ERROR_EOF: |
| rv = RV_EOD; |
| reasonstr = _("end of media file"); |
| break; |
| case DRIVE_ERROR_EOD: |
| rv = RV_EOD; |
| reasonstr = _("end of recorded data"); |
| break; |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOD; |
| reasonstr = _("end of media"); |
| break; |
| case DRIVE_ERROR_MEDIA: |
| rv = RV_EOD; |
| reasonstr = _("media error or no media"); |
| break; |
| case DRIVE_ERROR_CORRUPTION: |
| rv = RV_CORRUPT; |
| reasonstr = _("end of media file"); |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| reasonstr = _("end of media file"); |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| reasonstr = _("dumping core"); |
| break; |
| } |
| mlog(MLOG_NORMAL, _( |
| "attempt to read %u bytes failed: %s\n"), |
| req_bufsz, |
| reasonstr); |
| return rv; |
| } |
| if (off >= bstatp->bs_size) { |
| assert(off == bstatp->bs_size); |
| ntowrite = 0; |
| } else if ((off64_t)sup_bufsz > bstatp->bs_size - off) { |
| ntowrite = (size_t)(bstatp->bs_size - off); |
| } else { |
| ntowrite = sup_bufsz; |
| } |
| assert(ntowrite <= (size_t)INTGENMAX); |
| if (ntowrite > 0) { |
| *bytesreadp += (off64_t)ntowrite; |
| if (fd != -1) { |
| size_t tries; |
| size_t remaining; |
| int rval; |
| off64_t tmp_off; |
| |
| rval = 0; /* for lint */ |
| for (nwritten = 0, |
| tries = 0, |
| remaining = ntowrite, |
| tmp_off = off |
| ; |
| nwritten < (int)ntowrite |
| && |
| tries < WRITE_TRIES_MAX |
| ; |
| nwritten += rval, |
| tries++, |
| remaining -= (size_t)rval, |
| tmp_off += (off64_t)rval) { |
| int rttrunc = 0; |
| int trycnt = 0; |
| assert(remaining |
| <= |
| (size_t)INTGENMAX); |
| /* |
| * Do the write. Due to delayed allocation |
| * it's possible to receive false ENOSPC |
| * errors when the filesystem is nearly |
| * full. XFS kernel code tries to avoid |
| * this, but cannot always do so. Catch |
| * ENOSPC and mimic the kernel behavior |
| * by trying to flush the current file |
| * first, then trying a system wide sync |
| * if ENOSPC still occurs. |
| */ |
| for (trycnt = 0; trycnt < 3; trycnt++) { |
| rval = write(fd, bufp, remaining); |
| if (rval >= 0 || errno != ENOSPC) |
| break; |
| |
| (trycnt == 0) ? |
| fdatasync(fd) : sync(); |
| } |
| if (rval < 0) { |
| nwritten = rval; |
| break; |
| } |
| assert((size_t)rval <= remaining); |
| if (rval < remaining) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, |
| _("attempt to " |
| "write %u bytes to %s at " |
| "offset %lld failed: " |
| "only %d bytes written\n"), |
| remaining, |
| path, |
| tmp_off, |
| rval); |
| } |
| if (rttrunc) { |
| /* truncate and re-set rval */ |
| if (rval == remaining) |
| rval = rttrunc; |
| ftruncate(fd, bstatp->bs_size); |
| } |
| } |
| } else { |
| nwritten = (int)ntowrite; |
| } |
| } else { |
| nwritten = 0; |
| } |
| (*dop->do_return_read_buf)(drivep, bufp, sup_bufsz); |
| if ((size_t)nwritten != ntowrite) { |
| if (nwritten < 0) { |
| mlog(MLOG_NORMAL, _( |
| "attempt to write %u bytes to %s " |
| "at offset %lld failed: %s\n"), |
| ntowrite, |
| path, |
| off, |
| strerror(errno)); |
| } else { |
| assert((size_t)nwritten < ntowrite); |
| mlog(MLOG_NORMAL, _( |
| "attempt to write %u bytes to %s at " |
| "offset %lld failed: only %d bytes " |
| "written\n"), |
| ntowrite, |
| path, |
| off, |
| nwritten); |
| } |
| /* stop attempting to write, but complete reads |
| */ |
| fd = -1; |
| assert(ntowrite <= (size_t)INTGENMAX); |
| nwritten = (int)ntowrite; |
| } |
| sz -= (off64_t)sup_bufsz; |
| off += (off64_t)nwritten; |
| } |
| |
| return RV_OK; |
| } |
| |
| static char *extattrbufp = 0; /* ptr to start of all the extattr buffers */ |
| static size_t extattrbufsz = 0; /* size of each extattr buffer */ |
| |
| static bool_t |
| extattr_init(size_t drivecnt) |
| { |
| assert(!extattrbufp); |
| extattrbufsz = EXTATTRHDR_SZ /* dump hdr */ |
| + |
| NAME_MAX /* attribute name */ |
| + |
| 1 /* NULL term. of name */ |
| + |
| ATTR_MAX_VALUELEN; /* attribute value */ |
| extattrbufsz = roundup(extattrbufsz, EXTATTRHDR_ALIGN); |
| |
| extattrbufp = memalign(EXTATTRHDR_ALIGN, extattrbufsz * drivecnt); |
| if (extattrbufp == NULL) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "Failed to allocate extended attribute buffer\n")); |
| return BOOL_FALSE; |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| static char * |
| get_extattrbuf(ix_t which) |
| { |
| return extattrbufp + (extattrbufsz * which); |
| } |
| |
| |
| struct extattr_cb_ctx { |
| extattrhdr_t *ecb_ahdrp; |
| }; |
| |
| typedef struct extattr_cb_ctx extattr_cb_ctx_t; |
| |
| static rv_t |
| restore_extattr(drive_t *drivep, |
| filehdr_t *fhdrp, |
| char *path, |
| bool_t ahcs, |
| bool_t isdirpr, |
| bool_t onlydoreadpr, |
| dah_t dah) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| extattrhdr_t *ahdrp = (extattrhdr_t *)get_extattrbuf(drivep->d_index); |
| stream_context_t *strctxp = (stream_context_t *)drivep->d_strmcontextp; |
| bstat_t *bstatp = &fhdrp->fh_stat; |
| bool_t isfilerestored = BOOL_FALSE; |
| |
| assert(extattrbufp); |
| |
| if (!isdirpr) |
| isfilerestored = partial_check(bstatp->bs_ino, bstatp->bs_size); |
| |
| /* peel off extattrs until null hdr hit |
| */ |
| for (;;) { |
| size_t recsz; |
| /* REFERENCED */ |
| int nread; |
| int rval; |
| rv_t rv; |
| |
| rv = read_extattrhdr(drivep, ahdrp, ahcs); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| |
| if (ahdrp->ah_flags & EXTATTRHDR_FLAGS_NULL) { |
| return RV_OK; |
| } |
| |
| recsz = (size_t)ahdrp->ah_sz; |
| assert(recsz <= extattrbufsz); |
| assert(recsz >= EXTATTRHDR_SZ); |
| nread = read_buf((char *)&ahdrp[1], |
| recsz - EXTATTRHDR_SZ, |
| (void *)drivep, |
| (rfp_t)dop->do_read, |
| (rrbfp_t)dop->do_return_read_buf, |
| &rval); |
| switch(rval) { |
| case 0: |
| break; |
| case DRIVE_ERROR_EOD: |
| case DRIVE_ERROR_EOF: |
| case DRIVE_ERROR_EOM: |
| case DRIVE_ERROR_MEDIA: |
| return RV_EOD; |
| case DRIVE_ERROR_CORRUPTION: |
| return RV_CORRUPT; |
| case DRIVE_ERROR_DEVICE: |
| return RV_DRIVE; |
| case DRIVE_ERROR_CORE: |
| default: |
| return RV_CORE; |
| } |
| assert(nread == (int)(recsz - EXTATTRHDR_SZ)); |
| |
| if (!persp->a.restoreextattrpr) { |
| continue; |
| } |
| |
| if (onlydoreadpr || tranp->t_toconlypr) |
| continue; |
| |
| /* NOTE: In the cases below, if we get errors then we issue warnings |
| * but we do not stop the restoration. |
| * We can still restore the file possibly without the |
| * extended attributes. |
| */ |
| if (isdirpr) { |
| assert(!path); |
| if (dah != DAH_NULL) { |
| dirattr_addextattr(dah, ahdrp); |
| } |
| } else if (isfilerestored && path[0] != '\0') { |
| setextattr(path, ahdrp); |
| } |
| } |
| /* NOTREACHED */ |
| } |
| |
| static bool_t |
| restore_dir_extattr_cb(char *path, dah_t dah) |
| { |
| /* |
| * directory extattr's are built during the directory phase |
| * by 1 thread so we only need one extattr buffer |
| * -> we pick the 0th one |
| */ |
| extattrhdr_t *ahdrp = (extattrhdr_t *)get_extattrbuf(0); |
| bool_t ok; |
| |
| /* ask the dirattr abstraction to call me back for each |
| * extended dirattr associated with this dah. |
| */ |
| ok = dirattr_cb_extattr(dah, |
| restore_dir_extattr_cb_cb, |
| ahdrp, |
| (void *)path); |
| return ok; |
| } |
| |
| static bool_t |
| restore_dir_extattr_cb_cb(extattrhdr_t *ahdrp, void *ctxp) |
| { |
| char *path = (char *)ctxp; |
| |
| setextattr(path, ahdrp); |
| |
| return BOOL_TRUE; |
| } |
| |
| static void |
| setextattr(char *path, extattrhdr_t *ahdrp) |
| { |
| static char dmiattr[] = "SGI_DMI_"; |
| bool_t isrootpr = ahdrp->ah_flags & EXTATTRHDR_FLAGS_ROOT; |
| bool_t issecurepr = ahdrp->ah_flags & EXTATTRHDR_FLAGS_SECURE; |
| bool_t isdmpr; |
| int attr_namespace; |
| int rval; |
| |
| isdmpr = (isrootpr && |
| !strncmp((char *)(&ahdrp[1]), dmiattr, sizeof(dmiattr)-1)); |
| |
| /* If restoreextattrpr not set, then we are here because -D was |
| * specified. So return unless it looks like a root DMAPI attribute. |
| */ |
| if (!persp->a.restoreextattrpr && !isdmpr) |
| return; |
| |
| if (isrootpr) { |
| attr_namespace = ATTR_ROOT; |
| } else if (issecurepr) { |
| attr_namespace = ATTR_SECURE; |
| } else { |
| attr_namespace = 0; |
| } |
| |
| rval = attr_set(path, |
| (char *)(&ahdrp[1]), |
| ((char *)ahdrp) + (u_long_t)ahdrp->ah_valoff, |
| (int)ahdrp->ah_valsz, |
| attr_namespace | ATTR_DONTFOLLOW); |
| if (rval) { |
| char *namespace; |
| if (isrootpr) { |
| namespace = _("root"); |
| } else if (issecurepr) { |
| namespace = _("secure"); |
| } else { |
| namespace = _("non-root"); |
| } |
| |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "unable to set %s extended attribute for %s: " |
| "%s (%d)\n"), |
| namespace, |
| path, |
| strerror(errno), |
| errno); |
| } |
| } |
| |
| #ifdef DEBUGPARTIALS |
| /* |
| * Debug code to view the partials in the partial register |
| */ |
| void |
| dump_partials(void) |
| { |
| partial_rest_t *isptr = NULL; |
| bytespan_t *bsptr = NULL; |
| int i; |
| |
| pi_lock(); |
| printf("\npartial_reg: count=%d\n", (int)persp->a.parrestcnt); |
| if (persp->a.parrestcnt > 0) { |
| for (i=0; i < partialmax; i++) { |
| if (persp->a.parrest[i].is_ino > 0) { |
| int j; |
| |
| isptr = &persp->a.parrest[i]; |
| printf("\tino=%llu ", |
| (unsigned long long)isptr->is_ino); |
| for (j=0, bsptr=isptr->is_bs; |
| j < drivecnt; |
| j++, bsptr++) |
| { |
| if (bsptr->endoffset > 0) { |
| printf("%d:%lld-%lld ", |
| j, (long long)bsptr->offset, |
| (long long)bsptr->endoffset); |
| } |
| } |
| printf("\n"); |
| } |
| } |
| } |
| printf("\n"); |
| pi_unlock(); |
| } |
| |
| |
| /* There can only be at most 2 partials for a given stream. |
| * An unfinished one from a split and the current one from |
| * a multiple group extent or another split. |
| * If there are more than 2, then there is an internal error. |
| */ |
| void |
| check_valid_partials(void) |
| { |
| int *num_partials; /* array for sum of partials for a given drive */ |
| partial_rest_t *isptr = NULL; |
| bytespan_t *bsptr = NULL; |
| int i; |
| |
| /* zero the sums for each stream */ |
| num_partials = calloc(drivecnt, sizeof(int)); |
| if (!num_partials) { |
| perror("num_partials array allocation"); |
| return; |
| } |
| |
| pi_lock(); |
| if (persp->a.parrestcnt > 0) { |
| for (i=0; i < partialmax; i++) { |
| if (persp->a.parrest[i].is_ino > 0) { |
| int j; |
| |
| isptr = &persp->a.parrest[i]; |
| for (j=0, bsptr=isptr->is_bs; |
| j < drivecnt; |
| j++, bsptr++) |
| { |
| if (bsptr->endoffset > 0) { |
| num_partials[j]++; |
| if (num_partials[j] > 2) { |
| pi_unlock(); |
| mlog(MLOG_NORMAL | MLOG_WARNING, |
| "partial_reg: Too many partials (>2) for drive: %d\n", j); |
| dump_partials(); |
| exit(EXIT_ERROR); |
| } |
| } |
| } |
| } |
| } |
| } |
| pi_unlock(); |
| free(num_partials); |
| } |
| #endif |
| |
| /* partial_reg - Registers files that are only partially restored by |
| * a dump stream into the persistent state. |
| * |
| * This is done because DMAPI extended attributes must not be set until |
| * the entire file has been restored in order to co-ordinate with the |
| * Data Migration Facility (DMF) daemons. Since extended attributes are |
| * recorded with each extent group in the dump, this registry is used to |
| * make sure only the final dump stream applies the extended attributes. |
| * |
| * Likewise, certain extended inode flags (e.g. XFS_XFLAG_IMMUTABLE) |
| * should only be set after all data for a file has been restored. |
| */ |
| static void |
| partial_reg(ix_t d_index, |
| xfs_ino_t ino, |
| off64_t fsize, |
| off64_t offset, |
| off64_t sz) |
| { |
| off64_t endoffset; |
| partial_rest_t *isptr = NULL; |
| bytespan_t *bsptr = NULL; |
| int i; |
| |
| mlog(MLOG_NITTY, "partial_reg: d_index = %d, ino = %llu, " |
| "fsize = %lld, offset = %lld, sz = %lld\n", |
| d_index, ino, fsize, offset, sz); |
| |
| endoffset = offset + sz; |
| |
| if (partialmax == 0) |
| return; |
| |
| pi_lock(); |
| |
| /* Search for a matching inode. Gaps can exist so we must search |
| * all entries. |
| */ |
| for (i=0; i < partialmax; i++) { |
| if (persp->a.parrest[i].is_ino == ino) { |
| isptr = &persp->a.parrest[i]; |
| break; |
| } |
| } |
| |
| /* If not found, find a free one, fill it in and return */ |
| if (!isptr) { |
| mlog(MLOG_NITTY | MLOG_NOLOCK, |
| "partial_reg: no entry found for %llu\n", ino); |
| /* find a free one */ |
| for (i=0; i < partialmax; i++) { |
| if (persp->a.parrest[i].is_ino == 0) { |
| int j; |
| |
| isptr = &persp->a.parrest[i]; |
| isptr->is_ino = ino; |
| persp->a.parrestcnt++; |
| |
| /* Clear all endoffsets (this value is |
| * used to decide if an entry is used or |
| * not |
| */ |
| for (j=0, bsptr=isptr->is_bs; |
| j < drivecnt; j++, bsptr++) { |
| bsptr->endoffset = 0; |
| } |
| |
| goto found; |
| } |
| } |
| |
| /* Should never get here. */ |
| pi_unlock(); |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "partial_reg: Out of records. Extend attrs applied early.\n")); |
| #ifdef DEBUGPARTIALS |
| dump_partials(); |
| #endif |
| return; |
| } |
| |
| found: |
| /* Update this drive's entry */ |
| bsptr = &isptr->is_bs[d_index]; |
| if (bsptr->endoffset == 0) { |
| /* no existing entry for this drive, fill in the values */ |
| bsptr->offset = offset; |
| bsptr->endoffset = endoffset; |
| mlog(MLOG_NITTY | MLOG_NOLOCK, |
| "partial_reg: update entry [%d]: " |
| "<off = %lld, end = %lld>\n", |
| d_index, offset, endoffset); |
| } else { |
| bool_t ret; |
| |
| /* entry exists for this drive, just extend the endoffset, the |
| * records will be sequential for any given drive. |
| */ |
| bsptr->endoffset = endoffset; |
| ret = partial_check2(isptr, fsize); |
| mlog(MLOG_NITTY | MLOG_NOLOCK, |
| "partial_reg: extend entry [%d]: <end = %lld>\n", |
| d_index, endoffset); |
| mlog(MLOG_NITTY | MLOG_NOLOCK, |
| "partial_reg: partial_check returns: %d\n", ret); |
| } |
| |
| pi_unlock(); |
| |
| #ifdef DEBUGPARTIALS |
| check_valid_partials(); |
| dump_partials(); |
| #endif |
| } |
| |
| |
| /* Checks the registry of files that are only partially restored by |
| * any given dump stream to see if the remainder of the file has |
| * been restored by another dump stream. |
| */ |
| static bool_t |
| partial_check (xfs_ino_t ino, off64_t fsize) |
| { |
| partial_rest_t *isptr = NULL; |
| bool_t ret; |
| int i; |
| |
| if (partialmax == 0) |
| return BOOL_TRUE; |
| |
| pi_lock(); |
| |
| /* Check if no files are listed in the sync area */ |
| if (persp->a.parrestcnt == 0) { |
| pi_unlock(); |
| return BOOL_TRUE; |
| } |
| |
| /* Search for the inode. Gaps can exist so we must search |
| * all entries. |
| */ |
| for (i=0; i < partialmax; i++) { |
| if (persp->a.parrest[i].is_ino == ino) { |
| isptr = &persp->a.parrest[i]; |
| break; |
| } |
| } |
| |
| /* If not found, return okay */ |
| if (!isptr) { |
| pi_unlock(); |
| return BOOL_TRUE; |
| } |
| |
| ret = partial_check2(isptr, fsize); |
| |
| pi_unlock(); |
| |
| #ifdef DEBUGPARTIALS |
| check_valid_partials(); |
| dump_partials(); |
| #endif |
| |
| return ret; |
| } |
| |
| /* |
| * Checks the given parrest entry to see if the file has |
| * been completely restored. |
| * Always invoked with the persistent inventory locked (pi_lock) |
| */ |
| static bool_t |
| partial_check2(partial_rest_t *isptr, off64_t fsize) |
| { |
| bytespan_t *bsptr = NULL; |
| off64_t curoffset = 0; |
| int i; |
| |
| gapsearch: |
| /* Search the entire set of bytespan records to see if the next |
| * span has been restored. Bytespans are not necessarily in order |
| * so the search is repeated from the start each time. |
| */ |
| for (i=0, bsptr=isptr->is_bs; i < drivecnt; i++, bsptr++) { |
| if (bsptr->endoffset > 0 && |
| bsptr->offset <= curoffset && |
| bsptr->endoffset > curoffset) |
| { |
| curoffset = bsptr->endoffset; |
| goto gapsearch; |
| } |
| } |
| |
| /* Check if all bytes are accounted for. */ |
| if (curoffset >= fsize) { |
| isptr->is_ino = 0; /* clear the entry */ |
| persp->a.parrestcnt--; |
| return BOOL_TRUE; |
| } else { |
| return BOOL_FALSE; |
| } |
| } |
| |
| static char * |
| ehdr_typestr(int32_t type) |
| { |
| switch (type) { |
| case EXTENTHDR_TYPE_LAST: |
| return "LAST"; |
| case EXTENTHDR_TYPE_ALIGN: |
| return "ALIGN"; |
| case EXTENTHDR_TYPE_DATA: |
| return "DATA"; |
| case EXTENTHDR_TYPE_HOLE: |
| return "HOLE"; |
| default: |
| return "?"; |
| } |
| } |
| |
| /* ARGSUSED */ |
| bool_t |
| content_overwrite_ok(char *path, |
| int32_t ctime, |
| int32_t mtime, |
| char **reasonstrp, |
| bool_t *exists) |
| { |
| struct stat statbuf; |
| |
| *exists = BOOL_TRUE; |
| |
| /* if file doesn't exist, allow |
| */ |
| |
| if (lstat(path, &statbuf)) { |
| *reasonstrp = 0; |
| if (errno == ENOENT) { |
| *exists = BOOL_FALSE; |
| } |
| return BOOL_TRUE; |
| } |
| |
| /* if overwrites absolutely inhibited, disallow |
| */ |
| if (persp->a.existpr) { |
| *reasonstrp = _("overwrites inhibited"); |
| return BOOL_FALSE; |
| } |
| |
| /* if newer time specified, compare |
| */ |
| if (persp->a.newerpr) { |
| if ((time32_t)ctime < persp->a.newertime) { |
| *reasonstrp = _("too old"); |
| return BOOL_FALSE; |
| } |
| } |
| |
| /* don't overwrite changed files |
| */ |
| if (persp->a.changepr) { |
| if (statbuf.st_ctime >= (time32_t)ctime) { |
| *reasonstrp = _("existing version is newer"); |
| return BOOL_FALSE; |
| } |
| } |
| |
| *reasonstrp = 0; |
| return BOOL_TRUE; |
| } |
| |
| |
| static void |
| set_mcflag(ix_t thrdix) |
| { |
| lock(); |
| mcflag[thrdix] = BOOL_TRUE; |
| content_media_change_needed = BOOL_TRUE; |
| unlock(); |
| } |
| |
| static void |
| clr_mcflag(ix_t thrdix) |
| { |
| lock(); |
| mcflag[thrdix] = BOOL_FALSE; |
| for (thrdix = 0; thrdix < drivecnt; thrdix++) { |
| if (mcflag[thrdix]) { |
| unlock(); |
| return; |
| } |
| } |
| content_media_change_needed = BOOL_FALSE; |
| unlock(); |
| } |
| |
| /* debug functions ***********************************************************/ |
| |
| static void |
| pi_show(char *introstring) |
| { |
| char strbuf[100]; |
| /* REFERENCED */ |
| int strbuflen; |
| fold_t fold; |
| |
| if (mlog_level_ss[MLOG_SS_MEDIA] < MLOG_NITTY + 1) { |
| return; |
| } |
| |
| mlog_lock(); |
| |
| strbuflen = sprintf(strbuf, |
| "persistent inventory media file tree%s", |
| introstring); |
| assert((size_t)strbuflen < sizeof(strbuf)); |
| fold_init(fold, strbuf, ':'); |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| "\n%s\n\n", |
| fold); |
| |
| pi_show_nomloglock(); |
| |
| fold_init(fold, "end persistent inventory display", '.'); |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| "\n%s\n\n", |
| fold); |
| |
| mlog_unlock(); |
| } |
| |
| static void |
| pi_show_nomloglock(void) |
| { |
| dh_t strmh; |
| int strmix; |
| |
| |
| /* no point in proceeding if pi not begun |
| */ |
| if (persp->s.strmheadh == DH_NULL) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, _( |
| "session inventory unknown\n")); |
| return; |
| } |
| |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, _( |
| "session inventory display\n")); |
| |
| /* iterate over all streams |
| */ |
| for (strmh = persp->s.strmheadh, strmix = 0 |
| ; |
| strmh != DH_NULL |
| ; |
| strmh = DH2S(strmh)->s_nexth, strmix++) { |
| dh_t objh; |
| int objix; |
| |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _("\nmedia stream %u:\n"), |
| strmix); |
| if (DH2S(strmh)->s_cldh == DH_NULL) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _("\n media objects not yet identified\n")); |
| continue; |
| } |
| /* iterate over all objects |
| */ |
| for (objh = DH2S(strmh)->s_cldh, objix = 0 |
| ; |
| objh != DH_NULL |
| ; |
| objh = DH2O(objh)->o_nexth, objix++) { |
| dh_t fileh; |
| ix_t fileix; |
| |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _("\n media object %u:\n\n"), |
| objix); |
| if (DH2O(objh)->o_idlabvalpr) { |
| if (strlen(DH2O(objh)->o_lab)) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" label: ")); |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| "\"%s\"\n", |
| DH2O(objh)->o_lab); |
| } else { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" label is blank\n")); |
| } |
| if (!uuid_is_null(DH2O(objh)->o_id)) { |
| char media_string_uuid[UUID_STR_LEN + 1]; |
| uuid_unparse(DH2O(objh)->o_id, |
| media_string_uuid); |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" id: %s\n"), |
| media_string_uuid); |
| } |
| } else { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" label not identified\n")); |
| } |
| if (DH2O(objh)->o_fmfmixvalpr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" index within object " |
| "of first media file: %u\n"), |
| DH2O(objh)->o_fmfmix); |
| } |
| if (DH2O(objh)->o_fmfsixvalpr) { |
| mlog(MLOG_DEBUG | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" index within stream " |
| "of first media file: %u\n"), |
| DH2O(objh)->o_fmfsix); |
| } |
| |
| if (DH2O(objh)->o_indrivepr) { |
| if (drivecnt > 1) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" now in drive %u\n"), |
| DH2O(objh)->o_indriveix); |
| } else { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" now in drive\n")); |
| } |
| } |
| |
| if (DH2O(objh)->o_cldh == DH_NULL) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" media files not yet " |
| "identified\n")); |
| continue; |
| } |
| |
| /* iterate over all files |
| */ |
| for (fileh = DH2O(objh)->o_cldh, fileix = 0 |
| ; |
| fileh != DH_NULL |
| ; |
| fileh = DH2F(fileh)->f_nexth, fileix++) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _("\n media file %u"), |
| fileix); |
| if (DH2O(objh)->o_fmfmixvalpr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| " (%u):\n", |
| DH2O(objh)->o_fmfmix + fileix); |
| } else { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| ":\n"); |
| } |
| if (DH2F(fileh)->f_szvalpr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" size: " |
| "%lld bytes\n"), |
| DH2F(fileh)->f_sz); |
| } |
| if (DH2F(fileh)->f_dirtriedpr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" used for directory " |
| "restoral\n")); |
| } |
| if (DH2F(fileh)->f_valpr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" first extent contained: " |
| "ino %llu off %lld\n"), |
| DH2F(fileh)->f_firstegrp.eg_ino, |
| DH2F(fileh)->f_firstegrp.eg_off); |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" next extent to restore: " |
| "ino %llu off %lld\n"), |
| DH2F(fileh)->f_curegrp.eg_ino, |
| DH2F(fileh)->f_curegrp.eg_off); |
| mlog(MLOG_DEBUG | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" rollback mark %lld\n"), |
| DH2F(fileh)->f_curmark); |
| } |
| if (DH2F(fileh)->f_nondirskippr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" contains no selected " |
| "non-directories\n")); |
| } |
| if (DH2F(fileh)->f_nondirdonepr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" non-directories done\n")); |
| } |
| if (DH2F(fileh)->f_flags & PF_INV) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" contains session " |
| "inventory\n")); |
| } |
| if (DH2F(fileh)->f_flags & PF_TERM) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" is stream terminator\n")); |
| } |
| if (DH2F(fileh)->f_underheadpr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _(" now reading\n")); |
| } |
| } |
| if (!DH2O(objh)->o_lmfknwnpr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _("\n may be additional " |
| "unidentified media files\n")); |
| } |
| } |
| if (!DH2S(strmh)->s_lastobjknwnpr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK | MLOG_MEDIA, |
| _("\n may be " |
| "additional unidentified media objects\n\n")); |
| } |
| } |
| } |
| |
| static int |
| egrpcmp(egrp_t *egrpap, egrp_t *egrpbp) |
| { |
| if (egrpap->eg_ino < egrpbp->eg_ino) { |
| return -1; |
| } else if (egrpap->eg_ino > egrpbp->eg_ino) { |
| return 1; |
| } else if (egrpap->eg_off < egrpbp->eg_off) { |
| return -1; |
| } else if (egrpap->eg_off > egrpbp->eg_off) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| static void |
| display_dump_label(bool_t lockpr, |
| int mllevel, |
| char *introstr, |
| global_hdr_t *grhdrp, |
| media_hdr_t *mrhdrp, |
| content_hdr_t *crhdrp, |
| content_inode_hdr_t *scrhdrp) |
| { |
| char dateline[28]; |
| char level_string[2]; |
| char dump_string_uuid[UUID_STR_LEN + 1]; |
| char media_string_uuid[UUID_STR_LEN + 1]; |
| char fs_string_uuid[UUID_STR_LEN + 1]; |
| |
| assert(scrhdrp->cih_level >= 0); |
| assert(scrhdrp->cih_level < 10); |
| level_string[0] = (char)('0' + (u_char_t)scrhdrp->cih_level); |
| level_string[1] = 0; |
| |
| uuid_unparse(grhdrp->gh_dumpid, dump_string_uuid); |
| uuid_unparse(mrhdrp->mh_mediaid, media_string_uuid); |
| uuid_unparse(crhdrp->ch_fsid, fs_string_uuid); |
| |
| if (lockpr) { |
| mlog_lock(); |
| } |
| |
| mlog(mllevel | MLOG_NOLOCK, |
| "%s", |
| introstr); |
| mlog(mllevel | MLOG_NOLOCK, |
| _("hostname: %s\n"), |
| grhdrp->gh_hostname); |
| mlog(mllevel | MLOG_NOLOCK, |
| _("mount point: %s\n"), |
| crhdrp->ch_mntpnt); |
| mlog(mllevel | MLOG_NOLOCK, |
| _("volume: %s\n"), |
| crhdrp->ch_fsdevice); |
| mlog(mllevel | MLOG_NOLOCK, |
| _("session time: %s"), |
| ctime32_r(&grhdrp->gh_timestamp, dateline)); |
| mlog(mllevel | MLOG_NOLOCK, |
| _("level: %s%s\n"), |
| level_string, |
| (scrhdrp->cih_dumpattr & CIH_DUMPATTR_RESUME) |
| ? |
| _(" resumed") |
| : |
| ""); |
| mlog(mllevel | MLOG_NOLOCK, |
| _("session label: ")); |
| mlog(mllevel | MLOG_NOLOCK | MLOG_BARE, |
| "\"%s\"\n", |
| grhdrp->gh_dumplabel); |
| mlog(mllevel | MLOG_NOLOCK, |
| _("media label: ")); |
| mlog(mllevel | MLOG_NOLOCK | MLOG_BARE, |
| "\"%s\"\n", |
| mrhdrp->mh_medialabel); |
| mlog(mllevel | MLOG_NOLOCK, |
| _("file system id: %s\n"), |
| fs_string_uuid); |
| mlog(mllevel | MLOG_NOLOCK, |
| _("session id: %s\n"), |
| dump_string_uuid); |
| mlog(mllevel | MLOG_NOLOCK, |
| _("media id: %s\n"), |
| media_string_uuid); |
| |
| if (lockpr) { |
| mlog_unlock(); |
| } |
| } |
| |
| static void |
| display_needed_objects(purp_t purp, |
| bag_t *bagp, |
| bool_t knownholespr, |
| bool_t maybeholespr) |
| { |
| if (bagp) { |
| ix_t ix; |
| bagiter_t iter; |
| bagobj_t *bagobjp; |
| if (purp == PURP_DIR) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, _( |
| "the following media objects " |
| "contain media files not yet tried " |
| "for directory hierarchy restoral:\n")); |
| } |
| if (purp == PURP_NONDIR) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, _( |
| "the following media objects " |
| "are needed:\n")); |
| } |
| bagiter_init(bagp, &iter); |
| bagobjp = 0; /* keep lint happy */ |
| ix = 0; |
| while (bagiter_next(&iter, |
| (void **)&bagobjp)) { |
| char uuidstr[UUID_STR_LEN + 1]; |
| uuid_unparse(bagobjp->id, uuidstr); |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| "\n"); |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| _("%2u. label: "), |
| ix); |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| "\"%s\"\n", |
| bagobjp->label); |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| _(" id: ")); |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| "\"%s\"\n", |
| uuidstr); |
| if (bagobjp->indrivepr) { |
| if (drivecnt > 1) { |
| mlog(MLOG_NORMAL |
| | |
| MLOG_BARE |
| | |
| MLOG_NOLOCK, |
| _(" now in drive %u\n"), |
| bagobjp->indriveix); |
| } else { |
| mlog(MLOG_NORMAL |
| | |
| MLOG_BARE |
| | |
| MLOG_NOLOCK, |
| _(" now in drive\n")); |
| } |
| } |
| ix++; |
| bagobjp = 0; /* keep lint happy */ |
| } |
| } |
| if (knownholespr) { |
| if (purp == PURP_DIR) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| bagp ? |
| _("\nthere are additional unidentified media " |
| "objects containing media files not yet tried " |
| "for directory hierarchy restoral:\n") |
| : |
| _("\nthere are unidentified media " |
| "objects containing media files not yet tried " |
| "for directory hierarchy restoral:\n")); |
| } |
| if (purp == PURP_NONDIR) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| bagp ? |
| _("\nthere are additional unidentified media " |
| "objects not yet fully restored\n") |
| : |
| _("\nthere are unidentified media objects " |
| "not yet fully restored\n")); |
| } |
| } else if (maybeholespr) { |
| if (purp == PURP_DIR) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| bagp ? |
| _("\nthere may be additional unidentified media " |
| "objects containing media files not yet tried " |
| "for directory hierarchy restoral:\n") |
| : |
| _("\nthere may be unidentified media " |
| "objects containing media files not yet tried " |
| "for directory hierarchy restoral:\n")); |
| |
| } |
| if (purp == PURP_NONDIR) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| bagp ? |
| _("\nthere may be additional unidentified media " |
| "objects not yet fully restored\n") |
| : |
| _("\there may be unidentified media " |
| "objects not yet fully restored\n")); |
| } |
| } |
| |
| if (!bagp && !knownholespr && !maybeholespr) { |
| mlog(MLOG_NORMAL | MLOG_BARE | MLOG_NOLOCK, |
| _("no additional media objects needed\n")); |
| } |
| } |
| |
| static int |
| quotafilecheck(char *type, char *dstdir, char *quotafile) |
| { |
| struct stat s; |
| char buf[MAXPATHLEN]; |
| |
| sprintf(buf, |
| "%s/%s", |
| dstdir, |
| quotafile); |
| |
| if (stat (buf, &s) >= 0 && S_ISREG(s.st_mode)) { |
| mlog(MLOG_NORMAL, _( |
| "%s quota information written to '%s'\n"), |
| type, |
| buf); |
| return 1; |
| } |
| |
| return 0; |
| } |