| /* |
| * Copyright (c) 2000-2004 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/stat.h> |
| #include <sys/prctl.h> |
| #include <time.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <signal.h> |
| #include <sys/statvfs.h> |
| #include <dirent.h> |
| #include <sys/ioctl.h> |
| #include <sys/quota.h> |
| #include <malloc.h> |
| #include <assert.h> |
| #include <string.h> |
| #include <uuid/uuid.h> |
| |
| #include <xfs/xfs.h> |
| #include <xfs/jdm.h> |
| #ifdef linux |
| #include <linux/limits.h> |
| #include <xfs/xqm.h> |
| #endif |
| |
| #include <attr/attributes.h> |
| |
| #include "config.h" |
| |
| #include "hsmapi.h" |
| |
| #include "exit.h" |
| #include "types.h" |
| #include "path.h" |
| #include "timeutil.h" |
| #include "util.h" |
| #include "lock.h" |
| #include "qlock.h" |
| #include "mlog.h" |
| #include "dlog.h" |
| #include "getopt.h" |
| #include "stream.h" |
| #include "cldmgr.h" |
| #include "global.h" |
| #include "drive.h" |
| #include "media.h" |
| #include "content.h" |
| #include "content_common.h" |
| #include "content_inode.h" |
| #include "fs.h" |
| #include "inomap.h" |
| #include "var.h" |
| #include "inventory.h" |
| #include "getdents.h" |
| #include "arch_xlate.h" |
| |
| /* max "unsigned long long int" |
| */ |
| #define ULONGLONG_MAX 18446744073709551615LLU |
| |
| /* legal range of dump levels |
| */ |
| #define LEVEL_DEFAULT 0 |
| #define LEVEL_MAX 9 |
| |
| /* ordinary files as big or bigger than this many pages will be |
| * preceeded in the dump by enough padding to align the first byte |
| * of that file's data to a page boundary |
| */ |
| #define PGALIGNTHRESH 8 |
| |
| |
| /* structure definitions used locally ****************************************/ |
| |
| /* number of bstats bstat_iter fetches at a time |
| */ |
| #define BSTATBUFLEN 4096 |
| |
| /* if the source file system type can't be determined, assume it is this |
| */ |
| #define FS_DEFAULT "xfs" |
| |
| /* marks consist of a opaque drive layer cookie and a startpoint. |
| * the drive layer requires that it be passed a pointer to a drive_markrec_t. |
| * we tack on content-specific baggage (startpt_t). this works because we |
| * allocate and free mark_t's here. |
| */ |
| struct mark { |
| drive_markrec_t dm; |
| startpt_t startpt; |
| }; |
| |
| typedef struct mark mark_t; |
| |
| /* Media_mfile_begin() entry state. |
| */ |
| enum bes { BES_INIT, /* in the beginning */ |
| BES_ENDOK, /* last media file successfully flushed to media */ |
| BES_ENDEOM, /* hit EOM while writing last media file */ |
| BES_INVAL }; /* to assert protocol being followed */ |
| |
| typedef enum bes bes_t; |
| |
| /* per-stream context |
| */ |
| struct context { |
| filehdr_t *cc_filehdrp; |
| /* pre-allocated buffer: heads each dumped file |
| */ |
| extenthdr_t *cc_extenthdrp; |
| /* pre-allocated buffer: heads each dumped file extent |
| */ |
| void *cc_inomap_contextp; |
| /* pre-allocated context to speed inomap iteration |
| */ |
| char *cc_getdentsbufp; |
| size_t cc_getdentsbufsz; |
| /* pre-allocated buffer for getdents() syscall |
| */ |
| char *cc_mdirentbufp; |
| size_t cc_mdirentbufsz; |
| /* pre-allocated buffer for on-media dirent |
| */ |
| char *cc_extattrlistbufp; |
| size_t cc_extattrlistbufsz; |
| /* pre-allocated buffer for retrieving a |
| * list of extended file attributes |
| */ |
| attr_multiop_t *cc_extattrrtrvarrayp; |
| size_t cc_extattrrtrvarraylen; |
| /* pre-allocated array of ops for retrieving the |
| * values for a list of extended file attributes |
| */ |
| char *cc_extattrdumpbufp; |
| size_t cc_extattrdumpbufsz; |
| /* pre-allocated buffer for dumping the names and |
| * values for a list of extended file attributes |
| */ |
| hsm_f_ctxt_t *cc_hsm_f_ctxtp; |
| /* pre-allocated HSM context used for holding HSM |
| state information about a file across subroutine |
| calls. |
| */ |
| char *cc_readlinkbufp; |
| size_t cc_readlinkbufsz; |
| /* pre-allocated buffer for readlink() |
| */ |
| off64_t cc_mfilesz; |
| /* total bytes dumped to media file |
| */ |
| size_t cc_markscommitted; |
| /* number of marks committed in mfile. only useful |
| * info is if greater than zero. |
| */ |
| xfs_ino_t cc_stat_lastino; |
| /* monotonic strm nondir ino dumped |
| */ |
| bool_t cc_completepr; |
| /* set if stream completely dumped. useful for |
| * determining if dump was interrupted |
| */ |
| bool_t cc_Media_useterminatorpr; |
| /* true if stream terminators are expected and |
| * will be used |
| */ |
| char *cc_Media_firstlabel; |
| /* optional command line media label. only used |
| * for first media object in stream, and only if |
| * media object does not already have a label |
| */ |
| bes_t cc_Media_begin_entrystate; |
| /* Media_mfile_begin context entry state |
| */ |
| }; |
| |
| typedef struct context context_t; |
| |
| /* extent group context, used by dump_file() |
| */ |
| #define BMAP_LEN 512 |
| |
| struct extent_group_context { |
| getbmapx_t eg_bmap[BMAP_LEN]; |
| getbmapx_t *eg_nextbmapp; /* ptr to the next extent to dump */ |
| getbmapx_t *eg_endbmapp; /* to detect extent exhaustion */ |
| int eg_fd; /* file desc. */ |
| int eg_bmapix; /* debug info only, not used */ |
| int eg_gbmcnt; /* debug, counts getbmapx calls for ino*/ |
| }; |
| |
| typedef struct extent_group_context extent_group_context_t; |
| |
| |
| /* minimum getdents() buffer size |
| */ |
| #define GETDENTSBUF_SZ_MIN (2 * pgsz) |
| |
| |
| /* minimum sizes for extended attributes buffers |
| */ |
| #define EXTATTR_LISTBUF_SZ (XATTR_LIST_MAX) |
| #define EXTATTR_RTRVARRAY_LEN (1 * pgsz) |
| #define EXTATTR_DUMPBUF_SZ (4 * pgsz) |
| |
| /* for printing ext attr namespace |
| */ |
| #define EXTATTR_NAMESPACE(flag) (((flag) & ATTR_ROOT) ? _("root") : \ |
| (((flag) & ATTR_SECURE) ? _("secure") : \ |
| _("non-root"))) |
| |
| /* for printing file type |
| */ |
| #define FILETYPE(statp) (((statp)->bs_mode & S_IFMT) == S_IFDIR \ |
| ? _("dir") : _("nondir")) |
| |
| /* per-drive status descriptor |
| */ |
| struct pds { |
| enum { PDS_NULL, /* per-drive activity not begun */ |
| PDS_INOMAP, /* dumping inomap */ |
| PDS_DIRDUMP, /* dumping dirs */ |
| PDS_NONDIR, /* dumping nondirs */ |
| PDS_INVSYNC, /* waiting for inventory */ |
| PDS_INVDUMP, /* dumping session inventory */ |
| PDS_TERMDUMP /* writing stream terminator */ |
| } pds_phase; |
| size64_t pds_dirdone; /* number of directories done */ |
| }; |
| |
| typedef struct pds pds_t; |
| |
| /* declarations of externally defined global symbols *************************/ |
| |
| extern void usage(void); |
| extern bool_t preemptchk(int); |
| extern char *homedir; |
| extern bool_t pipeline; |
| extern bool_t stdoutpiped; |
| extern char *sistr; |
| extern size_t pgsz; |
| |
| |
| /* forward declarations of locally defined static functions ******************/ |
| |
| /* file dumpers |
| */ |
| static rv_t dump_dirs(ix_t strmix, |
| struct xfs_bstat *bstatbufp, |
| size_t bstatbuflen, |
| void *inomap_contextp); |
| static rv_t dump_dir(ix_t strmix, |
| jdm_fshandle_t *, |
| int, |
| struct xfs_bstat *); |
| static rv_t dump_file(void *, |
| jdm_fshandle_t *, |
| int, |
| struct xfs_bstat *); |
| static rv_t dump_file_reg(drive_t *drivep, |
| context_t *contextp, |
| content_inode_hdr_t *scwhdrp, |
| jdm_fshandle_t *, |
| struct xfs_bstat *, |
| bool_t *); |
| static rv_t dump_file_spec(drive_t *drivep, |
| context_t *contextp, |
| jdm_fshandle_t *, |
| struct xfs_bstat *); |
| static rv_t dump_filehdr(drive_t *drivep, |
| context_t *contextp, |
| struct xfs_bstat *, |
| off64_t, |
| int); |
| static rv_t dump_extenthdr(drive_t *drivep, |
| context_t *contextp, |
| int32_t, |
| int32_t, |
| off64_t, |
| off64_t); |
| static rv_t dump_dirent(drive_t *drivep, |
| context_t *contextp, |
| struct xfs_bstat *, |
| xfs_ino_t, |
| gen_t, |
| char *, |
| size_t); |
| static rv_t init_extent_group_context(jdm_fshandle_t *, |
| struct xfs_bstat *, |
| extent_group_context_t *); |
| static void cleanup_extent_group_context(extent_group_context_t *); |
| static rv_t dump_extent_group(drive_t *drivep, |
| context_t *contextp, |
| struct xfs_bstat *, |
| extent_group_context_t *, |
| off64_t, |
| off64_t, |
| bool_t, |
| off64_t *, |
| off64_t *, |
| bool_t *); |
| static bool_t dump_session_inv(drive_t *drivep, |
| context_t *contextp, |
| media_hdr_t *mwhdrp, |
| content_inode_hdr_t *scwhdrp); |
| static rv_t write_pad(drive_t *drivep, size_t); |
| |
| static void mark_callback(void *, drive_markrec_t *, bool_t); |
| |
| static void inv_cleanup(void); |
| static void dump_terminator(drive_t *drivep, |
| context_t *contextp, |
| media_hdr_t *mwhdrp); |
| static rv_t Media_mfile_begin(drive_t *drivep, |
| context_t *contextp, |
| bool_t intr_allowed); |
| static rv_t Media_mfile_end(drive_t *drivep, |
| context_t *contextp, |
| media_hdr_t *mwhdrp, |
| off64_t *ncommittedp, |
| bool_t hit_eom); |
| static bool_t Media_prompt_overwrite(drive_t *drivep); |
| static rv_t Media_erasechk(drive_t *drivep, |
| int dcaps, |
| bool_t intr_allowed, |
| bool_t prevmediapresentpr); |
| static bool_t Media_prompt_erase(drive_t *drivep); |
| static char *Media_prompt_label(drive_t *drivep, char *bufp, size_t bufsz); |
| static void update_cc_Media_useterminatorpr(drive_t *drivep, |
| context_t *contextp); |
| static void set_mcflag(ix_t thrdix); |
| static void clr_mcflag(ix_t thrdix); |
| |
| static bool_t check_complete_flags(void); |
| |
| static rv_t dump_extattrs(drive_t *drivep, |
| context_t *contextp, |
| jdm_fshandle_t *fshandlep, |
| struct xfs_bstat *statp); |
| static rv_t dump_extattr_list(drive_t *drivep, |
| context_t *contextp, |
| jdm_fshandle_t *fshandlep, |
| struct xfs_bstat *statp, |
| attrlist_t *listp, |
| int flag, |
| bool_t *abortprp); |
| static char *dump_extattr_buildrecord(struct xfs_bstat *statp, |
| char *dumpbufp, |
| char *dumpbufendp, |
| char *namesrcp, |
| uint32_t valuesz, |
| int flag, |
| char **valuepp); |
| static rv_t dump_extattrhdr(drive_t *drivep, |
| context_t *contextp, |
| struct xfs_bstat *statp, |
| size_t recsz, |
| size_t valoff, |
| ix_t flags, |
| uint32_t valsz); |
| |
| static bool_t save_quotas(char *mntpnt, |
| quota_info_t *quotainfo); |
| static int getxfsqstat(char *fsdev); |
| |
| |
| |
| /* definition of locally defined global variables ****************************/ |
| |
| bool_t content_media_change_needed; |
| char *media_change_alert_program = NULL; |
| hsm_fs_ctxt_t *hsm_fs_ctxtp = NULL; |
| uint64_t hdr_mfilesz = 0; |
| uint64_t maxdumpfilesize = 0; |
| bool_t allowexcludefiles_pr = BOOL_FALSE; |
| |
| /* definition of locally defined static variables *****************************/ |
| |
| static bool_t sc_preerasepr = BOOL_FALSE; |
| /* pre-erase media |
| */ |
| static inv_idbtoken_t sc_inv_idbtoken = INV_TOKEN_NULL; |
| /* handle to inventory |
| */ |
| static inv_sestoken_t sc_inv_sestoken = INV_TOKEN_NULL; |
| /* handle to inventory session |
| */ |
| static inv_stmtoken_t *sc_inv_stmtokenp = 0; |
| /* array of inventory session stream handles |
| */ |
| static bool_t sc_inv_updatepr = BOOL_TRUE; |
| /* set if ok to update online inventory with stats of this dump |
| */ |
| static ix_t sc_level = LEVEL_DEFAULT; |
| /* dump level requested |
| */ |
| static bool_t sc_incrpr = BOOL_FALSE; |
| static time32_t sc_incrbasetime; |
| static ix_t sc_incrbaselevel; |
| static uuid_t sc_incrbaseid; |
| /* if an incremental dump, the base, level and time of the incremental |
| * base dump. TRICKY: if resuming an incremental dump, this is the |
| * base of the original incremental. |
| */ |
| static bool_t sc_resumepr = BOOL_FALSE; |
| static time32_t sc_resumebasetime = 0; |
| static uuid_t sc_resumebaseid; |
| static size_t sc_resumerangecnt = 0; |
| static drange_t *sc_resumerangep = 0; |
| /* if a resumed dump, the id, time and undumped ino/offset ranges |
| * of the interrupted dump being resumed. |
| */ |
| static jdm_fshandle_t *sc_fshandlep = 0; |
| /* dmi file system handle |
| */ |
| static int sc_fsfd = -1; |
| /* open file descriptor for root directory |
| */ |
| static struct xfs_bstat *sc_rootxfsstatp = 0; |
| /* pointer to loaded bulkstat for root directory |
| */ |
| static startpt_t *sc_startptp = 0; |
| /* an array of stream ino/offset start points |
| */ |
| static time32_t sc_stat_starttime = 0; |
| /* for cacluating elapsed time |
| */ |
| static ix_t sc_stat_inomapphase = 0; |
| static ix_t sc_stat_inomappass = 0; |
| static size64_t sc_stat_inomapcnt; |
| static size64_t sc_stat_inomapdone; |
| static size64_t sc_stat_dircnt = 0; |
| /* total number of directory inodes to be dumped (strm 0) |
| */ |
| static pds_t sc_stat_pds[STREAM_SIMMAX]; |
| /* per-drive stream status |
| */ |
| static size64_t sc_stat_nondircnt = 0; |
| /* total number of non-directory inodes to be dumped (all strms) |
| */ |
| static size64_t sc_stat_nondirdone = 0; |
| /* total number of non-directory inodes dumped (all strms) |
| */ |
| static size64_t sc_stat_datasz = 0; |
| /* total size in bytes of non-dirs to be dumped (all strms) |
| */ |
| static size64_t sc_stat_datadone = 0; |
| /* total size in bytes of non-dirs dumped (all strms) |
| */ |
| static size_t sc_thrdsarrivedcnt = 0; |
| /* each thread checks in by bumping this count under lock. |
| * used to decide when its ok to begin waiting for all threads |
| * to arrive at sync pt for session inventory dump. |
| */ |
| static size_t sc_thrdsdonecnt = 0; |
| /* number of threads which are ready to dump the session inventory. |
| * when equal to the number of streams remaining (stream_cnt()), |
| * can proceed with inventory dumps |
| */ |
| static context_t *sc_contextp; |
| /* an array of per-stream context descriptors |
| */ |
| static bool_t sc_mcflag[STREAM_SIMMAX]; |
| /* media change flag |
| */ |
| static bool_t sc_dumpextattrpr = BOOL_TRUE; |
| /* dump extended attributes |
| */ |
| static bool_t sc_dumpasoffline = BOOL_FALSE; |
| /* dump dual-residency HSM files as offline |
| */ |
| static bool_t sc_use_old_direntpr = BOOL_FALSE; |
| /* dump dirents as dirent_v1_t instead of dirent_t |
| * (for compat with dump format 2) |
| */ |
| |
| static bool_t sc_savequotas = BOOL_TRUE; |
| /* save quota information in dump |
| */ |
| static quota_info_t quotas[] = { |
| { "user quota", BOOL_TRUE, CONTENT_QUOTAFILE, "", "-uf", XFS_QUOTA_UDQ_ACCT, 0 }, |
| { "project quota", BOOL_TRUE, CONTENT_PQUOTAFILE, "", "-pf", XFS_QUOTA_PDQ_ACCT, 0 }, |
| { "group quota", BOOL_TRUE, CONTENT_GQUOTAFILE, "", "-gf", XFS_QUOTA_GDQ_ACCT, 0 } |
| }; |
| |
| /* definition of locally defined global functions ****************************/ |
| |
| |
| /* definition of locally defined static functions ****************************/ |
| static bool_t create_inv_session( |
| global_hdr_t *gwhdrtemplatep, |
| uuid_t *fsidp, |
| const char *mntpnt, |
| const char *fsdevice, |
| ix_t subtreecnt, |
| size_t strmix); |
| |
| /* |
| * Verify that we are asked to dump from the root of the filesystem; |
| * test this by checking whether the inode number we've been given matches |
| * the inode number for this directory's ".." |
| */ |
| static bool_t |
| check_rootdir(int fd, |
| xfs_ino_t ino) |
| { |
| struct dirent *gdp; |
| size_t gdsz; |
| bool_t found = BOOL_FALSE; |
| |
| gdsz = sizeof(struct dirent) + NAME_MAX + 1; |
| if (gdsz < GETDENTSBUF_SZ_MIN) |
| gdsz = GETDENTSBUF_SZ_MIN; |
| gdp = (struct dirent *)calloc(1, gdsz); |
| assert(gdp); |
| |
| while (1) { |
| struct dirent *p; |
| int nread; |
| |
| nread = getdents_wrap(fd, (char *)gdp, gdsz); |
| /* |
| * negative count indicates something very bad happened; |
| * try to gracefully end this dir. |
| */ |
| if (nread < 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, |
| _("unable to read dirents for directory ino %llu: %s\n"), |
| ino, strerror(errno)); |
| break; |
| } |
| |
| /* no more directory entries: break; */ |
| if (!nread) |
| break; |
| |
| for (p = gdp; nread > 0; |
| nread -= (int)p->d_reclen, |
| assert(nread >= 0), |
| p = (struct dirent *)((char *)p + p->d_reclen)) { |
| if (!strcmp(p->d_name, "..")) { |
| if (p->d_ino == ino) |
| found = BOOL_TRUE; |
| break; |
| } |
| } |
| } |
| free(gdp); |
| return found; |
| } |
| |
| bool_t |
| content_init(int argc, |
| char *argv[], |
| global_hdr_t *gwhdrtemplatep) |
| { |
| |
| inv_idbtoken_t inv_idbt; |
| inv_session_t *sessp = 0; |
| drive_hdr_t *dwhdrtemplatep; |
| media_hdr_t *mwhdrtemplatep; |
| content_hdr_t *cwhdrtemplatep; |
| content_inode_hdr_t *scwhdrtemplatep; |
| ix_t subtreecnt; |
| char **subtreep; |
| ix_t subtreeix; |
| bool_t resumereqpr = BOOL_FALSE; |
| char *srcname; |
| char mntpnt[GLOBAL_HDR_STRING_SZ]; |
| char fsdevice[GLOBAL_HDR_STRING_SZ]; |
| char fstype[CONTENT_HDR_FSTYPE_SZ]; |
| bool_t skip_unchanged_dirs = BOOL_FALSE; |
| uuid_t fsid; |
| bool_t underfoundpr; |
| ix_t underlevel = (ix_t)(-1); |
| time32_t undertime = 0; |
| uuid_t underid; |
| bool_t underpartialpr = BOOL_FALSE; |
| bool_t underinterruptedpr = BOOL_FALSE; |
| bool_t samefoundpr; |
| time32_t sametime = 0; |
| uuid_t sameid; |
| bool_t samepartialpr = BOOL_FALSE; |
| bool_t sameinterruptedpr = BOOL_FALSE; |
| size_t strmix; |
| int c; |
| int i; |
| int qstat; |
| int rval; |
| bool_t ok; |
| extern char *optarg; |
| extern int optind, opterr, optopt; |
| char *baseuuidstr = NULL; |
| uuid_t baseuuid; |
| bool_t baseuuidvalpr; |
| uint64_t dircnt; |
| uint64_t nondircnt; |
| uint64_t datasz; |
| uint64_t inocnt; |
| uint64_t inomapsz; |
| uint64_t direntsz; |
| uint64_t filesz; |
| uint64_t size_estimate; |
| |
| /* basic sanity checks |
| */ |
| assert(sizeof(mode_t) == MODE_SZ); |
| assert(sizeof(timestruct_t) == TIMESTRUCT_SZ); |
| assert(sizeof(bstat_t) == BSTAT_SZ); |
| assert(sizeof(filehdr_t) == FILEHDR_SZ); |
| assert(sizeof(extenthdr_t) == EXTENTHDR_SZ); |
| assert(sizeof(direnthdr_t) == DIRENTHDR_SZ); |
| assert(sizeof(direnthdr_v1_t) == DIRENTHDR_SZ); |
| assert(DIRENTHDR_SZ % DIRENTHDR_ALIGN == 0); |
| assert(sizeofmember(content_hdr_t, ch_specific) |
| >= |
| sizeof(content_inode_hdr_t)); |
| assert(sizeof(extattrhdr_t) == EXTATTRHDR_SZ); |
| |
| /* calculate offsets of portions of the write hdr template |
| */ |
| dwhdrtemplatep = (drive_hdr_t *)gwhdrtemplatep->gh_upper; |
| mwhdrtemplatep = (media_hdr_t *)dwhdrtemplatep->dh_upper; |
| cwhdrtemplatep = (content_hdr_t *)mwhdrtemplatep->mh_upper; |
| scwhdrtemplatep = (content_inode_hdr_t *) cwhdrtemplatep->ch_specific; |
| |
| if (gwhdrtemplatep->gh_version < GLOBAL_HDR_VERSION_3) { |
| sc_use_old_direntpr = BOOL_TRUE; |
| } |
| |
| /* process command line args |
| */ |
| optind = 1; |
| opterr = 0; |
| subtreecnt = 0; |
| baseuuidvalpr = BOOL_FALSE; |
| while ((c = getopt(argc, argv, GETOPT_CMDSTRING)) != EOF) { |
| switch (c) { |
| case GETOPT_LEVEL: |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| sc_level = (ix_t)atoi(optarg); |
| if (sc_level > LEVEL_MAX) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument must be " |
| "between 0 and %d\n"), |
| c, |
| LEVEL_MAX); |
| usage(); |
| return BOOL_FALSE; |
| } |
| break; |
| case GETOPT_SUBTREE: |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (optarg[0] == '/') { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument (subtree) " |
| "must be a relative pathname\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| subtreecnt++; |
| break; |
| case GETOPT_MAXDUMPFILESIZE: |
| if (!optarg || optarg [0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| maxdumpfilesize = strtoull(optarg, NULL, 0); |
| if (maxdumpfilesize == 0 || |
| maxdumpfilesize > ULONGLONG_MAX / 1024 || |
| (maxdumpfilesize == ULONGLONG_MAX && errno == ERANGE)) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument is not a valid file size\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| maxdumpfilesize *= 1024; |
| break; |
| case GETOPT_NOUNCHANGEDDIRS: |
| skip_unchanged_dirs = BOOL_TRUE; |
| break; |
| case GETOPT_EXCLUDEFILES: |
| allowexcludefiles_pr = BOOL_TRUE; |
| break; |
| case GETOPT_RESUME: |
| resumereqpr = BOOL_TRUE; |
| break; |
| case GETOPT_NOINVUPDATE: |
| sc_inv_updatepr = BOOL_FALSE; |
| break; |
| case GETOPT_ERASE: |
| sc_preerasepr = BOOL_TRUE; |
| break; |
| 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: |
| sc_dumpextattrpr = BOOL_FALSE; |
| break; |
| case GETOPT_DUMPASOFFLINE: |
| sc_dumpasoffline = BOOL_TRUE; |
| break; |
| case GETOPT_BASED: |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| baseuuidstr = optarg; |
| |
| if (uuid_parse(baseuuidstr, baseuuid) < 0) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "-%c argument not a valid " |
| "dump session id\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| baseuuidvalpr = BOOL_TRUE; |
| } |
| } |
| |
| if (resumereqpr && baseuuidvalpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "may not specify both -%c and -%c\n"), |
| GETOPT_BASED, |
| GETOPT_RESUME); |
| return BOOL_FALSE; |
| } |
| |
| /* the user may specify stdout as the destination, by a single |
| * dash ('-') with no option letter. This must appear between |
| * all lettered arguments and the source file system pathname. |
| */ |
| if (optind < argc && !strcmp(argv[optind ], "-")) { |
| optind++; |
| } |
| |
| /* the last argument must be either the mount point or a |
| * device pathname of the file system to be dumped. |
| */ |
| if (optind >= argc) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "source file system " |
| "not specified\n")); |
| usage(); |
| return BOOL_FALSE; |
| } |
| srcname = argv[optind]; |
| |
| if (preemptchk(PREEMPT_FULL)) { |
| return BOOL_FALSE; |
| } |
| |
| /* allocate space for the subtree pointer array and load it |
| */ |
| if (subtreecnt) { |
| subtreep = (char **)calloc(subtreecnt, sizeof(char *)); |
| assert(subtreep); |
| optind = 1; |
| opterr = 0; |
| subtreeix = 0; |
| while ((c = getopt(argc, argv, GETOPT_CMDSTRING)) != EOF) { |
| switch (c) { |
| case GETOPT_SUBTREE: |
| assert(subtreeix < subtreecnt); |
| assert(optarg && optarg[0] != '-'); |
| subtreep[subtreeix++] = optarg; |
| break; |
| } |
| } |
| assert(subtreeix == subtreecnt); |
| } else { |
| subtreep = 0; |
| } |
| |
| /* call a magic function to figure out if the last argument is |
| * a mount point or a device pathname, and retrieve the file |
| * system type, full pathname of the character special device |
| * containing the file system, the latest mount point, and the file |
| * system ID (uuid). returns BOOL_FALSE if the last |
| * argument doesn't look like a file system. |
| */ |
| if (!fs_info(fstype, |
| sizeof(fstype), |
| FS_DEFAULT, |
| fsdevice, |
| sizeof(fsdevice), |
| mntpnt, |
| sizeof(mntpnt), |
| &fsid, |
| srcname)) { |
| |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "%s does not identify a file system\n"), |
| srcname); |
| usage(); |
| return BOOL_FALSE; |
| } |
| |
| /* verify that the file system is mounted. This must be enhanced |
| * to mount an unmounted file system on a temporary mount point, |
| * if it is not currently mounted. |
| */ |
| if (!fs_mounted(fstype, fsdevice, mntpnt, &fsid)) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "%s must be mounted to be dumped\n"), |
| srcname); |
| return BOOL_FALSE; |
| } |
| |
| /* place the fs info in the write hdr template |
| */ |
| (void)strncpyterm(cwhdrtemplatep->ch_mntpnt, |
| mntpnt, |
| sizeof(cwhdrtemplatep->ch_mntpnt)); |
| (void)strncpyterm(cwhdrtemplatep->ch_fsdevice, |
| fsdevice, |
| sizeof(cwhdrtemplatep->ch_fsdevice)); |
| (void)strncpyterm(cwhdrtemplatep->ch_fstype, |
| fstype, |
| sizeof(cwhdrtemplatep->ch_fstype)); |
| uuid_copy(cwhdrtemplatep->ch_fsid, fsid); |
| |
| /* write quota information */ |
| if(sc_savequotas) { |
| |
| sc_savequotas = BOOL_FALSE; |
| for(i = 0; i < (sizeof(quotas) / sizeof(quotas[0])); i++) { |
| quotas[i].savequotas = BOOL_FALSE; |
| qstat = getxfsqstat(fsdevice); |
| if (qstat > 0 && (qstat & quotas[i].statflag)) { |
| sprintf(quotas[i].quotapath, "%s/%s", mntpnt, quotas[i].quotafile); |
| if(save_quotas(mntpnt, "as[i])) { |
| if(subtreecnt) { |
| subtreecnt++; |
| subtreep = (char **) realloc(subtreep, |
| subtreecnt * sizeof(char *)); |
| assert(subtreep); |
| subtreep[subtreecnt - 1] = quotas[i].quotafile; |
| } |
| sc_savequotas = BOOL_TRUE; |
| quotas[i].savequotas = BOOL_TRUE; |
| } else { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "failed to save %s information, continuing\n"), |
| quotas[i].desc); |
| } |
| } |
| } |
| } |
| |
| |
| /* create my /var directory if it doesn't already exist. |
| */ |
| var_create(); |
| |
| /* get two session descriptors from the inventory: one for the last |
| * dump at this level, and one for the last dump at a lower level. |
| * the former will be used to check if the last dump at this level |
| * was prematurely terminated; if so, for those inos already dumped |
| * then, we will look only for changes since that dump. the latter |
| * will give us a change date for all other inos. |
| */ |
| |
| if (preemptchk(PREEMPT_FULL)) { |
| return BOOL_FALSE; |
| } |
| |
| /* briefly open the online dump inventory, so it can be used |
| * to calculate incremental and resumed dumps. |
| */ |
| inv_idbt = inv_open((inv_predicate_t)INV_BY_UUID, |
| INV_SEARCH_ONLY, |
| (void *)&fsid); |
| |
| /* if a based request, look for the indicated session. |
| * if found, and not interrupted, this will be used as an |
| * incremental base. if interrupted, will be used as |
| * resume base. |
| */ |
| if (baseuuidvalpr) { |
| ix_t strix; |
| ix_t strcnt; |
| inv_stream_t *bsp; |
| bool_t interruptedpr; |
| |
| underfoundpr = BOOL_FALSE; |
| samefoundpr = BOOL_FALSE; |
| underinterruptedpr = BOOL_FALSE; |
| sameinterruptedpr = BOOL_FALSE; |
| interruptedpr = BOOL_FALSE; |
| |
| ok = inv_get_session_byuuid(&fsid, &baseuuid, &sessp); |
| if (!ok) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "could not find specified base dump (%s) " |
| "in inventory\n"), |
| baseuuidstr); |
| return BOOL_FALSE; |
| } |
| strcnt = (ix_t)sessp->s_nstreams; |
| for (strix = 0; strix < strcnt; strix++) { |
| bsp = &sessp->s_streams[strix]; |
| if (bsp->st_interrupted) { |
| interruptedpr = BOOL_TRUE; |
| break; |
| } |
| } |
| |
| if (interruptedpr) { |
| sc_level = (ix_t)sessp->s_level; |
| resumereqpr = BOOL_TRUE; |
| samefoundpr = BOOL_TRUE; |
| sametime = sessp->s_time; |
| uuid_copy (sameid, sessp->s_sesid); |
| samepartialpr = sessp->s_ispartial; |
| sameinterruptedpr = BOOL_TRUE; |
| sc_resumerangecnt = (size_t)sessp->s_nstreams; |
| sc_resumerangep = (drange_t *)calloc(sc_resumerangecnt, |
| sizeof(drange_t)); |
| assert(sc_resumerangep); |
| for (strmix = 0; strmix < sc_resumerangecnt; strmix++) { |
| inv_stream_t *bsp; |
| inv_stream_t *esp; |
| drange_t *p = &sc_resumerangep[strmix]; |
| bsp = &sessp->s_streams[strmix]; |
| esp = (strmix < sc_resumerangecnt - 1) |
| ? |
| bsp + 1 |
| : |
| 0; |
| if (bsp->st_interrupted) { |
| sameinterruptedpr = BOOL_TRUE; |
| p->dr_begin.sp_ino = bsp->st_endino; |
| p->dr_begin.sp_offset = bsp->st_endino_off; |
| if (esp) { |
| p->dr_end.sp_ino = esp->st_startino; |
| p->dr_end.sp_offset = |
| esp->st_startino_off; |
| mlog(MLOG_DEBUG, |
| "resume range stream %u " |
| "ino %llu:%lld to " |
| "%llu:%lld\n", |
| strmix, |
| p->dr_begin.sp_ino, |
| p->dr_begin.sp_offset, |
| p->dr_end.sp_ino, |
| p->dr_end.sp_offset); |
| } else { |
| p->dr_end.sp_flags = STARTPT_FLAGS_END; |
| mlog(MLOG_DEBUG, |
| "resume range stream %u " |
| "ino %llu:%lld to " |
| "end\n", |
| strmix, |
| p->dr_begin.sp_ino, |
| p->dr_begin.sp_offset); |
| } |
| } else { |
| /* set the range start pt's END flag to |
| * indicate the range was not interrupted. |
| */ |
| p->dr_begin.sp_flags = STARTPT_FLAGS_END; |
| } |
| } |
| } else { |
| if (sessp->s_level >= LEVEL_MAX) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "cannot select dump session %d as base " |
| "for incremental dump: " |
| "level must be less than %d\n"), |
| sessp->s_level, |
| LEVEL_MAX); |
| return BOOL_FALSE; |
| } |
| sc_level = (ix_t)sessp->s_level + 1; |
| undertime = sessp->s_time; |
| underlevel = (ix_t)sessp->s_level; |
| uuid_copy (underid, sessp->s_sesid); |
| underpartialpr = sessp->s_ispartial; |
| underinterruptedpr = BOOL_FALSE; |
| underfoundpr = BOOL_TRUE; |
| } |
| inv_free_session(&sessp); |
| sessp = 0; |
| ok = inv_close(inv_idbt); |
| assert(ok); |
| inv_idbt = INV_TOKEN_NULL; |
| goto baseuuidbypass; |
| } |
| |
| /* look for the most recent dump at a level less than the level |
| * of this dump. extract the time, level, id, and predicates partial |
| * and interrupted. |
| */ |
| underfoundpr = BOOL_FALSE; |
| if (sc_level > 0) { |
| if (inv_idbt == INV_TOKEN_NULL) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "cannot calculate incremental dump: " |
| "online inventory not available\n")); |
| return BOOL_FALSE; |
| } |
| ok = inv_lastsession_level_lessthan(&fsid, |
| inv_idbt, |
| (u_char_t)sc_level, |
| &sessp); |
| if (!ok) { |
| sessp = 0; |
| } |
| |
| if (sessp) { |
| ix_t strix; |
| ix_t strcnt; |
| inv_stream_t *bsp; |
| |
| undertime = sessp->s_time; |
| underlevel = (ix_t)sessp->s_level; |
| uuid_copy (underid, sessp->s_sesid); |
| underpartialpr = sessp->s_ispartial; |
| underinterruptedpr = BOOL_FALSE; |
| strcnt = (ix_t)sessp->s_nstreams; |
| for (strix = 0; strix < strcnt; strix++) { |
| bsp = &sessp->s_streams[strix]; |
| if (bsp->st_interrupted) { |
| underinterruptedpr = BOOL_TRUE; |
| break; |
| } |
| } |
| underfoundpr = BOOL_TRUE; |
| inv_free_session(& sessp); |
| sessp = 0; |
| } |
| } |
| |
| /* look for the most recent dump at a level equal to the level |
| * of this dump. extract the time, level, id, and predicates partial |
| * and interrupted, and for each stream the range of ino/offset |
| * values not dumped. |
| */ |
| if (inv_idbt != INV_TOKEN_NULL) { |
| /* REFERENCED */ |
| bool_t ok1; |
| ok = inv_lastsession_level_equalto(&fsid, |
| inv_idbt, |
| (u_char_t)sc_level, |
| &sessp); |
| ok1 = inv_close(inv_idbt); |
| assert(ok1); |
| if (!ok) { |
| sessp = 0; |
| } |
| inv_idbt = INV_TOKEN_NULL; |
| } else { |
| sessp = 0; |
| } |
| |
| samefoundpr = BOOL_FALSE; |
| if (sessp) { |
| sametime = sessp->s_time; |
| uuid_copy(sameid, sessp->s_sesid); |
| samepartialpr = sessp->s_ispartial; |
| sameinterruptedpr = BOOL_FALSE; |
| sc_resumerangecnt = (size_t)sessp->s_nstreams; |
| sc_resumerangep = (drange_t *)calloc(sc_resumerangecnt, |
| sizeof(drange_t)); |
| assert(sc_resumerangep); |
| for (strmix = 0; strmix < sc_resumerangecnt; strmix++) { |
| inv_stream_t *bsp; |
| inv_stream_t *esp; |
| drange_t *p = &sc_resumerangep[strmix]; |
| bsp = &sessp->s_streams[strmix]; |
| esp = (strmix < sc_resumerangecnt - 1) |
| ? |
| bsp + 1 |
| : |
| 0; |
| if (bsp->st_interrupted) { |
| sameinterruptedpr = BOOL_TRUE; |
| p->dr_begin.sp_ino = bsp->st_endino; |
| p->dr_begin.sp_offset = bsp->st_endino_off; |
| if (esp) { |
| p->dr_end.sp_ino = esp->st_startino; |
| p->dr_end.sp_offset = |
| esp->st_startino_off; |
| mlog(MLOG_DEBUG, |
| "resume range stream %u " |
| "ino %llu:%lld to " |
| "%llu:%lld\n", |
| strmix, |
| p->dr_begin.sp_ino, |
| p->dr_begin.sp_offset, |
| p->dr_end.sp_ino, |
| p->dr_end.sp_offset); |
| } else { |
| p->dr_end.sp_flags = STARTPT_FLAGS_END; |
| mlog(MLOG_DEBUG, |
| "resume range stream %u " |
| "ino %llu:%lld to " |
| "end\n", |
| strmix, |
| p->dr_begin.sp_ino, |
| p->dr_begin.sp_offset); |
| } |
| } else { |
| /* set the range start pt's END flag to |
| * indicate the range was not interrupted. |
| */ |
| p->dr_begin.sp_flags = STARTPT_FLAGS_END; |
| } |
| } |
| inv_free_session(& sessp); |
| sessp = 0; |
| samefoundpr = BOOL_TRUE; |
| } |
| |
| baseuuidbypass: |
| |
| /* now determine the incremental and resume bases, if any. |
| */ |
| if (samefoundpr && !sameinterruptedpr) { |
| free((void *)sc_resumerangep); |
| sc_resumerangep = 0; |
| samefoundpr = BOOL_FALSE; |
| } |
| if (samefoundpr && !resumereqpr) { |
| if (!underfoundpr || undertime <= sametime) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "most recent level %d dump " |
| "was interrupted, " |
| "but not resuming that dump since " |
| "resume (-R) option not specified\n"), |
| sc_level); |
| } |
| free((void *)sc_resumerangep); |
| sc_resumerangep = 0; |
| samefoundpr = BOOL_FALSE; |
| } |
| if (underfoundpr) { |
| assert(underlevel <= LEVEL_MAX); |
| assert(undertime); |
| if (samefoundpr) { |
| if (undertime >= sametime) { |
| if (underinterruptedpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "most recent base for " |
| "incremental dump was " |
| "interrupted (level %u): " |
| "must resume or redump " |
| "at or below level %d\n"), |
| underlevel, |
| sc_level); |
| return BOOL_FALSE; |
| } |
| if (subtreecnt && !underpartialpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "level %u incremental " |
| "subtree dump " |
| "will be based on non-subtree " |
| "level %u dump\n"), |
| sc_level, |
| underlevel); |
| } |
| if (!subtreecnt && underpartialpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "level %u incremental " |
| "non-subtree dump " |
| "will be based on subtree " |
| "level %u dump\n"), |
| sc_level, |
| underlevel); |
| } |
| sc_incrpr = BOOL_TRUE; |
| sc_incrbasetime = undertime; |
| sc_incrbaselevel = underlevel; |
| uuid_copy(sc_incrbaseid, underid); |
| sc_resumepr = BOOL_FALSE; |
| assert(sc_resumerangep); |
| free((void *)sc_resumerangep); |
| sc_resumerangep = 0; |
| } else { |
| if (subtreecnt && !samepartialpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "level %u incremental " |
| "subtree dump " |
| "will be based on non-subtree " |
| "level %u resumed dump\n"), |
| sc_level, |
| sc_level); |
| } |
| if (!subtreecnt && samepartialpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "level %u incremental " |
| "non-subtree dump " |
| "will be based on subtree " |
| "level %u resumed dump\n"), |
| sc_level, |
| sc_level); |
| } |
| assert(sametime); |
| sc_incrpr = BOOL_TRUE; |
| sc_incrbasetime = undertime; |
| sc_incrbaselevel = underlevel; |
| sc_resumepr = BOOL_TRUE; |
| sc_resumebasetime = sametime; |
| uuid_copy(sc_resumebaseid, sameid); |
| assert(sc_resumerangep); |
| } |
| } else { |
| if (underinterruptedpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "most recent base for " |
| "incremental dump was " |
| "interrupted (level %u): " |
| "must resume or redump " |
| "at or below level %d\n"), |
| underlevel, |
| sc_level); |
| return BOOL_FALSE; |
| } |
| if (subtreecnt && !underpartialpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "level %u incremental " |
| "subtree dump " |
| "will be based on non-subtree " |
| "level %u dump\n"), |
| sc_level, |
| underlevel); |
| } |
| if (!subtreecnt && underpartialpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "level %u incremental " |
| "non-subtree dump " |
| "will be based on subtree " |
| "level %u dump\n"), |
| sc_level, |
| underlevel); |
| } |
| sc_incrpr = BOOL_TRUE; |
| sc_incrbasetime = undertime; |
| sc_incrbaselevel = underlevel; |
| uuid_copy(sc_incrbaseid, underid); |
| sc_resumepr = BOOL_FALSE; |
| assert(!sc_resumerangep); |
| } |
| } else { |
| if (samefoundpr) { |
| assert(sametime); |
| if (subtreecnt && !samepartialpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "level %u " |
| "subtree dump " |
| "will be based on non-subtree " |
| "level %u resumed dump\n"), |
| sc_level, |
| sc_level); |
| } |
| if (!subtreecnt && samepartialpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "level %u " |
| "non-subtree dump " |
| "will be based on subtree " |
| "level %u resumed dump\n"), |
| sc_level, |
| sc_level); |
| } |
| sc_incrpr = BOOL_FALSE; |
| sc_resumepr = BOOL_TRUE; |
| sc_resumebasetime = sametime; |
| uuid_copy(sc_resumebaseid, sameid); |
| assert(sc_resumerangep); |
| } else { |
| sc_incrpr = BOOL_FALSE; |
| sc_resumepr = BOOL_FALSE; |
| assert(!sc_resumerangep); |
| if (sc_level > 0) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "cannot find earlier dump " |
| "to base level %d increment upon\n"), |
| sc_level); |
| return BOOL_FALSE; |
| } |
| } |
| } |
| |
| /* don't allow interrupted dumps of a lesser level to be bases |
| */ |
| if (sc_incrpr && underinterruptedpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "most recent base dump (level %d begun %s) " |
| "was interrupted: aborting\n"), |
| sc_incrbaselevel, |
| ctimennl(&sc_incrbasetime)); |
| return BOOL_FALSE; |
| } |
| |
| /* reject if resume (-R) specified, but base was not interrupted |
| */ |
| if (!sc_resumepr && resumereqpr) { |
| mlog(MLOG_NORMAL | MLOG_ERROR, _( |
| "resume (-R) option inappropriate: " |
| "no interrupted level %d dump to resume\n"), |
| sc_level); |
| return BOOL_FALSE; |
| } |
| |
| /* announce the dump characteristics |
| */ |
| if (sc_incrpr) { |
| if (sc_resumepr) { |
| char restimestr[30]; |
| char incrtimestr[30]; |
| |
| strcpy(restimestr, ctimennl(&sc_resumebasetime)); |
| assert(strlen(restimestr) < sizeof(restimestr)); |
| strcpy(incrtimestr, ctimennl(&sc_incrbasetime)); |
| assert(strlen(incrtimestr) < sizeof(incrtimestr)); |
| |
| mlog(MLOG_VERBOSE, _( |
| "resuming level %d incremental dump of %s:%s " |
| "begun %s " |
| "(incremental base level %d begun %s)\n"), |
| sc_level, |
| gwhdrtemplatep->gh_hostname, |
| mntpnt, |
| restimestr, |
| sc_incrbaselevel, |
| incrtimestr); |
| } else { |
| mlog(MLOG_VERBOSE, _( |
| "level %d incremental dump of %s:%s " |
| "based on level %d dump begun %s\n"), |
| sc_level, |
| gwhdrtemplatep->gh_hostname, |
| mntpnt, |
| sc_incrbaselevel, |
| ctimennl(&sc_incrbasetime)); |
| } |
| } else { |
| if (sc_resumepr) { |
| mlog(MLOG_VERBOSE, _( |
| "resuming level %d dump of %s:%s begun %s\n"), |
| sc_level, |
| gwhdrtemplatep->gh_hostname, |
| mntpnt, |
| ctimennl(&sc_resumebasetime)); |
| } else { |
| mlog(MLOG_VERBOSE, _( |
| "level %d dump of %s:%s\n"), |
| sc_level, |
| gwhdrtemplatep->gh_hostname, |
| mntpnt); |
| } |
| } |
| |
| if (preemptchk(PREEMPT_FULL)) { |
| return BOOL_FALSE; |
| } |
| |
| /* announce the dump time |
| */ |
| mlog(MLOG_VERBOSE, _( |
| "dump date: %s\n"), |
| ctimennl(&gwhdrtemplatep->gh_timestamp)); |
| |
| /* display the session UUID |
| */ |
| { |
| char string_uuid[UUID_STR_LEN + 1]; |
| uuid_unparse(gwhdrtemplatep->gh_dumpid, string_uuid); |
| mlog(MLOG_VERBOSE, _( |
| "session id: %s\n"), |
| string_uuid); |
| } |
| |
| /* display the session label |
| */ |
| mlog(MLOG_VERBOSE, _( |
| "session label: \"%s\"\n"), |
| gwhdrtemplatep->gh_dumplabel); |
| |
| /* get a file descriptor for the file system. any file |
| * contained in the file system will do; use the mntpnt. |
| * needed by bigstat. |
| */ |
| sc_fsfd = open(mntpnt, O_RDONLY); |
| if (sc_fsfd < 0) { |
| mlog(MLOG_NORMAL, _( |
| "unable to open %s: %s\n"), |
| mntpnt, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| |
| /* figure out the ino for the root directory of the fs |
| * and get its xfs_bstat_t for inomap_build() |
| */ |
| { |
| stat64_t rootstat; |
| rval = fstat64(sc_fsfd, &rootstat); |
| if (rval) { |
| mlog(MLOG_NORMAL, _( |
| "could not stat %s\n"), |
| mntpnt); |
| return BOOL_FALSE; |
| } |
| |
| if (!check_rootdir(sc_fsfd, rootstat.st_ino)) { |
| mlog(MLOG_ERROR, |
| _("%s is not the root of the filesystem (bind mount?) - use primary mountpoint\n"), |
| mntpnt); |
| return BOOL_FALSE; |
| } |
| |
| sc_rootxfsstatp = |
| (struct xfs_bstat *)calloc(1, sizeof(struct xfs_bstat)); |
| assert(sc_rootxfsstatp); |
| |
| if (bigstat_one(sc_fsfd, rootstat.st_ino, sc_rootxfsstatp) < 0) { |
| mlog(MLOG_ERROR, |
| _("failed to get bulkstat information for root inode\n")); |
| return BOOL_FALSE; |
| } |
| } |
| |
| /* alloc a file system handle, to be used with the jdm_open() |
| * functions. |
| */ |
| sc_fshandlep = jdm_getfshandle(mntpnt); |
| if (!sc_fshandlep) { |
| mlog(MLOG_NORMAL, _( |
| "unable to construct a file system handle for %s: %s\n"), |
| mntpnt, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| |
| if (preemptchk(PREEMPT_FULL)) { |
| return BOOL_FALSE; |
| } |
| |
| /* If GETOPT_DUMPASOFFLINE was specified, allocate a filesystem context |
| * for use by the HSM routines. |
| */ |
| |
| if (sc_dumpasoffline) { |
| hsm_fs_ctxtp = HsmInitFsysContext(mntpnt, HSM_API_VERSION_1); |
| } |
| |
| /* set now so statline can be displayed |
| */ |
| sc_stat_starttime = gwhdrtemplatep->gh_timestamp; |
| |
| /* allocate storage for the stream startpoints, and build inomap. |
| * inomap_build() also fills in the start points. storage only needed |
| * until the startpoints are copied into each streams header. will |
| * be freed at the end of this function. |
| */ |
| sc_stat_inomapcnt = (size64_t)fs_getinocnt(mntpnt); |
| |
| sc_startptp = (startpt_t *)calloc(drivecnt, sizeof(startpt_t)); |
| assert(sc_startptp); |
| ok = inomap_build(sc_fshandlep, |
| sc_fsfd, |
| sc_rootxfsstatp, |
| sc_incrpr, |
| sc_incrbasetime, |
| sc_resumepr, |
| sc_resumebasetime, |
| sc_resumerangecnt, |
| sc_resumerangep, |
| subtreep, |
| subtreecnt, |
| skip_unchanged_dirs, |
| sc_startptp, |
| drivecnt, |
| &sc_stat_inomapphase, |
| &sc_stat_inomappass, |
| sc_stat_inomapcnt, |
| &sc_stat_inomapdone); |
| free((void *)subtreep); |
| subtreep = 0; |
| if (!ok) { |
| return BOOL_FALSE; |
| } |
| |
| /* ask var to ask inomap to skip files under var if var is in |
| * the fs being dumped |
| */ |
| var_skip(&fsid, inomap_skip); |
| |
| /* fill in write header template content info. always produce |
| * an inomap for each media file. the dirdump flag will be set |
| * in content_stream_dump() for streams which dump the directories. |
| */ |
| assert(sizeof(cwhdrtemplatep->ch_specific) >= sizeof(*scwhdrtemplatep)); |
| scwhdrtemplatep->cih_mediafiletype = CIH_MEDIAFILETYPE_DATA; |
| scwhdrtemplatep->cih_level = (int32_t)sc_level; |
| scwhdrtemplatep->cih_dumpattr = CIH_DUMPATTR_INOMAP; |
| if (subtreecnt) { |
| scwhdrtemplatep->cih_dumpattr |= CIH_DUMPATTR_SUBTREE; |
| } |
| if (sc_inv_updatepr) { |
| scwhdrtemplatep->cih_dumpattr |= CIH_DUMPATTR_INVENTORY; |
| } |
| scwhdrtemplatep->cih_dumpattr |= CIH_DUMPATTR_FILEHDR_CHECKSUM; |
| scwhdrtemplatep->cih_dumpattr |= CIH_DUMPATTR_EXTENTHDR_CHECKSUM; |
| scwhdrtemplatep->cih_dumpattr |= CIH_DUMPATTR_DIRENTHDR_CHECKSUM; |
| scwhdrtemplatep->cih_dumpattr |= CIH_DUMPATTR_DIRENTHDR_GEN; |
| if (sc_incrpr) { |
| scwhdrtemplatep->cih_dumpattr |= CIH_DUMPATTR_INCREMENTAL; |
| scwhdrtemplatep->cih_last_time = sc_incrbasetime; |
| uuid_copy(scwhdrtemplatep->cih_last_id, sc_incrbaseid); |
| if (skip_unchanged_dirs) { |
| scwhdrtemplatep->cih_dumpattr |= |
| CIH_DUMPATTR_NOTSELFCONTAINED; |
| } |
| } |
| if (sc_resumepr) { |
| scwhdrtemplatep->cih_dumpattr |= CIH_DUMPATTR_RESUME; |
| scwhdrtemplatep->cih_resume_time = sc_resumebasetime; |
| uuid_copy(scwhdrtemplatep->cih_resume_id, sc_resumebaseid); |
| } |
| if (sc_dumpextattrpr) { |
| scwhdrtemplatep->cih_dumpattr |= CIH_DUMPATTR_EXTATTR; |
| scwhdrtemplatep->cih_dumpattr |= |
| CIH_DUMPATTR_EXTATTRHDR_CHECKSUM; |
| } |
| |
| scwhdrtemplatep->cih_rootino = sc_rootxfsstatp->bs_ino; |
| inomap_writehdr(scwhdrtemplatep); |
| |
| /* log the dump size. just a rough approx. |
| */ |
| dircnt = scwhdrtemplatep->cih_inomap_dircnt; |
| nondircnt = scwhdrtemplatep->cih_inomap_nondircnt; |
| datasz = scwhdrtemplatep->cih_inomap_datasz; |
| inocnt = dircnt + nondircnt; |
| inomapsz = inomap_getsz(); |
| direntsz = inocnt * (uint64_t)(DIRENTHDR_SZ + 8); |
| filesz = inocnt * (uint64_t)(FILEHDR_SZ + EXTENTHDR_SZ); |
| |
| hdr_mfilesz = GLOBAL_HDR_SZ |
| + |
| inomapsz |
| + |
| direntsz; |
| size_estimate = hdr_mfilesz |
| + |
| filesz |
| + |
| datasz; |
| mlog(MLOG_VERBOSE, _( |
| "estimated dump size: %llu bytes\n"), |
| size_estimate); |
| |
| if (drivecnt > 1) { |
| mlog(MLOG_VERBOSE, _( |
| "estimated dump size per stream: %llu bytes\n"), |
| hdr_mfilesz + (filesz + datasz) / drivecnt); |
| } |
| mlog(MLOG_DEBUG, |
| "estimated dump header size: %llu bytes\n", |
| hdr_mfilesz); |
| mlog(MLOG_DEBUG, |
| "estimated component sizes: global hdr: %llu bytes, " |
| "inomap: %llu bytes, dir entries: %llu bytes, " |
| "file hdrs: %llu bytes, datasz: %llu bytes\n", |
| GLOBAL_HDR_SZ, inomapsz, direntsz, |
| filesz, datasz); |
| |
| /* extract the progress stat denominators from the write hdr |
| * template. placed there by inomap_writehdr() |
| */ |
| sc_stat_dircnt = scwhdrtemplatep->cih_inomap_dircnt; |
| sc_stat_nondircnt = scwhdrtemplatep->cih_inomap_nondircnt; |
| sc_stat_datasz = scwhdrtemplatep->cih_inomap_datasz; |
| |
| /* allocate and populate per-stream context descriptors |
| */ |
| sc_contextp = (context_t *)calloc(drivecnt, sizeof(context_t)); |
| assert(sc_contextp); |
| for (strmix = 0; strmix < drivecnt; strmix++) { |
| context_t *contextp = &sc_contextp[strmix]; |
| |
| contextp->cc_filehdrp = |
| (filehdr_t *)calloc(1, sizeof(filehdr_t)); |
| assert(contextp->cc_filehdrp); |
| |
| contextp->cc_extenthdrp = |
| (extenthdr_t *)calloc(1, sizeof(extenthdr_t)); |
| assert(contextp->cc_extenthdrp); |
| |
| contextp->cc_getdentsbufsz = sizeof(struct dirent) |
| + |
| NAME_MAX + 1; |
| if (contextp->cc_getdentsbufsz < GETDENTSBUF_SZ_MIN) { |
| contextp->cc_getdentsbufsz = GETDENTSBUF_SZ_MIN; |
| } |
| contextp->cc_getdentsbufp = |
| (char *) calloc(1, contextp->cc_getdentsbufsz); |
| assert(contextp->cc_getdentsbufp); |
| |
| contextp->cc_mdirentbufsz = sizeof(direnthdr_t) |
| + |
| NAME_MAX + 1 |
| + |
| DIRENTHDR_ALIGN; |
| contextp->cc_mdirentbufp = |
| (char *) calloc(1, contextp->cc_mdirentbufsz); |
| assert(contextp->cc_mdirentbufp); |
| |
| contextp->cc_extattrlistbufsz = EXTATTR_LISTBUF_SZ; |
| contextp->cc_extattrrtrvarraylen = EXTATTR_RTRVARRAY_LEN; |
| contextp->cc_extattrdumpbufsz = 2 * ATTR_MAX_VALUELEN; |
| if (contextp->cc_extattrdumpbufsz < EXTATTR_DUMPBUF_SZ) { |
| contextp->cc_extattrdumpbufsz = EXTATTR_DUMPBUF_SZ; |
| } |
| contextp->cc_extattrlistbufp = |
| (char *)calloc(1, contextp->cc_extattrlistbufsz); |
| assert(contextp->cc_extattrlistbufp); |
| contextp->cc_extattrrtrvarrayp = |
| (attr_multiop_t *)calloc(contextp->cc_extattrrtrvarraylen, |
| sizeof(attr_multiop_t)); |
| assert(contextp->cc_extattrrtrvarrayp); |
| contextp->cc_extattrdumpbufp = |
| (char *)memalign(sizeof(extattrhdr_t), |
| contextp->cc_extattrdumpbufsz); |
| assert(contextp->cc_extattrdumpbufp); |
| if (hsm_fs_ctxtp) { |
| contextp->cc_hsm_f_ctxtp = HsmAllocateFileContext( |
| hsm_fs_ctxtp); |
| } else { |
| contextp->cc_hsm_f_ctxtp = NULL; |
| } |
| |
| contextp->cc_readlinkbufsz = MAXPATHLEN + SYMLINK_ALIGN; |
| contextp->cc_readlinkbufp = |
| (char *) calloc(1, contextp->cc_readlinkbufsz); |
| assert(contextp->cc_readlinkbufp); |
| |
| contextp->cc_inomap_contextp = inomap_alloc_context(); |
| } |
| |
| /* look for command line media labels. these will be assigned |
| * to each stream as found. this label is only for the media |
| * object currently in the drive. subsequently inserted media |
| * objects must get a label via prompting. |
| */ |
| |
| { |
| context_t *cp = sc_contextp; |
| context_t *ep = sc_contextp + drivecnt; |
| |
| optind = 1; |
| opterr = 0; |
| while ((c = getopt(argc, argv, GETOPT_CMDSTRING)) != EOF) { |
| switch (c) { |
| case GETOPT_MEDIALABEL: |
| if (cp >= ep) { |
| mlog(MLOG_NORMAL, _( |
| "more -%c arguments " |
| "than number of drives\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| if (!optarg || optarg[0] == '-') { |
| mlog(MLOG_NORMAL, _( |
| "-%c argument missing\n"), |
| c); |
| usage(); |
| return BOOL_FALSE; |
| } |
| cp->cc_Media_firstlabel = optarg; |
| cp++; |
| break; |
| } |
| } |
| |
| if (cp > sc_contextp && cp < ep) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "media labels given for only %d out of %d " |
| "drives\n"), |
| cp - sc_contextp, |
| drivecnt); |
| } |
| } |
| |
| if (preemptchk(PREEMPT_FULL)) { |
| return BOOL_FALSE; |
| } |
| |
| /* open the dump inventory and a dump inventory write session |
| * if an inventory update is to be done. |
| */ |
| if (sc_inv_updatepr) { |
| bool_t result; |
| sigset_t tty_set, orig_set; |
| |
| /* hold tty signals while creating a new inventory session */ |
| sigemptyset(&tty_set); |
| sigaddset(&tty_set, SIGINT); |
| sigaddset(&tty_set, SIGQUIT); |
| sigaddset(&tty_set, SIGHUP); |
| pthread_sigmask(SIG_BLOCK, &tty_set, &orig_set); |
| |
| result = create_inv_session(gwhdrtemplatep, &fsid, mntpnt, |
| fsdevice, subtreecnt, strmix); |
| |
| pthread_sigmask(SIG_SETMASK, &orig_set, NULL); |
| |
| if (!result) { |
| return BOOL_FALSE; |
| } |
| } |
| |
| /* set media change flags to FALSE; |
| */ |
| { |
| ix_t ix; |
| ix_t endix = sizeof(sc_mcflag) |
| / |
| sizeof(sc_mcflag[0]); |
| for (ix = 0; ix < endix; ix++) { |
| sc_mcflag[ix] = BOOL_FALSE; |
| } |
| } |
| content_media_change_needed = BOOL_FALSE; |
| |
| /* initialize the per-drive status |
| */ |
| { |
| ix_t driveix; |
| for (driveix = 0; driveix < STREAM_SIMMAX; driveix++) { |
| sc_stat_pds[driveix].pds_phase = PDS_NULL; |
| } |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| #define STATLINESZ 160 |
| |
| size_t |
| content_statline(char **linespp[]) |
| { |
| static char statlinebuf[STREAM_SIMMAX + 1][STATLINESZ]; |
| static char *statline[STREAM_SIMMAX + 1]; |
| size_t statlinecnt; |
| size64_t nondirdone; |
| size64_t datadone; |
| double percent; |
| time_t elapsed; |
| time_t now; |
| struct tm *tmp; |
| ix_t i; |
| |
| /* build and supply the line array |
| */ |
| for (i = 0; i < STREAM_SIMMAX + 1; i++) { |
| statline[i] = &statlinebuf[i][0]; |
| } |
| *linespp = statline; |
| statlinecnt = 0; |
| |
| /* if start time not initialized, return no strings |
| */ |
| if (!sc_stat_starttime) { |
| return 0; |
| } |
| |
| /* calculate the elapsed time |
| */ |
| now = time(0); |
| elapsed = now - sc_stat_starttime; |
| |
| /* get local time |
| */ |
| tmp = localtime(&now); |
| |
| /* if inomap phase indicated, report on that |
| */ |
| if (sc_stat_inomapphase && sc_stat_inomapcnt) { |
| if (sc_stat_inomappass) { |
| sprintf(statline[0], |
| "status at %02d:%02d:%02d: " |
| "inomap phase %u pass %u " |
| "%llu/%llu inos scanned, " |
| "%ld seconds elapsed\n", |
| tmp->tm_hour, |
| tmp->tm_min, |
| tmp->tm_sec, |
| (unsigned int)sc_stat_inomapphase, |
| (unsigned int)sc_stat_inomappass, |
| (unsigned long long)sc_stat_inomapdone, |
| (unsigned long long)sc_stat_inomapcnt, |
| elapsed); |
| assert(strlen(statline[0]) < STATLINESZ); |
| } else { |
| sprintf(statline[0], |
| "status at %02d:%02d:%02d: " |
| "inomap phase %u " |
| "%llu/%llu inos scanned, " |
| "%ld seconds elapsed\n", |
| tmp->tm_hour, |
| tmp->tm_min, |
| tmp->tm_sec, |
| (unsigned int)sc_stat_inomapphase, |
| (unsigned long long)sc_stat_inomapdone, |
| (unsigned long long)sc_stat_inomapcnt, |
| elapsed); |
| assert(strlen(statline[0]) < STATLINESZ); |
| } |
| return 1; |
| } |
| |
| /* get the accumulated totals for non-dir inos and data bytes dumped |
| */ |
| lock(); |
| nondirdone = sc_stat_nondirdone; |
| datadone = sc_stat_datadone; |
| unlock(); |
| |
| /* non-dir dump phase */ |
| if (nondirdone || datadone) { |
| /* calculate percentage of data dumped |
| */ |
| if (sc_stat_datasz) { |
| percent = (double)datadone |
| / |
| (double)sc_stat_datasz; |
| 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 dumped, " |
| "%.1lf%%%% data dumped, " |
| "%ld seconds elapsed\n", |
| tmp->tm_hour, |
| tmp->tm_min, |
| tmp->tm_sec, |
| (unsigned long long) nondirdone, |
| (unsigned long long) sc_stat_nondircnt, |
| percent, |
| elapsed); |
| } else { |
| sprintf(statline[0], |
| "status at %02d:%02d:%02d: " |
| "%ld seconds elapsed\n", |
| tmp->tm_hour, |
| tmp->tm_min, |
| tmp->tm_sec, |
| elapsed); |
| } |
| |
| assert(strlen(statline[0]) < STATLINESZ); |
| |
| /* optionally create stat lines for each drive |
| */ |
| statlinecnt = 1; |
| for (i = 0; i < drivecnt; i++) { |
| pds_t *pdsp = &sc_stat_pds[i]; |
| if (pdsp->pds_phase == PDS_NULL |
| || |
| pdsp->pds_phase == PDS_NONDIR) { |
| continue; |
| } |
| statline[statlinecnt][0] = 0; |
| if (drivecnt > 1) { |
| sprintf(statline[statlinecnt], |
| "drive %u: ", |
| (unsigned int)i); |
| } |
| switch(pdsp->pds_phase) { |
| case PDS_INOMAP: |
| strcat(statline[statlinecnt], |
| "dumping inomap"); |
| break; |
| case PDS_DIRDUMP: |
| sprintf(&statline[statlinecnt] |
| [strlen(statline[statlinecnt])], |
| "%llu/%llu directories dumped", |
| (unsigned long long)pdsp->pds_dirdone, |
| (unsigned long long)sc_stat_dircnt); |
| break; |
| case PDS_INVSYNC: |
| strcat(statline[statlinecnt], |
| "waiting to dump inventory"); |
| break; |
| case PDS_INVDUMP: |
| strcat(statline[statlinecnt], |
| "dumping inventory"); |
| break; |
| case PDS_TERMDUMP: |
| strcat(statline[statlinecnt], |
| "dumping stream terminator"); |
| break; |
| default: |
| break; |
| } |
| sprintf(&statline[statlinecnt] |
| [strlen(statline[statlinecnt])], |
| "\n"); |
| assert(strlen(statline[statlinecnt]) < STATLINESZ); |
| statlinecnt++; |
| } |
| |
| return statlinecnt; |
| } |
| |
| static bool_t |
| create_inv_session( |
| global_hdr_t *gwhdrtemplatep, |
| uuid_t *fsidp, |
| const char *mntpnt, |
| const char *fsdevice, |
| ix_t subtreecnt, |
| size_t strmix) |
| { |
| int rval; |
| char *qmntpnt; |
| char *qfsdevice; |
| |
| /* create a cleanup handler to close the inventory on exit. */ |
| rval = atexit(inv_cleanup); |
| assert(!rval); |
| |
| sc_inv_idbtoken = inv_open((inv_predicate_t)INV_BY_UUID, |
| INV_SEARCH_N_MOD, |
| (void *)fsidp); |
| if (sc_inv_idbtoken == INV_TOKEN_NULL) { |
| return BOOL_FALSE; |
| } |
| qmntpnt = (char *)calloc(1, strlen(gwhdrtemplatep->gh_hostname) |
| + 1 + strlen(mntpnt) + 1); |
| assert(qmntpnt); |
| assert(strlen(gwhdrtemplatep->gh_hostname)); |
| sprintf(qmntpnt, "%s:%s", gwhdrtemplatep->gh_hostname, mntpnt); |
| qfsdevice = (char *)calloc(1, strlen(gwhdrtemplatep->gh_hostname) |
| + 1 + strlen(fsdevice) + 1); |
| assert(qfsdevice); |
| sprintf(qfsdevice, "%s:%s", gwhdrtemplatep->gh_hostname, fsdevice); |
| |
| sc_inv_sestoken = inv_writesession_open(sc_inv_idbtoken, |
| fsidp, |
| &gwhdrtemplatep->gh_dumpid, |
| gwhdrtemplatep->gh_dumplabel, |
| subtreecnt ? BOOL_TRUE |
| : BOOL_FALSE, |
| sc_resumepr, |
| (u_char_t)sc_level, |
| drivecnt, |
| gwhdrtemplatep->gh_timestamp, |
| qmntpnt, |
| qfsdevice); |
| if (sc_inv_sestoken == INV_TOKEN_NULL) { |
| return BOOL_FALSE; |
| } |
| |
| /* open an inventory stream for each stream |
| */ |
| sc_inv_stmtokenp = (inv_stmtoken_t *) |
| calloc(drivecnt, sizeof(inv_stmtoken_t)); |
| assert(sc_inv_stmtokenp); |
| for (strmix = 0; strmix < drivecnt; strmix++) { |
| drive_t *drivep = drivepp[strmix]; |
| char *drvpath; |
| |
| if (strcmp(drivep->d_pathname, "stdio")) { |
| drvpath = path_reltoabs(drivep->d_pathname, homedir); |
| } else { |
| drvpath = drivep->d_pathname; |
| } |
| sc_inv_stmtokenp[strmix] = inv_stream_open(sc_inv_sestoken, |
| drvpath); |
| if (strcmp(drivep->d_pathname, "stdio")) { |
| free((void *)drvpath); |
| } |
| if (sc_inv_stmtokenp[strmix] == INV_TOKEN_NULL) { |
| return BOOL_FALSE; |
| } |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| static void |
| mark_set(drive_t *drivep, xfs_ino_t ino, off64_t offset, int32_t flags) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| mark_t *markp = (mark_t *)calloc(1, sizeof(mark_t)); |
| assert(markp); |
| |
| if (flags & STARTPT_FLAGS_NULL) { |
| mlog(MLOG_DEBUG, |
| "setting media NULL mark\n"); |
| } else if (flags & STARTPT_FLAGS_END) { |
| mlog(MLOG_DEBUG, |
| "setting media END mark\n"); |
| } else { |
| mlog(MLOG_DEBUG, |
| "setting media mark" |
| " for ino %llu offset %lld\n", |
| ino, |
| offset); |
| } |
| |
| markp->startpt.sp_ino = ino; |
| markp->startpt.sp_offset = offset; |
| markp->startpt.sp_flags = flags; |
| (*dop->do_set_mark)(drivep, |
| mark_callback, |
| (void *)drivep->d_index, |
| (drive_markrec_t *)markp); |
| } |
| |
| static void |
| mark_callback(void *p, drive_markrec_t *dmp, bool_t committed) |
| { |
| /* get context |
| */ |
| ix_t strmix = (ix_t)p; |
| context_t *contextp = &sc_contextp[strmix]; |
| drive_t *drivep = drivepp[strmix]; |
| drive_hdr_t *dwhdrp = drivep->d_writehdrp; |
| media_hdr_t *mwhdrp = (media_hdr_t *)dwhdrp->dh_upper; |
| content_hdr_t *cwhdrp = (content_hdr_t *)mwhdrp->mh_upper; |
| content_inode_hdr_t *scwhdrp = (content_inode_hdr_t *) |
| (void *) |
| cwhdrp->ch_specific; |
| |
| /* this is really a mark_t, allocated by mark_set() |
| */ |
| mark_t *markp = (mark_t *)dmp; |
| |
| if (committed) { |
| /* bump the per-mfile mark committed count |
| */ |
| contextp->cc_markscommitted++; |
| |
| /* copy the mark into the write header: this establishes the |
| * starting point should we need to retry the non-dir portion |
| * of the dump |
| */ |
| |
| /* log the mark commit |
| */ |
| if (markp->startpt.sp_flags & STARTPT_FLAGS_NULL) { |
| mlog(MLOG_DEBUG, |
| "media NULL mark committed" |
| " in media file %d\n", |
| mwhdrp->mh_dumpfileix); |
| scwhdrp->cih_startpt.sp_flags |= STARTPT_FLAGS_NULL; |
| } else if (markp->startpt.sp_flags & STARTPT_FLAGS_END) { |
| mlog(MLOG_DEBUG, |
| "media END mark committed" |
| " in media file %d\n", |
| mwhdrp->mh_dumpfileix); |
| if (scwhdrp->cih_endpt.sp_flags & STARTPT_FLAGS_END) { |
| scwhdrp->cih_startpt.sp_ino++; |
| scwhdrp->cih_startpt.sp_offset = 0; |
| } else { |
| scwhdrp->cih_startpt = scwhdrp->cih_endpt; |
| } |
| scwhdrp->cih_startpt.sp_flags |= STARTPT_FLAGS_END; |
| } else { |
| mlog(MLOG_DEBUG, |
| "media mark committed" |
| " for ino %llu offset %lld" |
| " in media file %d\n", |
| markp->startpt.sp_ino, |
| markp->startpt.sp_offset, |
| mwhdrp->mh_dumpfileix); |
| scwhdrp->cih_startpt = markp->startpt; |
| } |
| } else { |
| /* note the mark was not committed |
| */ |
| if (markp->startpt.sp_flags & STARTPT_FLAGS_NULL) { |
| mlog(MLOG_DEBUG, |
| "media NULL mark -NOT- committed\n"); |
| } else if (markp->startpt.sp_flags & STARTPT_FLAGS_END) { |
| mlog(MLOG_DEBUG, |
| "media END mark -NOT- committed\n"); |
| } else { |
| mlog(MLOG_DEBUG, |
| "media mark -NOT- committed" |
| " for ino %llu offset %lld\n", |
| markp->startpt.sp_ino, |
| markp->startpt.sp_offset); |
| } |
| } |
| |
| /* get rid of this mark (it was allocated by mark_set()) |
| */ |
| free((void *)markp); |
| } |
| |
| /* begin - called by stream process to invoke the dump stream |
| */ |
| int |
| content_stream_dump(ix_t strmix) |
| { |
| context_t *contextp = &sc_contextp[strmix]; |
| drive_t *drivep = drivepp[strmix]; |
| drive_hdr_t *dwhdrp = drivep->d_writehdrp; |
| media_hdr_t *mwhdrp = (media_hdr_t *)dwhdrp->dh_upper; |
| content_hdr_t *cwhdrp = (content_hdr_t *)mwhdrp->mh_upper; |
| content_inode_hdr_t *scwhdrp = (content_inode_hdr_t *) |
| cwhdrp->ch_specific; |
| void *inomap_contextp; |
| bool_t all_nondirs_committed; |
| bool_t empty_mediafile; |
| time_t elapsed; |
| inv_stmtoken_t inv_stmt; |
| struct xfs_bstat *bstatbufp; |
| const size_t bstatbuflen = BSTATBUFLEN; |
| int rval; |
| rv_t rv; |
| |
| /* sanity checks |
| */ |
| assert(RV_OK == 0); /* bigstat_iter depends on this */ |
| |
| /* allocate a buffer for use by bstat_iter |
| */ |
| bstatbufp = (struct xfs_bstat *)calloc(bstatbuflen, |
| sizeof(struct xfs_bstat)); |
| assert(bstatbufp); |
| |
| /* allocate an inomap context */ |
| inomap_contextp = inomap_alloc_context(); |
| assert(inomap_contextp); |
| |
| /* determine if stream terminators will be used and are expected. |
| * this will be revised each time a new media file is begun. |
| */ |
| update_cc_Media_useterminatorpr(drivep, contextp); |
| |
| /* check in |
| */ |
| lock(); |
| sc_thrdsarrivedcnt++; |
| unlock(); |
| |
| /* fill in write hdr stream start and end points |
| */ |
| scwhdrp->cih_startpt = sc_startptp[strmix]; |
| if (strmix < drivecnt - 1) { |
| scwhdrp->cih_endpt = sc_startptp[strmix + 1]; |
| } else { |
| scwhdrp->cih_endpt.sp_flags = STARTPT_FLAGS_END; |
| } |
| |
| // the first stream dumps the directories |
| if (strmix == 0) { |
| scwhdrp->cih_dumpattr |= CIH_DUMPATTR_DIRDUMP; |
| } |
| |
| /* fill in inomap fields of write hdr |
| */ |
| inomap_writehdr(scwhdrp); |
| |
| /* used to decide if any non-dirs not yet on media |
| */ |
| all_nondirs_committed = BOOL_FALSE; |
| |
| /* used to guarantee we don't count the same ino more than once |
| * in the progress stats |
| */ |
| contextp->cc_stat_lastino = 0; |
| |
| /* used to detect generation of an empty media file; |
| * contains at most an inomap and dirdump and null file hdr. |
| */ |
| empty_mediafile = BOOL_FALSE; |
| |
| /* get the inventory stream token |
| */ |
| if (sc_inv_stmtokenp) { |
| inv_stmt = sc_inv_stmtokenp[strmix]; |
| } else { |
| inv_stmt = INV_TOKEN_NULL; |
| } |
| |
| /* loop, dumping media files, until the entire stream is dumped. |
| * each time we hit EOM/EOF, repeat the inomap and directory dump. |
| * dump the non-dirs beginning with the current startpoint. |
| * The current startpoint will be updated each time a media mark |
| * is committed. |
| */ |
| for (;;) { |
| xfs_ino_t startino; |
| bool_t stop_requested; |
| bool_t hit_eom; |
| bool_t all_dirs_committed; |
| bool_t all_nondirs_sent; |
| off64_t startoffset; |
| off64_t ncommitted; |
| bool_t done; |
| |
| /* used to decide whether or not to go back for more. |
| */ |
| stop_requested = BOOL_FALSE; |
| |
| /* TRUE if hit EOM while dumping |
| */ |
| hit_eom = BOOL_FALSE; |
| |
| /* used to decide if the media file contains all |
| * of the inomap and dirdump. |
| */ |
| all_dirs_committed = BOOL_FALSE; |
| |
| /* used to decide if all non-dirs were sent (not necessarily |
| * committed) |
| */ |
| all_nondirs_sent = BOOL_FALSE; |
| |
| /* always clear the NULL flag from the stream startpoint |
| * before beginning the media file. allows detection |
| * of null file hdr commit. |
| */ |
| scwhdrp->cih_startpt.sp_flags &= ~STARTPT_FLAGS_NULL; |
| |
| /* save the original start points, to be given to |
| * the inventory at the end of each media file. |
| */ |
| startino = scwhdrp->cih_startpt.sp_ino; |
| startoffset = scwhdrp->cih_startpt.sp_offset; |
| |
| /* set the accumulated file size to zero. |
| * this will be monitored by dump_file() to decide |
| * if the current dump file is too long. if so, |
| * it will set a startpoint and spoof an EOF. |
| * this will cause a new dump file to be started, |
| * beginning at the startpoint. |
| */ |
| contextp->cc_mfilesz = 0; |
| |
| /* tell the Media abstraction to position a media object |
| * and begin a new media file. This will dump the media |
| * file header if successful. |
| */ |
| rv = Media_mfile_begin(drivep, contextp, BOOL_TRUE); |
| if (rv == RV_INTR) { |
| return mlog_exit(EXIT_INTERRUPT, rv); |
| } |
| if (rv == RV_TIMEOUT) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "media change timeout will be treated as " |
| "a request to stop using drive: " |
| "can resume later\n")); |
| mlog_exit_hint(RV_QUIT); |
| return mlog_exit(EXIT_NORMAL, rv); |
| } |
| if (rv == RV_QUIT) { |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "media change decline will be treated as " |
| "a request to stop using drive: " |
| "can resume later\n")); |
| mlog_exit_hint(RV_QUIT); |
| return mlog_exit(EXIT_NORMAL, rv); |
| } |
| if (rv == RV_DRIVE) { |
| return mlog_exit(EXIT_NORMAL, rv); |
| } |
| if (rv == RV_ERROR) { |
| return mlog_exit(EXIT_ERROR, rv); |
| } |
| if (rv == RV_CORE) { |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| assert(rv == RV_OK); |
| if (rv != RV_OK) { |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| |
| /* sync up here with other streams if reasonable |
| */ |
| mlog(MLOG_VERBOSE, _( |
| "creating dump session media file %u " |
| "(media %u, file %u)\n"), |
| mwhdrp->mh_dumpfileix, |
| mwhdrp->mh_mediaix, |
| mwhdrp->mh_mediafileix); |
| |
| /* initialize the count of marks committed in the media file. |
| * will be bumped by mark_callback(). |
| */ |
| contextp->cc_markscommitted = 0; |
| |
| /* first dump the inomap |
| */ |
| mlog(MLOG_VERBOSE, _( |
| "dumping ino map\n")); |
| sc_stat_pds[strmix].pds_phase = PDS_INOMAP; |
| rv = inomap_dump(drivep); |
| if (rv == RV_INTR) { |
| stop_requested = BOOL_TRUE; |
| goto decision_more; |
| } |
| if (rv == RV_EOM) { |
| hit_eom = BOOL_TRUE; |
| goto decision_more; |
| } |
| if (rv == RV_DRIVE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_NORMAL, rv); |
| } |
| if (rv == RV_ERROR) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_ERROR, rv); |
| } |
| if (rv == RV_CORE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| assert(rv == RV_OK); |
| if (rv != RV_OK) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| |
| /* now dump the directories, if this is a stream that dumps |
| * directories. use the bigstat iterator capability to call |
| * my dump_dir function for each directory in the bitmap. |
| */ |
| if (scwhdrp->cih_dumpattr & CIH_DUMPATTR_DIRDUMP) { |
| sc_stat_pds[strmix].pds_dirdone = 0; |
| rv = dump_dirs(strmix, |
| bstatbufp, |
| bstatbuflen, |
| inomap_contextp); |
| if (rv == RV_INTR) { |
| stop_requested = BOOL_TRUE; |
| goto decision_more; |
| } |
| if (rv == RV_EOM) { |
| hit_eom = BOOL_TRUE; |
| goto decision_more; |
| } |
| if (rv == RV_DRIVE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_NORMAL, rv); |
| } |
| if (rv == RV_ERROR) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_ERROR, rv); |
| } |
| if (rv == RV_CORE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| assert(rv == RV_OK); |
| if (rv != RV_OK) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| } |
| |
| /* finally, dump the non-directory files beginning with this |
| * stream's startpoint. Note that dump_file will set one or |
| * more media marks; the callback will update the hdr's |
| * startpoint; thus each time a demarcated portion of a |
| * non-directory file is fully committed to media, |
| * the starting point for the next media file will be advanced. |
| */ |
| if (!all_nondirs_committed) { |
| mlog(MLOG_VERBOSE, _( |
| "dumping non-directory files\n")); |
| sc_stat_pds[strmix].pds_phase = PDS_NONDIR; |
| rv = RV_OK; |
| inomap_reset_context(inomap_contextp); |
| rval = bigstat_iter(sc_fshandlep, |
| sc_fsfd, |
| BIGSTAT_ITER_NONDIR, |
| scwhdrp->cih_startpt.sp_ino, |
| (bstat_cbfp_t)dump_file, |
| (void *)strmix, |
| inomap_next_nondir, |
| inomap_contextp, |
| (int *)&rv, |
| pipeline ? |
| (bool_t (*)(int))preemptchk : 0, |
| bstatbufp, |
| bstatbuflen); |
| if (rval) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_FAULT, RV_CORE); |
| } |
| if (rv == RV_INTR) { |
| stop_requested = BOOL_TRUE; |
| goto decision_more; |
| } |
| if (rv == RV_EOM) { |
| hit_eom = BOOL_TRUE; |
| goto decision_more; |
| } |
| if (rv == RV_EOF) { |
| goto decision_more; |
| } |
| if (rv == RV_DRIVE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_NORMAL, rv); |
| } |
| if (rv == RV_ERROR) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_ERROR, rv); |
| } |
| if (rv == RV_CORE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| assert(rv == RV_OK || rv == RV_NOMORE); |
| if (rv != RV_OK && rv != RV_NOMORE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| } |
| |
| /* if we got here, all files were sent without hitting |
| * the end of the current media object, or hitting the |
| * media file size limit. send the special END mark. |
| * this is only send at the end of the last media file in the |
| * dump session. if it actually gets committed to media, |
| * we know the last data written to media all made it. |
| * we won't know if this mark is committed to media until |
| * we attempt to end the write stream. |
| */ |
| all_nondirs_sent = BOOL_TRUE; |
| mark_set(drivep, |
| INO64MAX, |
| OFF64MAX, |
| STARTPT_FLAGS_END); |
| |
| decision_more: |
| /* write a null file hdr, to let restore recognize |
| * the end of the media file. the flags indicate |
| * whether or not this is intended to be the last |
| * media file in the stream. don't bother if we hit |
| * EOM. |
| */ |
| if (!hit_eom) { |
| rv = dump_filehdr(drivep, |
| contextp, |
| 0, |
| 0, |
| all_nondirs_sent |
| ? |
| (FILEHDR_FLAGS_NULL |
| | |
| FILEHDR_FLAGS_END) |
| : |
| FILEHDR_FLAGS_NULL); |
| if (rv == RV_DRIVE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_NORMAL, rv); |
| } |
| if (rv == RV_CORE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| if (rv == RV_ERROR) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_ERROR, rv); |
| } |
| |
| /* send a mark to detect if the null file header made |
| * it. mark callback will adjust start pt before this |
| * call returns if the null file header made it. |
| */ |
| mark_set(drivep, |
| INO64MAX, |
| OFF64MAX, |
| all_nondirs_sent |
| ? |
| STARTPT_FLAGS_NULL | STARTPT_FLAGS_END |
| : |
| STARTPT_FLAGS_NULL); |
| } |
| |
| /* tell the Media abstraction to end the media file. |
| * this is done before the inventory update, to |
| * see how much was actually committed to media. |
| * will invoke drive end_write, which will flush |
| * all pending marks. |
| */ |
| mlog(MLOG_VERBOSE, _( |
| "ending media file\n")); |
| ncommitted = 0; |
| rv = Media_mfile_end(drivep, |
| contextp, |
| mwhdrp, |
| &ncommitted, |
| hit_eom); |
| if (rv == RV_DRIVE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_NORMAL, rv); |
| } |
| if (rv == RV_CORE) { |
| free((void *)bstatbufp); |
| return mlog_exit(EXIT_FAULT, rv); |
| } |
| mlog(MLOG_VERBOSE, _( |
| "media file size %lld bytes\n"), |
| ncommitted); |
| |
| /* if at least one mark committed, we know all of |
| * the inomap and dirdump was committed. |
| */ |
| all_dirs_committed = (contextp->cc_markscommitted > 0); |
| |
| /* at this point we can check the new start point |
| * to determine if all nondirs have been committed. |
| * if this flag was already set, then this is a |
| * inomap and dirdump-only media file. |
| */ |
| if (scwhdrp->cih_startpt.sp_flags & STARTPT_FLAGS_END) { |
| if (all_nondirs_committed) { |
| empty_mediafile = BOOL_TRUE; |
| } |
| all_nondirs_committed = BOOL_TRUE; |
| } |
| |
| /* we are done if all nondirs have been committed. |
| * it is not necessary for the null file header to have |
| * been committed. |
| */ |
| done = all_nondirs_committed; |
| |
| /* tell the inventory about the media file |
| */ |
| if (inv_stmt != INV_TOKEN_NULL) { |
| bool_t ok; |
| |
| if (!all_dirs_committed) { |
| mlog(MLOG_DEBUG, |
| "giving inventory " |
| "partial dirdump media file\n"); |
| } else if (done && empty_mediafile) { |
| mlog(MLOG_DEBUG, |
| "giving inventory " |
| "empty last media file: " |
| "%llu:%lld\n", |
| startino, |
| startoffset); |
| } else if (empty_mediafile) { |
| mlog(MLOG_DEBUG, |
| "giving inventory " |
| "empty media file: " |
| "%llu:%lld\n", |
| startino, |
| startoffset); |
| } else if (done) { |
| mlog(MLOG_DEBUG, |
| "giving inventory " |
| "last media file: " |
| "%llu:%lld\n", |
| startino, |
| startoffset); |
| } else { |
| mlog(MLOG_DEBUG, |
| "giving inventory " |
| "media file: " |
| "%llu:%lld - %llu:%lld\n", |
| startino, |
| startoffset, |
| scwhdrp->cih_startpt.sp_ino, |
| scwhdrp->cih_startpt.sp_offset); |
| } |
| |
| /* already thread-safe, don't need to lock |
| */ |
| ok = inv_put_mediafile(inv_stmt, |
| &mwhdrp->mh_mediaid, |
| mwhdrp->mh_medialabel, |
| (uint)mwhdrp->mh_mediafileix, |
| startino, |
| startoffset, |
| scwhdrp->cih_startpt.sp_ino, |
| scwhdrp->cih_startpt.sp_offset, |
| ncommitted, |
| all_dirs_committed |
| && |
| !empty_mediafile, |
| BOOL_FALSE); |
| if (!ok) { |
| mlog(MLOG_NORMAL, _( |
| "inventory media file put failed\n")); |
| } |
| } |
| if (done) { |
| contextp->cc_completepr = BOOL_TRUE; |
| /* so inv_end_stream and main will know |
| */ |
| } |
| |
| /* don't go back for more if done or stop was requested |
| */ |
| if (done || stop_requested) { |
| break; |
| } |
| } /* end main dump loop */ |
| |
| /* check in |
| */ |
| lock(); |
| sc_thrdsdonecnt++; |
| unlock(); |
| |
| /* dump the session inventory and terminator here, if the drive |
| * supports multiple media files. must wait until all |
| * streams have completed or given up, so all media files |
| * from all streams have been registered. |
| */ |
| if (drivep->d_capabilities & DRIVE_CAP_FILES) { |
| if (stream_cnt() > 1) { |
| mlog(MLOG_VERBOSE, _( |
| "waiting for synchronized " |
| "session inventory dump\n")); |
| sc_stat_pds[strmix].pds_phase = PDS_INVSYNC; |
| } |
| |
| /* first be sure all threads have begun |
| */ |
| while (sc_thrdsarrivedcnt < drivecnt) { |
| sleep(1); |
| } |
| /* now wait for survivors to checkin |
| */ |
| while (sc_thrdsdonecnt < stream_cnt()) { |
| sleep(1); |
| } |
| /* proceeed |
| */ |
| sc_stat_pds[strmix].pds_phase = PDS_INVDUMP; |
| if (dump_session_inv(drivep, contextp, mwhdrp, scwhdrp)) { |
| sc_stat_pds[strmix].pds_phase = PDS_TERMDUMP; |
| dump_terminator(drivep, contextp, mwhdrp); |
| } |
| } |
| |
| sc_stat_pds[strmix].pds_phase = PDS_NULL; |
| |
| free((void *)bstatbufp); |
| |
| elapsed = time(0) - sc_stat_starttime; |
| |
| mlog(MLOG_TRACE, _( |
| "ending stream: %ld seconds elapsed\n"), |
| elapsed); |
| |
| return mlog_exit(EXIT_NORMAL, rv); |
| } |
| |
| /* indicates if the dump was complete. |
| * easy to tell: initially contextp->cc_completepr is false for each stream. |
| * only set true if stream complete. if any stream NOT complete, |
| * dump is not complete. |
| */ |
| bool_t |
| content_complete(void) |
| { |
| time_t elapsed; |
| bool_t completepr; |
| int i; |
| |
| completepr = check_complete_flags(); |
| |
| elapsed = time(0) - sc_stat_starttime; |
| |
| mlog(MLOG_VERBOSE, _( |
| "dump size (non-dir files) : %llu bytes\n"), |
| sc_stat_datadone); |
| |
| if (completepr) { |
| if(sc_savequotas) { |
| for(i = 0; i < (sizeof(quotas) / sizeof(quotas[0])); i++) { |
| if(quotas[i].savequotas && unlink(quotas[i].quotapath) < 0) { |
| mlog(MLOG_ERROR, _( |
| "unable to remove %s: %s\n"), |
| quotas[i].quotapath, |
| strerror (errno)); |
| } |
| } |
| } |
| |
| mlog(MLOG_VERBOSE, _( |
| "dump complete" |
| ": %ld seconds elapsed" |
| "\n"), |
| elapsed); |
| } else { |
| if (sc_inv_updatepr) { |
| mlog(MLOG_VERBOSE | MLOG_NOTE, _( |
| "dump interrupted" |
| ": %ld seconds elapsed" |
| ": may resume later using -%c option" |
| "\n"), |
| elapsed, |
| GETOPT_RESUME); |
| mlog_exit_hint(RV_INTR); |
| } else { |
| mlog(MLOG_VERBOSE | MLOG_NOTE, _( |
| "dump interrupted" |
| ": %ld seconds elapsed" |
| "\n"), |
| elapsed); |
| mlog_exit_hint(RV_INTR); |
| } |
| } |
| return completepr; |
| } |
| |
| #define PREAMBLEMAX 3 |
| #define QUERYMAX 1 |
| #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; |
| size_t maxdrvchoiceix; |
| size_t nochangeix; |
| size_t responseix; |
| ix_t thrdix; |
| |
| querycnt = 0; |
| querystr[querycnt++ ] = "select a drive to acknowledge media change\n"; |
| choicecnt = 0; |
| maxdrvchoiceix = 0; |
| for (thrdix = 0; thrdix < STREAM_SIMMAX; thrdix++) { |
| if (sc_mcflag[thrdix]) { |
| choicetothrdmap[choicecnt].thrdix = thrdix; |
| sprintf(choicetothrdmap[choicecnt].choicestr, |
| "drive %u", |
| (unsigned int)thrdix); |
| choicestr[choicecnt] = |
| choicetothrdmap[choicecnt].choicestr; |
| maxdrvchoiceix = choicecnt; |
| choicecnt++; |
| } |
| } |
| 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_MEDIA, |
| 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"; |
| } |
| assert(responseix == nochangeix); |
| return "continuing\n"; |
| } |
| |
| |
| static void |
| update_cc_Media_useterminatorpr(drive_t *drivep, context_t *contextp) |
| { |
| int dcaps = drivep->d_capabilities; |
| |
| contextp->cc_Media_useterminatorpr = BOOL_TRUE; |
| if (!(dcaps & DRIVE_CAP_FILES)) { |
| contextp->cc_Media_useterminatorpr = BOOL_FALSE; |
| } |
| if (!(dcaps & DRIVE_CAP_OVERWRITE)) { |
| contextp->cc_Media_useterminatorpr = BOOL_FALSE; |
| } |
| if (!(dcaps & DRIVE_CAP_BSF)) { |
| contextp->cc_Media_useterminatorpr = BOOL_FALSE; |
| } |
| if (!(dcaps & DRIVE_CAP_APPEND)) { |
| contextp->cc_Media_useterminatorpr = BOOL_FALSE; |
| } |
| } |
| |
| static rv_t |
| dump_dirs(ix_t strmix, |
| struct xfs_bstat *bstatbufp, |
| size_t bstatbuflen, |
| void *inomap_contextp) |
| { |
| xfs_ino_t lastino; |
| size_t bulkstatcallcnt; |
| struct xfs_fsop_bulkreq bulkreq; |
| |
| inomap_reset_context(inomap_contextp); |
| |
| /* begin iteration at ino zero |
| */ |
| lastino = 0; |
| for (bulkstatcallcnt = 0 ; ; bulkstatcallcnt++) { |
| struct xfs_bstat *p; |
| struct xfs_bstat *endp; |
| __s32 buflenout; |
| int rval; |
| |
| if (bulkstatcallcnt == 0) { |
| mlog(MLOG_VERBOSE, _( |
| "dumping directories\n")); |
| } |
| sc_stat_pds[strmix].pds_phase = PDS_DIRDUMP; |
| |
| /* check for interruption |
| */ |
| if (cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| |
| /* get a bunch of bulkstats |
| */ |
| mlog(MLOG_NITTY, |
| "dump_dirs SGI_FS_BULKSTAT %u buf len %u\n", |
| bulkstatcallcnt, |
| bstatbuflen); |
| |
| bulkreq.lastip = (__u64 *)&lastino; |
| bulkreq.icount = bstatbuflen; |
| bulkreq.ubuffer = bstatbufp; |
| bulkreq.ocount = &buflenout; |
| |
| rval = ioctl(sc_fsfd, XFS_IOC_FSBULKSTAT, &bulkreq); |
| |
| if (rval) { |
| mlog(MLOG_NORMAL, _( |
| "SGI_FS_BULKSTAT failed: " |
| "%s (%d)\n"), |
| strerror(errno), |
| errno); |
| return RV_ERROR; |
| } |
| mlog(MLOG_NITTY, |
| "dump_dirs SGI_FS_BULKSTAT returns %d entries\n", |
| buflenout); |
| |
| /* check if done |
| */ |
| if (buflenout == 0) { |
| return RV_OK; |
| } |
| |
| /* step through each node, dumping if |
| * appropriate |
| */ |
| for (p = bstatbufp, endp = bstatbufp + buflenout |
| ; |
| p < endp |
| ; |
| p++) { |
| rv_t rv; |
| |
| if (p->bs_ino == 0) |
| continue; |
| |
| if (!p->bs_nlink || !p->bs_mode) { |
| /* inode being modified, get synced data */ |
| mlog(MLOG_NITTY, |
| "ino %llu needs second bulkstat\n", |
| p->bs_ino); |
| |
| if (bigstat_one(sc_fsfd, p->bs_ino, p) < 0) { |
| mlog(MLOG_WARNING, _( |
| "failed to get bulkstat information for inode %llu\n"), |
| p->bs_ino); |
| continue; |
| } |
| if (!p->bs_nlink || !p->bs_mode || !p->bs_ino) { |
| mlog(MLOG_TRACE, |
| "failed to get valid bulkstat information for inode %llu\n", |
| p->bs_ino); |
| continue; |
| } |
| } |
| if ((p->bs_mode & S_IFMT) != S_IFDIR) { |
| continue; |
| } |
| |
| rv = dump_dir(strmix, sc_fshandlep, sc_fsfd, p); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| } |
| |
| lastino = inomap_next_dir(inomap_contextp, lastino); |
| if (lastino == INO64MAX) { |
| mlog(MLOG_DEBUG, "bulkstat seeked to EOS\n"); |
| return 0; |
| } |
| |
| mlog(MLOG_DEBUG, "bulkstat seeked to %llu\n", lastino); |
| |
| lastino = (lastino > 0) ? lastino - 1 : 0; |
| } |
| /* NOTREACHED */ |
| } |
| |
| static rv_t |
| dump_dir(ix_t strmix, |
| jdm_fshandle_t *fshandlep, |
| int fsfd, |
| struct xfs_bstat *statp) |
| { |
| context_t *contextp = &sc_contextp[strmix]; |
| drive_t *drivep = drivepp[strmix]; |
| void *inomap_contextp = contextp->cc_inomap_contextp; |
| int state; |
| int fd; |
| struct dirent *gdp = (struct dirent *)contextp->cc_getdentsbufp; |
| size_t gdsz = contextp->cc_getdentsbufsz; |
| int gdcnt; |
| gen_t gen; |
| rv_t rv; |
| |
| /* no way this can be non-dir, but check anyway |
| */ |
| assert((statp->bs_mode & S_IFMT) == S_IFDIR); |
| if ((statp->bs_mode & S_IFMT) != S_IFDIR) { |
| return RV_OK; |
| } |
| |
| /* skip if no links |
| */ |
| if (statp->bs_nlink < 1) { |
| return RV_OK; |
| } |
| |
| /* see what the inomap says about this ino |
| */ |
| state = inomap_get_state(inomap_contextp, statp->bs_ino); |
| |
| /* skip if not in inomap |
| */ |
| if (state == MAP_INO_UNUSED |
| || |
| state == MAP_DIR_NOCHNG |
| || |
| state == MAP_NDR_NOCHNG) { |
| if (state == MAP_NDR_NOCHNG) { |
| mlog(MLOG_DEBUG, |
| "inomap inconsistency ino %llu: " |
| "map says is non-dir but is dir: skipping\n", |
| statp->bs_ino); |
| } |
| return RV_OK; |
| } |
| |
| /* note if map says a non-dir |
| */ |
| if (state == MAP_NDR_CHANGE) { |
| mlog(MLOG_DEBUG, |
| "inomap inconsistency ino %llu: " |
| "map says non-dir but is dir: skipping\n", |
| statp->bs_ino); |
| return RV_OK; |
| } |
| |
| /* bump the stats now. a bit early, but fewer lines of code |
| */ |
| sc_stat_pds[strmix].pds_dirdone++; |
| |
| /* if bulkstat ino# occupied more than 32 bits and |
| * linux ino# for getdents is 32 bits then |
| * warn and skip. |
| */ |
| if (statp->bs_ino > (xfs_ino_t)INOMAX) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "unable to dump directory: ino %llu too large\n"), |
| statp->bs_ino); |
| return RV_OK; /* continue anyway */ |
| } |
| |
| mlog(MLOG_TRACE, |
| "dumping directory ino %llu\n", |
| statp->bs_ino); |
| |
| /* open the directory named by statp |
| */ |
| fd = jdm_open(fshandlep, statp, O_RDONLY); |
| if (fd < 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "unable to open directory: ino %llu: %s\n"), |
| statp->bs_ino, strerror(errno)); |
| return RV_OK; /* continue anyway */ |
| } |
| |
| /* dump the file header. |
| */ |
| rv = dump_filehdr(drivep, contextp, statp, 0, 0); |
| if (rv != RV_OK) { |
| close(fd); |
| return rv; |
| } |
| |
| /* dump dirents - lots of buffering done here, to achieve OS- |
| * independence. if proves to be to much overhead, can streamline. |
| */ |
| for (gdcnt = 1, rv = RV_OK; rv == RV_OK; gdcnt++) { |
| struct dirent *p; |
| int nread; |
| register size_t reclen; |
| |
| nread = getdents_wrap(fd, (char *)gdp, gdsz); |
| |
| /* negative count indicates something very bad happened; |
| * try to gracefully end this dir. |
| */ |
| if (nread < 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "unable to read dirents (%d) for " |
| "directory ino %llu: %s\n"), |
| gdcnt, |
| statp->bs_ino, |
| strerror(errno)); |
| /* !!! curtis looked at this, and pointed out that |
| * we could take some recovery action here. if the |
| * errno is appropriate, lseek64 to the value of |
| * doff field of the last dirent successfully |
| * obtained, and contiue the loop. |
| */ |
| nread = 0; /* pretend we are done */ |
| } |
| |
| /* no more directory entries: break; |
| */ |
| if (nread == 0) { |
| break; |
| } |
| |
| /* translate and dump each entry: skip "." and ".." |
| * and null entries. |
| */ |
| for (p = gdp, |
| reclen = (size_t)p->d_reclen |
| ; |
| nread > 0 |
| ; |
| nread -= (int)reclen, |
| assert(nread >= 0), |
| p = (struct dirent *)((char *)p + reclen), |
| reclen = (size_t)p->d_reclen) { |
| xfs_ino_t ino; |
| register size_t namelen = strlen(p->d_name); |
| #ifdef DEBUG |
| register size_t nameszmax = (size_t)reclen |
| - |
| offsetofmember(struct dirent, |
| d_name); |
| |
| /* getdents(2) guarantees that the string will |
| * be null-terminated, but the record may have |
| * padding after the null-termination. |
| */ |
| assert(namelen < nameszmax); |
| #endif |
| |
| /* skip "." and ".." |
| */ |
| if (*(p->d_name + 0) == '.' |
| && |
| (*(p->d_name + 1) == 0 |
| || |
| (*(p->d_name + 1) == '.' |
| && |
| *(p->d_name + 2) == 0))) { |
| continue; |
| } |
| |
| ino = (xfs_ino_t)p->d_ino; |
| |
| if (ino == 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "encountered 0 ino (%s) in " |
| "directory ino %llu: NOT dumping\n"), |
| p->d_name, |
| statp->bs_ino); |
| continue; |
| } |
| |
| /* lookup the gen number in the ino-to-gen map. |
| * if it's not there, we have to get it the slow way. |
| */ |
| if (inomap_get_gen(NULL, p->d_ino, &gen)) { |
| struct xfs_bstat statbuf; |
| int scrval; |
| |
| scrval = bigstat_one(fsfd, |
| p->d_ino, |
| &statbuf); |
| if (scrval) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "could not stat " |
| "dirent %s ino %llu: %s: " |
| "using null generation count " |
| "in directory entry\n"), |
| p->d_name, |
| (xfs_ino_t)p->d_ino, |
| strerror(errno)); |
| gen = 0; |
| } else { |
| gen = statbuf.bs_gen; |
| } |
| } |
| |
| rv = dump_dirent(drivep, |
| contextp, |
| statp, |
| ino, |
| gen, |
| p->d_name, |
| namelen); |
| if (rv != RV_OK) { |
| break; |
| } |
| } |
| } |
| |
| /* write a null dirent hdr, unless trouble encountered in the loop |
| */ |
| if (rv == RV_OK) { |
| rv = dump_dirent(drivep, contextp, statp, 0, 0, 0, 0); |
| } |
| |
| if (rv == RV_OK |
| && |
| sc_dumpextattrpr |
| && |
| (statp->bs_xflags & XFS_XFLAG_HASATTR)) { |
| rv = dump_extattrs(drivep, contextp, fshandlep, statp); |
| } |
| |
| close(fd); |
| |
| /* if an error occurred, just return the error |
| */ |
| return rv; |
| } |
| |
| static rv_t |
| dump_extattrs(drive_t *drivep, |
| context_t *contextp, |
| jdm_fshandle_t *fshandlep, |
| struct xfs_bstat *statp) |
| { |
| ix_t pass; |
| int flag; |
| attrlist_cursor_t cursor; |
| rv_t rv; |
| bool_t abort; |
| |
| /* dump a file header specially marked as heading extended attributes |
| */ |
| mlog(MLOG_NITTY, |
| "dumping %s ino %llu extended attributes filehdr\n", |
| FILETYPE(statp), |
| statp->bs_ino); |
| |
| rv = dump_filehdr(drivep, contextp, statp, 0, FILEHDR_FLAGS_EXTATTR); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| |
| /* loop three times: once for the non-root, once for root, and |
| * again for the secure attributes. |
| */ |
| for (pass = 0; pass < 3; pass++) { |
| bool_t more; |
| |
| if (pass == 0) |
| flag = 0; |
| else if (pass == 1) |
| flag = ATTR_ROOT; |
| else |
| flag = ATTR_SECURE; |
| |
| mlog(MLOG_NITTY, |
| "dumping %s extended attributes for %s ino %llu\n", |
| EXTATTR_NAMESPACE(flag), |
| FILETYPE(statp), |
| statp->bs_ino); |
| |
| /* loop dumping the extended attributes from the namespace |
| * selected by the outer loop |
| */ |
| memset(&cursor, 0, sizeof(cursor)); |
| more = BOOL_FALSE; |
| do { |
| attrlist_t *listp; |
| int rval = 0; |
| |
| rval = jdm_attr_list(fshandlep, statp, |
| contextp->cc_extattrlistbufp, |
| (int)contextp->cc_extattrlistbufsz, |
| flag, &cursor); |
| if (rval) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "could not get list of %s attributes for " |
| "%s ino %llu: %s (%d)\n"), |
| EXTATTR_NAMESPACE(flag), |
| FILETYPE(statp), |
| statp->bs_ino, |
| strerror(errno), |
| errno); |
| break; |
| } |
| |
| listp = (attrlist_t *)contextp->cc_extattrlistbufp; |
| more = listp->al_more; |
| |
| abort = BOOL_FALSE; |
| rv = dump_extattr_list(drivep, |
| contextp, |
| fshandlep, |
| statp, |
| listp, |
| flag, |
| &abort); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| } while (more && !abort); |
| } |
| |
| /* finally, dump a dummy extattr hdr so restore will know |
| * we're done. |
| */ |
| /*DBG*/mlog(MLOG_NITTY, |
| "dumping NULL extattrhdr\n"); |
| rv = dump_extattrhdr(drivep, |
| contextp, |
| statp, |
| EXTATTRHDR_SZ, |
| 0, |
| EXTATTRHDR_FLAGS_NULL, |
| 0); |
| return rv; |
| } |
| |
| static rv_t |
| dump_extattr_list(drive_t *drivep, |
| context_t *contextp, |
| jdm_fshandle_t *fshandlep, |
| struct xfs_bstat *statp, |
| attrlist_t *listp, |
| int flag, |
| bool_t *abortprp) |
| { |
| size_t listlen = (size_t)listp->al_count; |
| ix_t nameix; |
| char *dumpbufp; |
| char *endp; |
| size_t bufsz; |
| int rval = 0; |
| rv_t rv; |
| char *dumpbufendp = contextp->cc_extattrdumpbufp |
| + |
| contextp->cc_extattrdumpbufsz; |
| |
| /* sanity checks |
| */ |
| assert(listp->al_count >= 0); |
| |
| /* fill up a retrieve array and build a dump buffer; |
| * can run out of entries in the name list, space in the |
| * retrieve buffer, or space in the dump buffer |
| */ |
| dumpbufp = contextp->cc_extattrdumpbufp; |
| endp = dumpbufp; |
| for (nameix = 0; nameix < listlen;) { |
| ix_t rtrvix; |
| size_t rtrvcnt; |
| |
| rtrvix = 0; |
| while (nameix < listlen && rtrvix < EXTATTR_RTRVARRAY_LEN) { |
| attrlist_ent_t *entp; |
| char *valuep; |
| attr_multiop_t *opp; |
| |
| entp = ATTR_ENTRY(listp, nameix); |
| opp = &contextp->cc_extattrrtrvarrayp[rtrvix]; |
| |
| /* Offer the HSM a chance to avoid dumping certain |
| * attributes. |
| */ |
| |
| if (hsm_fs_ctxtp) { |
| int skip_entry; |
| |
| if (!HsmFilterExistingAttribute( |
| contextp->cc_hsm_f_ctxtp, entp->a_name, |
| entp->a_valuelen, flag, |
| &skip_entry)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "HSM could not filter %s " |
| "attribute %s for %s ino %llu\n"), |
| EXTATTR_NAMESPACE(flag), |
| entp->a_name, |
| FILETYPE(statp), |
| statp->bs_ino); |
| *abortprp = BOOL_TRUE; |
| return RV_OK; |
| } |
| if (skip_entry) { |
| nameix++; |
| continue; |
| } |
| } |
| |
| dumpbufp = dump_extattr_buildrecord(statp, |
| dumpbufp, |
| dumpbufendp, |
| entp->a_name, |
| entp->a_valuelen, |
| flag, |
| &valuep); |
| if (dumpbufp > dumpbufendp) { |
| break; /* won't fit in buffer */ |
| } |
| if (valuep != NULL) { /* if added to dump buffer */ |
| endp = dumpbufp; |
| opp->am_attrname = entp->a_name; |
| opp->am_attrvalue = valuep; |
| opp->am_length = (int)entp->a_valuelen; |
| opp->am_flags = flag; |
| opp->am_error = 0; |
| opp->am_opcode = ATTR_OP_GET; |
| rtrvix++; |
| } |
| nameix++; |
| } |
| |
| /* Either the retrieve buffer is full, the dump buffer is full, |
| * or we just put the last attribute into the dump buffer. In |
| * any case, fill in the values for any attributes added so far. |
| */ |
| |
| rtrvcnt = rtrvix; |
| if (rtrvcnt > 0) { |
| rval = jdm_attr_multi(fshandlep, statp, |
| (void *)contextp->cc_extattrrtrvarrayp, |
| (int)rtrvcnt, |
| 0); |
| if (rval) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "could not retrieve %s attributes for " |
| "%s ino %llu: %s (%d)\n"), |
| EXTATTR_NAMESPACE(flag), |
| FILETYPE(statp), |
| statp->bs_ino, |
| strerror(errno), |
| errno); |
| *abortprp = BOOL_TRUE; |
| return RV_OK; |
| } |
| |
| for (rtrvix = 0; rtrvix < rtrvcnt; rtrvix++) { |
| attr_multiop_t *opp; |
| opp = &contextp->cc_extattrrtrvarrayp[rtrvix]; |
| if (opp->am_error) { |
| if (opp->am_error == ENOATTR && |
| flag & ATTR_SECURE) { |
| /* Security attributes are supported by |
| * the kernel but jdm_attr_multi() returns |
| * ENOATTR for every 'user' space attribute |
| * during the 'security' pass of the extended |
| * attribute loop (pass==3). Suppress the |
| * following error message with a no-op. The |
| * jdm_attr_multi() problem is fixed in mod |
| * xfs-linux:xfs-kern:167038a (PV 907903). |
| */ |
| continue; |
| } |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "attr_multi indicates error while " |
| "retrieving %s attribute [%s] for " |
| "%s ino %llu: %s (%d)\n"), |
| EXTATTR_NAMESPACE(flag), |
| opp->am_attrname, |
| FILETYPE(statp), |
| statp->bs_ino, |
| strerror(opp->am_error), |
| opp->am_error); |
| } |
| } |
| } |
| |
| /* The values for all attributes in the dump buffer have been |
| * filled in. If the dump buffer isn't full yet, let's wait |
| * and put some more attributes in. |
| */ |
| |
| if (dumpbufp <= dumpbufendp) |
| continue; /* no buffer overflow yet */ |
| |
| assert(endp > contextp->cc_extattrdumpbufp); |
| bufsz = (size_t)(endp - contextp->cc_extattrdumpbufp); |
| |
| rval = write_buf(contextp->cc_extattrdumpbufp, |
| bufsz, |
| (void *)drivep, |
| (gwbfp_t)drivep->d_opsp->do_get_write_buf, |
| (wfp_t)drivep->d_opsp->do_write); |
| switch (rval) { |
| case 0: |
| rv = RV_OK; |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOM; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| break; |
| } |
| if (rv != RV_OK) { |
| *abortprp = BOOL_FALSE; |
| return rv; |
| } |
| dumpbufp = contextp->cc_extattrdumpbufp; |
| endp = dumpbufp; |
| } |
| |
| /* All existing attributes are in the dump buffer. See if the HSM |
| * needs to add any addtional attributes. |
| */ |
| |
| if (!listp->al_more && hsm_fs_ctxtp) { |
| int hsmcursor = 0; |
| for (;;) { |
| char *hsmnamep; |
| char *hsmvaluep; |
| char *valuep; |
| uint32_t hsmvaluesz; |
| |
| if (!HsmAddNewAttribute(contextp->cc_hsm_f_ctxtp, |
| hsmcursor, |
| flag, |
| &hsmnamep, |
| &hsmvaluep, |
| &hsmvaluesz)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "HSM could not add new %s attribute " |
| "#%d for %s ino %llu\n"), |
| EXTATTR_NAMESPACE(flag), |
| hsmcursor, |
| FILETYPE(statp), |
| statp->bs_ino); |
| *abortprp = BOOL_TRUE; |
| return RV_OK; |
| } |
| if (hsmnamep == NULL) { |
| break; /* No more attributes to add */ |
| } |
| |
| dumpbufp = dump_extattr_buildrecord(statp, |
| dumpbufp, |
| dumpbufendp, |
| hsmnamep, |
| hsmvaluesz, |
| flag, |
| &valuep); |
| |
| if (dumpbufp < dumpbufendp) { /* if fits in buffer */ |
| endp = dumpbufp; |
| (void)memcpy(valuep, hsmvaluep, hsmvaluesz); |
| hsmcursor++; |
| continue; |
| } |
| |
| assert(endp > contextp->cc_extattrdumpbufp); |
| bufsz = (size_t)(endp - contextp->cc_extattrdumpbufp); |
| |
| rval = write_buf(contextp->cc_extattrdumpbufp, |
| bufsz, |
| (void *)drivep, |
| (gwbfp_t)drivep->d_opsp->do_get_write_buf, |
| (wfp_t)drivep->d_opsp->do_write); |
| switch (rval) { |
| case 0: |
| rv = RV_OK; |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOM; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| break; |
| } |
| if (rv != RV_OK) { |
| *abortprp = BOOL_FALSE; |
| return rv; |
| } |
| dumpbufp = contextp->cc_extattrdumpbufp; |
| endp = dumpbufp; |
| } |
| } |
| |
| /* If any attributes remain unwritten in the dump buffer, write them |
| * now. |
| */ |
| |
| if (endp > contextp->cc_extattrdumpbufp) { |
| bufsz = (size_t)(endp - contextp->cc_extattrdumpbufp); |
| |
| rval = write_buf(contextp->cc_extattrdumpbufp, |
| bufsz, |
| (void *)drivep, |
| (gwbfp_t)drivep->d_opsp->do_get_write_buf, |
| (wfp_t)drivep->d_opsp->do_write); |
| switch (rval) { |
| case 0: |
| rv = RV_OK; |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOM; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| break; |
| } |
| if (rv != RV_OK) { |
| *abortprp = BOOL_FALSE; |
| return rv; |
| } |
| } |
| |
| *abortprp = BOOL_FALSE; |
| return RV_OK; |
| } |
| |
| static char * |
| dump_extattr_buildrecord(struct xfs_bstat *statp, |
| char *dumpbufp, |
| char *dumpbufendp, |
| char *namesrcp, |
| uint32_t valuesz, |
| int flag, |
| char **valuepp) |
| { |
| extattrhdr_t *ahdrp = (extattrhdr_t *)dumpbufp; |
| char *namep = dumpbufp + EXTATTRHDR_SZ; |
| uint32_t namelen = strlen(namesrcp); |
| uint32_t namesz = namelen + 1; |
| char *valuep = namep + namesz; |
| uint32_t recsz = EXTATTRHDR_SZ + namesz + valuesz; |
| extattrhdr_t tmpah; |
| |
| recsz = (recsz + (EXTATTRHDR_ALIGN - 1)) |
| & |
| ~(EXTATTRHDR_ALIGN - 1); |
| |
| if (dumpbufp + recsz > dumpbufendp) { |
| *valuepp = 0; |
| return dumpbufp + recsz; |
| } |
| |
| if (namelen > NAME_MAX) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "%s extended attribute name for %s ino %llu too long: " |
| "%u, max is %u: skipping\n"), |
| EXTATTR_NAMESPACE(flag), |
| FILETYPE(statp), |
| statp->bs_ino, |
| namelen, |
| NAME_MAX); |
| *valuepp = 0; |
| return dumpbufp; |
| } |
| |
| if (valuesz > ATTR_MAX_VALUELEN) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "%s extended attribute value for %s ino %llu too long: " |
| "%u, max is %u: skipping\n"), |
| EXTATTR_NAMESPACE(flag), |
| FILETYPE(statp), |
| statp->bs_ino, |
| valuesz, |
| ATTR_MAX_VALUELEN); |
| *valuepp = 0; |
| return dumpbufp; |
| } |
| |
| /*DBG*/mlog(MLOG_NITTY, |
| "building extattr " |
| "record sz %u " |
| "hdrsz %u " |
| "namesz %u (%s) " |
| "valsz %u\n", |
| recsz, |
| EXTATTRHDR_SZ, |
| namesz, namesrcp, |
| valuesz); |
| (void)strcpy(namep, namesrcp); |
| |
| memset((void *)&tmpah, 0, sizeof(tmpah)); |
| tmpah.ah_sz = recsz; |
| assert(EXTATTRHDR_SZ + namesz < UINT16MAX); |
| tmpah.ah_valoff = (uint16_t)(EXTATTRHDR_SZ + namesz); |
| tmpah.ah_flags = (uint16_t) |
| ((flag & ATTR_ROOT) ? EXTATTRHDR_FLAGS_ROOT : |
| ((flag & ATTR_SECURE) ? EXTATTRHDR_FLAGS_SECURE : 0)); |
| tmpah.ah_valsz = valuesz; |
| tmpah.ah_flags |= EXTATTRHDR_FLAGS_CHECKSUM; |
| tmpah.ah_checksum = calc_checksum(&tmpah, EXTATTRHDR_SZ); |
| |
| xlate_extattrhdr(ahdrp, &tmpah, -1); |
| *valuepp = valuep; |
| return dumpbufp + recsz; |
| } |
| |
| /* ARGSUSED */ |
| static rv_t |
| dump_extattrhdr(drive_t *drivep, |
| context_t *contextp, |
| struct xfs_bstat *statp, |
| size_t recsz, |
| size_t valoff, |
| ix_t flags, |
| uint32_t valsz) |
| { |
| extattrhdr_t ahdr; |
| extattrhdr_t tmpahdr; |
| int rval; |
| rv_t rv; |
| |
| memset((void *)&ahdr, 0, sizeof(ahdr)); |
| ahdr.ah_sz = recsz; |
| assert(valoff < UINT16MAX); |
| ahdr.ah_valoff = (uint16_t)valoff; |
| ahdr.ah_flags = (uint16_t)flags | EXTATTRHDR_FLAGS_CHECKSUM; |
| ahdr.ah_valsz = valsz; |
| ahdr.ah_checksum = calc_checksum(&ahdr, EXTATTRHDR_SZ); |
| |
| xlate_extattrhdr(&ahdr, &tmpahdr, 1); |
| rval = write_buf((char *)&tmpahdr, |
| EXTATTRHDR_SZ, |
| (void *)drivep, |
| (gwbfp_t)drivep->d_opsp->do_get_write_buf, |
| (wfp_t)drivep->d_opsp->do_write); |
| switch (rval) { |
| case 0: |
| rv = RV_OK; |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOM; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| break; |
| } |
| |
| return rv; |
| } |
| |
| /* this function is called by the bigstat iterator for all non-directory |
| * files. it passes the buck to file type-specific dump functions. |
| * return value is RV_EOF if the media file is getting too big, |
| * RV_... if trouble encountered with the media/drive, |
| * 0 if file completely dumped, RV_NOMORE if we've encountered the stream |
| * endpt, RV_INTR if operator interrupted the dump. |
| */ |
| /* ARGSUSED */ |
| static rv_t |
| dump_file(void *arg1, |
| jdm_fshandle_t *fshandlep, |
| int fsfd, |
| struct xfs_bstat *statp) |
| { |
| ix_t strmix = (ix_t)arg1; |
| context_t *contextp = &sc_contextp[strmix]; |
| drive_t *drivep = drivepp[strmix]; |
| drive_hdr_t *dwhdrp = drivep->d_writehdrp; |
| media_hdr_t *mwhdrp = (media_hdr_t *)dwhdrp->dh_upper; |
| content_hdr_t *cwhdrp = (content_hdr_t *)mwhdrp->mh_upper; |
| content_inode_hdr_t *scwhdrp = (content_inode_hdr_t *) |
| (void *) |
| cwhdrp->ch_specific; |
| startpt_t *startptp = &scwhdrp->cih_startpt; |
| startpt_t *endptp = &scwhdrp->cih_endpt; |
| bool_t file_skipped = BOOL_FALSE; |
| int state; |
| rv_t rv; |
| |
| /* skip if no links |
| */ |
| if (statp->bs_nlink < 1) { |
| if (statp->bs_ino > contextp->cc_stat_lastino) { |
| contextp->cc_stat_lastino = statp->bs_ino; |
| } |
| mlog(MLOG_DEBUG, "skip as no links for ino %llu\n", |
| statp->bs_ino); |
| return RV_OK; |
| } |
| |
| /* skip if prior to startpoint |
| */ |
| if (statp->bs_ino < startptp->sp_ino) { |
| if (statp->bs_ino > contextp->cc_stat_lastino) { |
| contextp->cc_stat_lastino = statp->bs_ino; |
| } |
| mlog(MLOG_DEBUG, "skip as ino %llu is prior to starpoint\n", |
| statp->bs_ino); |
| return RV_OK; |
| } |
| |
| /* skip if at or beyond next startpoint. return non-zero to |
| * abort iteration. |
| */ |
| if (!(endptp->sp_flags & STARTPT_FLAGS_END)) { |
| if (endptp->sp_offset == 0) { |
| if (statp->bs_ino >= endptp->sp_ino) { |
| if (statp->bs_ino > contextp->cc_stat_lastino) { |
| contextp->cc_stat_lastino = statp->bs_ino; |
| } |
| mlog(MLOG_DEBUG, "skip as ino %llu is at/beyond starpoint\n", |
| statp->bs_ino); |
| return RV_NOMORE; |
| } |
| } else { |
| if (statp->bs_ino > endptp->sp_ino) { |
| if (statp->bs_ino > contextp->cc_stat_lastino) { |
| contextp->cc_stat_lastino = statp->bs_ino; |
| } |
| mlog(MLOG_DEBUG, "skip as ino %llu is at/beyond starpoint\n", |
| statp->bs_ino); |
| return RV_NOMORE; |
| } |
| } |
| } |
| |
| /* see what the inomap says about this ino |
| */ |
| state = inomap_get_state(contextp->cc_inomap_contextp, statp->bs_ino); |
| |
| /* skip if not in inomap |
| */ |
| if (state == MAP_INO_UNUSED |
| || |
| state == MAP_DIR_NOCHNG |
| || |
| state == MAP_NDR_NOCHNG) { |
| if (state == MAP_DIR_NOCHNG) { |
| mlog(MLOG_DEBUG, |
| "inomap inconsistency ino %llu: " |
| "MAP_DIR_NOCHNG but is non-dir: skipping\n", |
| statp->bs_ino); |
| } |
| if (statp->bs_ino > contextp->cc_stat_lastino) { |
| contextp->cc_stat_lastino = statp->bs_ino; |
| } |
| mlog(MLOG_DEBUG, "skip as ino %llu is not marked as changed in inomap\n", |
| statp->bs_ino); |
| mlog(MLOG_DEBUG, "ino %llu is in state %d\n", |
| statp->bs_ino, state); |
| return RV_OK; |
| } |
| |
| /* note if map says a dir |
| */ |
| if (state == MAP_DIR_CHANGE || state == MAP_DIR_SUPPRT) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "inomap inconsistency ino %llu: " |
| "%s but is now non-dir: NOT dumping\n"), |
| statp->bs_ino, |
| state == MAP_DIR_CHANGE |
| ? |
| "map says changed dir" |
| : |
| "map says unchanged dir"); |
| } |
| |
| /* if GETOPT_DUMPASOFFLINE was specified, initialize the HSM's file |
| * context for use in other routines. If the context can't be |
| * initialized, don't dump the file. |
| */ |
| |
| if (hsm_fs_ctxtp) { |
| if (HsmInitFileContext(contextp->cc_hsm_f_ctxtp, statp)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "inomap inconsistency ino %llu: " |
| "hsm detected error: NOT dumping\n"), |
| statp->bs_ino); |
| if (statp->bs_ino > contextp->cc_stat_lastino) { |
| contextp->cc_stat_lastino = statp->bs_ino; |
| } |
| return RV_OK; |
| } |
| } |
| |
| /* pass on to specific dump function |
| */ |
| switch(statp->bs_mode & S_IFMT) { |
| case S_IFREG: |
| /* ordinary file |
| */ |
| |
| /* filter out any files that have grown beyond the |
| * max file size since the initial scan. |
| */ |
| if (maxdumpfilesize) { |
| off64_t estimated_size = statp->bs_blocks * |
| (off64_t)statp->bs_blksize; |
| |
| if (hsm_fs_ctxtp) { |
| HsmEstimateFileSpace(hsm_fs_ctxtp, |
| contextp->cc_hsm_f_ctxtp, |
| statp, |
| &estimated_size, |
| 1); |
| } |
| |
| /* quota files are exempt from max size check */ |
| if (estimated_size > maxdumpfilesize && |
| !is_quota_file(statp->bs_ino)) { |
| mlog(MLOG_DEBUG | MLOG_NOTE, |
| "ino %llu increased beyond maximum size: " |
| "NOT dumping\n", |
| statp->bs_ino); |
| return RV_OK; |
| } |
| } |
| rv = dump_file_reg(drivep, |
| contextp, |
| scwhdrp, |
| fshandlep, |
| statp, |
| &file_skipped); |
| if (statp->bs_ino > contextp->cc_stat_lastino) { |
| lock(); |
| sc_stat_nondirdone++; |
| unlock(); |
| contextp->cc_stat_lastino = statp->bs_ino; |
| } |
| break; /* drop out of switch to extattr dump */ |
| case S_IFCHR: |
| case S_IFBLK: |
| case S_IFIFO: |
| case S_IFLNK: |
| case S_IFSOCK: |
| /* only need a filehdr_t; no data |
| */ |
| rv = dump_file_spec(drivep, contextp, fshandlep, statp); |
| if (statp->bs_ino > contextp->cc_stat_lastino) { |
| lock(); |
| sc_stat_nondirdone++; |
| unlock(); |
| contextp->cc_stat_lastino = statp->bs_ino; |
| } |
| break; /* drop out of switch to extattr dump */ |
| case S_IFDIR: |
| default: |
| /* don't know how to dump these |
| */ |
| mlog(MLOG_VERBOSE, _( |
| "don't know how to dump ino %llu: mode %08x\n"), |
| statp->bs_ino, |
| statp->bs_mode); |
| if (statp->bs_ino > contextp->cc_stat_lastino) { |
| lock(); |
| sc_stat_nondirdone++; |
| unlock(); |
| contextp->cc_stat_lastino = statp->bs_ino; |
| } |
| return RV_OK; |
| } |
| |
| if (rv == RV_OK |
| && |
| file_skipped == BOOL_FALSE |
| && |
| sc_dumpextattrpr |
| && |
| (statp->bs_xflags & XFS_XFLAG_HASATTR)) { |
| rv = dump_extattrs(drivep, contextp, fshandlep, statp); |
| } |
| |
| return rv; |
| } |
| |
| /* a regular file may be broken into several portions if its size |
| * is large. Each portion begins with a filehdr_t and is followed by |
| * several extents. each extent begins with an extenthdr_t. returns RV_OK |
| * if all extents dumped, RV_... on drive errors, or RV_INTR if |
| * operator requested stop. |
| */ |
| static rv_t |
| dump_file_reg(drive_t *drivep, |
| context_t *contextp, |
| content_inode_hdr_t *scwhdrp, |
| jdm_fshandle_t *fshandlep, |
| struct xfs_bstat *statp, |
| bool_t *file_skippedp) |
| { |
| startpt_t *startptp = &scwhdrp->cih_startpt; |
| startpt_t *endptp = &scwhdrp->cih_endpt; |
| extent_group_context_t extent_group_context; |
| bool_t cmpltflg; |
| off64_t offset; |
| off64_t stopoffset; |
| bool_t sosig; /* stop offset is significant */ |
| off64_t maxextentcnt; |
| rv_t rv; |
| |
| /* determine the offset within the file where the dump should begin. |
| * it must have been aligned to the basic fs block size by the |
| * startpoint calculations done during strategy initialization. |
| */ |
| if (statp->bs_ino == startptp->sp_ino) { |
| offset = startptp->sp_offset; |
| assert((offset & (off64_t)(BBSIZE - 1)) == 0); |
| } else { |
| offset = 0; |
| } |
| |
| /* if this is a resumed dump and the resumption begins somewhere |
| * within this file, and that point is greater than offset set |
| * above, and that file hasn't changed since the resumed dump, |
| * modify offset. |
| */ |
| if (sc_resumepr) { |
| drange_t *drangep = sc_resumerangep; |
| size_t drangecnt = sc_resumerangecnt; |
| size_t drangeix; |
| |
| for (drangeix = 0; drangeix < drangecnt; drangeix++) { |
| drange_t *rp = &drangep[drangeix]; |
| if (statp->bs_ino == rp->dr_begin.sp_ino) { |
| register time32_t mtime = statp->bs_mtime.tv_sec; |
| register time32_t ctime = statp->bs_ctime.tv_sec; |
| register time32_t ltime = max(mtime, ctime); |
| if (ltime < sc_resumebasetime) { |
| if (rp->dr_begin.sp_offset > offset){ |
| offset =rp->dr_begin.sp_offset; |
| } |
| } |
| break; |
| } |
| } |
| assert((offset & (off64_t)(BBSIZE - 1)) == 0); |
| } |
| |
| /* determine the offset within the file where the dump should end. |
| * only significant if this is an inode spanning a startpoint. |
| */ |
| if (endptp->sp_flags & STARTPT_FLAGS_END) { |
| sosig = BOOL_FALSE; |
| stopoffset = 0; |
| } else if (statp->bs_ino == endptp->sp_ino) { |
| sosig = BOOL_TRUE; |
| stopoffset = endptp->sp_offset; |
| } else { |
| sosig = BOOL_FALSE; |
| stopoffset = 0; |
| } |
| |
| mlog(MLOG_TRACE, |
| "dumping regular file ino %llu " |
| "offset %lld " |
| "to offset %lld " |
| "(size %lld)\n", |
| statp->bs_ino, |
| offset, |
| sosig ? stopoffset : statp->bs_size, |
| statp->bs_size); |
| |
| /* calculate the maximum extent group size. files larger than this |
| * will be broken into multiple extent groups, each with its own |
| * filehdr_t. |
| */ |
| maxextentcnt = drivep->d_recmarksep; |
| |
| /* initialize the extent group context. if fails, just return, |
| * pretending the dump succeeded. |
| */ |
| rv = init_extent_group_context(fshandlep, |
| statp, |
| &extent_group_context); |
| if (rv != RV_OK) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "could not open regular file ino %llu mode 0x%08x: " |
| "%s: not dumped\n"), |
| statp->bs_ino, |
| statp->bs_mode, |
| strerror(errno)); |
| *file_skippedp = BOOL_TRUE; |
| return RV_OK; |
| } |
| |
| /* loop here, dumping marked groups of extents. each extent group |
| * is preceeded by a filehdr_t. this is required so that the |
| * recovery side can identify the fs file at each marked point |
| * in the stream. it sets by reference offset, bytecnt, and cmpltflg. |
| * it is important to understand that if a fs file is real big, |
| * it will be dumped in parts (extent groups), each beginning with |
| * an identical filehdr_t. |
| */ |
| cmpltflg = BOOL_FALSE; |
| |
| rv = RV_OK; |
| for (;;) { |
| off64_t bytecnt = 0; |
| off64_t bc; |
| |
| /* see if we are done. |
| */ |
| if (cmpltflg) { |
| assert(rv == RV_OK); |
| break; |
| } |
| |
| /* set a mark - important to do this now, before deciding |
| * the media file is to big or the operator asked to |
| * interrupt the dump. this mark, if committed, indicates |
| * the previous fs file / extent group was completely dumped. |
| */ |
| mark_set(drivep, statp->bs_ino, offset, 0); |
| |
| /* spoof EOF if the media file size is getting too big. |
| * note that the most we can go over is d_recmarksep. |
| */ |
| if (contextp->cc_mfilesz >= drivep->d_recmfilesz){ |
| rv = RV_EOF; |
| break; |
| } |
| |
| /* check if the operator has requested to interrupt the dump. |
| */ |
| if (cldmgr_stop_requested()) { |
| mlog(MLOG_NORMAL, _( |
| "dump interrupted prior to ino %llu offset %lld\n"), |
| statp->bs_ino, |
| offset); |
| mlog_exit_hint(RV_INTR); |
| rv = RV_INTR; |
| break; |
| } |
| |
| /* dump the file header |
| */ |
| mlog(MLOG_DEBUG, |
| "dumping extent group ino %llu offset %lld\n", |
| statp->bs_ino, |
| offset); |
| rv = dump_filehdr(drivep, contextp, statp, offset, 0); |
| if (rv != RV_OK) { |
| break; |
| } |
| bytecnt += sizeof(filehdr_t); |
| |
| /* dump a group of extents. returns by reference |
| * the offset of the next extent group (to be placed |
| * in the next mark), the total number of bytes written |
| * to media (headers and all), and a flag indicating |
| * all extents have been dumped. |
| */ |
| bc = 0; /* for lint */ |
| rv = dump_extent_group(drivep, |
| contextp, |
| statp, |
| &extent_group_context, |
| maxextentcnt, |
| stopoffset, |
| sosig, |
| &offset, |
| &bc, |
| &cmpltflg); |
| assert(bc >= 0); |
| bytecnt += bc; |
| if (rv != RV_OK) { |
| break; |
| } |
| |
| /* update global stat |
| */ |
| lock(); |
| sc_stat_datadone += (size64_t)bc; |
| unlock(); |
| |
| /* dump LAST extent hdr. one of these is placed at the |
| * end of each dumped file. necessary to detect the |
| * end of the file. |
| */ |
| rv = dump_extenthdr(drivep, |
| contextp, |
| EXTENTHDR_TYPE_LAST, |
| 0, |
| 0, |
| 0); |
| if (rv != RV_OK) { |
| break; |
| } |
| bytecnt += sizeof(extenthdr_t); |
| |
| /* update the media file size |
| */ |
| contextp->cc_mfilesz += bytecnt; |
| |
| } |
| |
| cleanup_extent_group_context(&extent_group_context); |
| return rv; |
| } |
| |
| /* dumps character, block, and fifo - special files. no data, just meta-data, |
| * all contained within the filehdr_t. also handles symbolic link files: |
| * appends a variable-length string after the filehdr_t. |
| */ |
| static rv_t |
| dump_file_spec(drive_t *drivep, |
| context_t *contextp, |
| jdm_fshandle_t *fshandlep, |
| struct xfs_bstat *statp) |
| { |
| int rval; |
| rv_t rv; |
| |
| mlog(MLOG_TRACE, |
| "dumping special file ino %llu mode 0x%04x\n", |
| statp->bs_ino, |
| statp->bs_mode); |
| |
| /* set a mark - important to do this now, before deciding |
| * the media file is to big. this mark, if committed, |
| * indicates the previous fs file was completely dumped. |
| */ |
| mark_set(drivep, statp->bs_ino, 0, 0); |
| |
| /* dump the file header |
| */ |
| rv = dump_filehdr(drivep, contextp, statp, 0, 0); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| |
| /* update the media file size |
| */ |
| contextp->cc_mfilesz += sizeof(filehdr_t); |
| |
| /* if a symbolic link, also dump the link pathname. |
| * use an extent header to represent the pathname. the |
| * extent sz will always be a multiple of SYMLINK_ALIGN. |
| * the symlink pathname char string will always be NULL-terminated. |
| */ |
| if ((statp->bs_mode & S_IFMT) == S_IFLNK) { |
| int nread; |
| size_t extentsz; |
| |
| /* read the link path. if error, dump a zero-length |
| * extent. in any case, nread will contain the number of |
| * bytes to dump, and contextp->cc_direntbufp will contain |
| * the bytes. |
| */ |
| nread = jdm_readlink(fshandlep, |
| statp, |
| contextp->cc_readlinkbufp, |
| contextp->cc_readlinkbufsz); |
| if (nread < 0) { |
| mlog(MLOG_DEBUG, |
| "could not read symlink ino %llu\n", |
| statp->bs_ino); |
| nread = 0; |
| } |
| |
| /* null-terminate the string |
| */ |
| assert((size_t)nread < contextp->cc_readlinkbufsz); |
| contextp->cc_readlinkbufp[nread] = 0; |
| |
| /* calculate the extent size - be sure to include room |
| * for the null-termination. |
| */ |
| extentsz = ((size_t)nread + 1 + (SYMLINK_ALIGN - 1)) |
| & |
| ~ (SYMLINK_ALIGN - 1); |
| assert(extentsz <= contextp->cc_readlinkbufsz); |
| |
| /* dump an extent header |
| */ |
| rv = dump_extenthdr(drivep, |
| contextp, |
| EXTENTHDR_TYPE_DATA, |
| 0, |
| 0, |
| (off64_t)extentsz); |
| if (rv != RV_OK) { |
| return rv; |
| } |
| |
| /* dump the link path extent |
| */ |
| rval = write_buf(contextp->cc_readlinkbufp, |
| extentsz, |
| (void *)drivep, |
| (gwbfp_t)drivep->d_opsp->do_get_write_buf, |
| (wfp_t)drivep->d_opsp->do_write); |
| switch (rval) { |
| case 0: |
| rv = RV_OK; |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOM; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| break; |
| } |
| if (rv != RV_OK) { |
| return rv; |
| } |
| } |
| |
| return RV_OK; |
| } |
| |
| /* contrives the initial state of the extent group context such that |
| * dump_extent_group() will fetch some extents from the kernel before it |
| * does anything else. |
| */ |
| static rv_t |
| init_extent_group_context(jdm_fshandle_t *fshandlep, |
| struct xfs_bstat *statp, |
| extent_group_context_t *gcp) |
| { |
| bool_t isrealtime; |
| int oflags; |
| struct flock fl; |
| |
| isrealtime = (bool_t)(statp->bs_xflags & XFS_XFLAG_REALTIME); |
| oflags = O_RDONLY; |
| if (isrealtime) { |
| oflags |= O_DIRECT; |
| } |
| (void)memset((void *)gcp, 0, sizeof(*gcp)); |
| gcp->eg_bmap[0].bmv_offset = 0; |
| gcp->eg_bmap[0].bmv_length = -1; |
| gcp->eg_bmap[0].bmv_count = BMAP_LEN; |
| gcp->eg_nextbmapp = &gcp->eg_bmap[1]; |
| gcp->eg_endbmapp = &gcp->eg_bmap[1]; |
| gcp->eg_bmapix = 0; |
| gcp->eg_gbmcnt = 0; |
| gcp->eg_fd = jdm_open(fshandlep, statp, oflags); |
| if (gcp->eg_fd < 0) { |
| return RV_ERROR; |
| } |
| |
| /* Check if a mandatory lock is set on the file to try and |
| * avoid blocking indefinitely on the reads later. Note that |
| * someone could still set a mandatory lock and hose xfsdump |
| * after this check but before all reads have completed. |
| * This change just closes the window a bit. |
| */ |
| if ((statp->bs_mode & S_ISGID) && (!(statp->bs_mode&S_IXOTH))) { |
| fl.l_type = F_RDLCK; |
| fl.l_whence = SEEK_SET; |
| fl.l_start = (off_t)0; |
| fl.l_len = 0; |
| if ((fcntl(gcp->eg_fd, F_GETLK, &fl)) < 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "locking check failed ino %llu\n"), |
| statp->bs_ino); |
| close(gcp->eg_fd); |
| return RV_ERROR; |
| } |
| if (fl.l_type != F_UNLCK) { |
| /* Mandatory lock is set */ |
| close(gcp->eg_fd); |
| errno = EBUSY; |
| return RV_ERROR; |
| } |
| } |
| return RV_OK; |
| } |
| |
| static void |
| cleanup_extent_group_context(extent_group_context_t *gcp) |
| { |
| (void)close(gcp->eg_fd); |
| } |
| |
| static rv_t |
| dump_extent_group(drive_t *drivep, |
| context_t *contextp, |
| struct xfs_bstat *statp, |
| extent_group_context_t *gcp, |
| off64_t maxcnt, |
| off64_t stopoffset, |
| bool_t sosig, |
| off64_t *nextoffsetp, |
| off64_t *bytecntp, |
| bool_t *cmpltflgp) |
| { |
| struct dioattr da; |
| drive_ops_t *dop = drivep->d_opsp; |
| bool_t isrealtime = (bool_t)(statp->bs_xflags |
| & |
| XFS_XFLAG_REALTIME); |
| off64_t nextoffset; |
| off64_t bytecnt; /* accumulates total bytes sent to media */ |
| int rval; |
| rv_t rv; |
| |
| /* |
| * Setup realtime I/O size. |
| */ |
| if (isrealtime) { |
| if ((ioctl(gcp->eg_fd, XFS_IOC_DIOINFO, &da) < 0)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "dioinfo failed ino %llu\n"), |
| statp->bs_ino); |
| da.d_miniosz = PGSZ; |
| } |
| } |
| |
| /* dump extents until the recommended extent length is achieved |
| */ |
| nextoffset = *nextoffsetp; |
| bytecnt = 0; |
| assert((nextoffset & (BBSIZE - 1)) == 0); |
| |
| for (;;) { |
| off64_t offset; |
| off64_t extsz; |
| |
| /* if we've dumped to the stop point return. |
| */ |
| if (sosig && nextoffset >= stopoffset) { |
| mlog(MLOG_NITTY, |
| "dumped to stop offset\n"); |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; |
| return RV_OK; |
| } |
| |
| /* if we've dumped the entire file, return |
| */ |
| if (nextoffset >= statp->bs_size) { |
| mlog(MLOG_NITTY, |
| "dumped to end of file\n"); |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; |
| return RV_OK; |
| } |
| |
| /* if we've exceeded the desired per-extent group byte count, |
| * call it quits. we'll be called back for more because the |
| * completion flag is set FALSE. |
| */ |
| if (bytecnt >= maxcnt) { |
| mlog(MLOG_NITTY, |
| "reached per-extent group byte count\n"); |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_FALSE; |
| return RV_OK; |
| } |
| |
| /* if we are not looking at a valid bmap entry, |
| * get one. |
| */ |
| if (gcp->eg_nextbmapp >= gcp->eg_endbmapp) { |
| int entrycnt; /* entries in new bmap */ |
| |
| assert(gcp->eg_nextbmapp == gcp->eg_endbmapp); |
| |
| /* get a new extent block |
| */ |
| mlog(MLOG_NITTY, |
| "calling getbmapx for ino %llu\n", |
| statp->bs_ino); |
| rval = ioctl(gcp->eg_fd, XFS_IOC_GETBMAPX, gcp->eg_bmap); |
| gcp->eg_gbmcnt++; |
| entrycnt = gcp->eg_bmap[0].bmv_entries; |
| if (entrycnt < 0) { /* workaround for getbmap bug */ |
| mlog(MLOG_DEBUG | MLOG_WARNING, _( |
| "getbmapx %d ino %lld mode 0x%08x " |
| "offset %lld ix %d " |
| "returns negative entry count\n"), |
| gcp->eg_gbmcnt, |
| statp->bs_ino, |
| statp->bs_mode, |
| nextoffset, |
| gcp->eg_bmapix); |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; |
| return RV_OK; |
| } |
| if (rval) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "getbmapx %d ino %lld mode 0x%08x " |
| "offset %lld failed: %s\n"), |
| gcp->eg_gbmcnt, |
| statp->bs_ino, |
| statp->bs_mode, |
| nextoffset, |
| strerror(errno)); |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; |
| return RV_OK; |
| } |
| |
| /* The F_GETBMAPX call succeeded. Give the HSM a chance |
| * to massage the extents. (It can change the number |
| * of extents remaining, even setting them to zero. |
| */ |
| |
| if (hsm_fs_ctxtp) { |
| if (!HsmModifyExtentMap(contextp->cc_hsm_f_ctxtp, |
| &gcp->eg_bmap[0])) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "hsm detected an extent map " |
| "error in ino %lld, skipping\n"), |
| statp->bs_ino); |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; |
| return RV_OK; |
| } |
| entrycnt = gcp->eg_bmap[0].bmv_entries; |
| } |
| |
| if (entrycnt <= 0) { |
| mlog(MLOG_NITTY, |
| "all extent groups dumped\n"); |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; |
| return RV_OK; |
| } |
| gcp->eg_nextbmapp = &gcp->eg_bmap[1]; |
| gcp->eg_endbmapp = &gcp->eg_bmap[entrycnt + 1]; |
| mlog(MLOG_NITTY, |
| "getbmapx supplied %d extent entries\n", |
| entrycnt); |
| } |
| |
| mlog(MLOG_NITTY, |
| "bmap entry %d ix %d block %lld offset %lld length %lld\n", |
| gcp->eg_nextbmapp - &gcp->eg_bmap[0], |
| gcp->eg_bmapix, |
| gcp->eg_nextbmapp->bmv_block, |
| gcp->eg_nextbmapp->bmv_offset, |
| gcp->eg_nextbmapp->bmv_length); |
| |
| /* if the next bmap entry represents a hole, go to the next |
| * one in the bmap, and rescan to check above assumptions. |
| * bump nextoffset to after the hole, if beyond current value. |
| */ |
| if (gcp->eg_nextbmapp->bmv_block == -1) { |
| off64_t tmpoffset; |
| |
| /* extract the offset and extent size from this |
| * entry |
| */ |
| offset = gcp->eg_nextbmapp->bmv_offset |
| * (off64_t)BBSIZE; |
| extsz = gcp->eg_nextbmapp->bmv_length |
| * (off64_t)BBSIZE; |
| |
| mlog(MLOG_NITTY, |
| "hole extent offset = %lld size = %lld\n", |
| offset, extsz); |
| |
| /* Encode the hole - dump the extent header |
| * with the right extent type. |
| */ |
| rv = dump_extenthdr(drivep, |
| contextp, |
| EXTENTHDR_TYPE_HOLE, |
| 0, |
| offset, |
| extsz); |
| if (rv != RV_OK) { |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; /*moot since rv != OK */ |
| return rv; |
| } |
| bytecnt += sizeof(extenthdr_t); |
| |
| tmpoffset = (gcp->eg_nextbmapp->bmv_offset |
| + |
| gcp->eg_nextbmapp->bmv_length) |
| * |
| (off64_t)BBSIZE; |
| if (tmpoffset > nextoffset) { |
| nextoffset = tmpoffset; |
| } |
| gcp->eg_nextbmapp++; |
| gcp->eg_bmapix++; |
| continue; |
| } |
| |
| /* if the next bmap entry has a zero size, go to the next |
| * one in the bmap, and rescan to check above assumptions. |
| */ |
| if (gcp->eg_nextbmapp->bmv_length <= 0) { |
| off64_t tmpoffset; |
| |
| mlog(MLOG_NITTY, |
| "non-positive extent\n"); |
| tmpoffset = gcp->eg_nextbmapp->bmv_offset |
| * |
| (off64_t)BBSIZE; |
| if (tmpoffset > nextoffset) { |
| nextoffset = tmpoffset; |
| } |
| gcp->eg_nextbmapp++; |
| gcp->eg_bmapix++; |
| continue; |
| } |
| |
| /* extract the offset and extent size from this |
| * entry |
| */ |
| offset = gcp->eg_nextbmapp->bmv_offset * (off64_t)BBSIZE; |
| extsz = gcp->eg_nextbmapp->bmv_length * (off64_t)BBSIZE; |
| mlog(MLOG_NITTY, |
| "extent offset %lld sz %lld; nextoffset %lld\n", |
| offset, |
| extsz, |
| nextoffset); |
| |
| /* if the new bmap entry begins below the stop offset |
| * but does not contain any data above the current |
| * offset, go to the next one and rescan. |
| */ |
| if (!sosig || offset < stopoffset) { |
| if (offset + extsz <= nextoffset) { |
| mlog(MLOG_NITTY, |
| "extent ends before nextoffset\n"); |
| gcp->eg_nextbmapp++; |
| gcp->eg_bmapix++; |
| continue; |
| } |
| } |
| |
| /* if the new bmap entry begins beyond the end of the file, |
| * we are done. |
| */ |
| if (offset >= statp->bs_size) { |
| mlog(MLOG_NITTY, |
| "extent beyond end of file\n"); |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; |
| return RV_OK; |
| } |
| |
| /* if the new bmap entry begins at or above the stop offset, |
| * stop. we are done. |
| */ |
| if (sosig && offset >= stopoffset) { |
| mlog(MLOG_NITTY, |
| "extent beyond stop offset\n"); |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; |
| return RV_OK; |
| } |
| |
| /* if the new entry begins below the range of |
| * interest, modify offset to begin at the |
| * beginning of the range of interest, and shorten |
| * extsz accordingly. |
| */ |
| if (offset < nextoffset) { |
| extsz -= nextoffset - offset; |
| offset = nextoffset; |
| mlog(MLOG_NITTY, |
| "adjusted bottom of extent to nextoffset: " |
| "offset %lld, sz %lld; nextoffset %lld\n", |
| offset, |
| extsz, |
| nextoffset); |
| } |
| assert(extsz > 0); |
| |
| /* if the resultant extent would put us over maxcnt, |
| * shorten it, and round up to the next BBSIZE (round |
| * upto d_miniosz for realtime). |
| */ |
| if (extsz > maxcnt - (bytecnt + sizeof(extenthdr_t))) { |
| int iosz; |
| |
| if (isrealtime) |
| iosz = da.d_miniosz; |
| else |
| iosz = BBSIZE; |
| extsz = maxcnt - (bytecnt + sizeof(extenthdr_t)); |
| extsz = (extsz + (off64_t)(iosz - 1)) |
| & |
| ~(off64_t)(iosz - 1); |
| mlog(MLOG_NITTY, |
| "adjusted top of extent to honor maxcnt " |
| "(rounded up %d): " |
| "offset %lld, sz %lld; maxcnt %lld\n", |
| iosz, |
| offset, |
| extsz, |
| maxcnt); |
| } |
| |
| /* if the shortened extent is too small, return; we'll |
| * pick it up next time around. exception: if the file |
| * size is zero, indicate we are done. |
| * !!! I don't believe this rule can ever fire! |
| */ |
| if (extsz <= 0) { |
| mlog(MLOG_NITTY, |
| "adjusted extent size is non-positive: " |
| "%lld (bs_size %lld)\n", |
| extsz, |
| statp->bs_size); |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| if (statp->bs_size == 0) { |
| *cmpltflgp = BOOL_TRUE; |
| } else { |
| *cmpltflgp = BOOL_FALSE; |
| } |
| return RV_OK; |
| } |
| |
| /* if the resultant extent extends beyond the end of the |
| * file, shorten the extent to the nearest BBSIZE alignment |
| * at or beyond EOF. (Shorten to d_miniosz for realtime |
| * files). |
| */ |
| if (extsz > statp->bs_size - offset) { |
| int iosz; |
| |
| if (isrealtime) |
| iosz = da.d_miniosz; |
| else |
| iosz = BBSIZE; |
| extsz = statp->bs_size - offset; |
| extsz = (extsz + (off64_t)(iosz - 1)) |
| & |
| ~(off64_t)(iosz - 1); |
| mlog(MLOG_NITTY, |
| "adjusted top of extent to match file size " |
| "(rounded up %d): " |
| "offset %lld, sz %lld; bs_size %lld\n", |
| iosz, |
| offset, |
| extsz, |
| statp->bs_size); |
| } |
| |
| /* if the extent extends beyond the stop offset, |
| * shorten it to the stop offset. |
| */ |
| if (sosig && (extsz > stopoffset - offset)) { |
| extsz = stopoffset - offset; |
| assert(extsz >= 0); |
| assert(!(extsz & (off64_t)(BBSIZE - 1))); |
| mlog(MLOG_NITTY, |
| "adjusted top of extent " |
| "to adhere to stop offset: " |
| "offset %lld, sz %lld; bs_size %lld\n", |
| offset, |
| extsz, |
| statp->bs_size); |
| } |
| |
| /* I/O performance is better if we align the media write |
| * buffer to a page boundary. do this if the extent is |
| * at least a page in length. Also, necessary for real time |
| * files |
| */ |
| if (isrealtime || extsz >= PGALIGNTHRESH * PGSZ) { |
| size_t cnt_to_align; |
| cnt_to_align = (*dop->do_get_align_cnt)(drivep); |
| if ((size_t)cnt_to_align < 2*sizeof(extenthdr_t)) { |
| cnt_to_align += PGSZ; |
| } |
| |
| /* account for the DATA header following the alignment |
| */ |
| cnt_to_align -= sizeof(extenthdr_t); |
| |
| rv = dump_extenthdr(drivep, |
| contextp, |
| EXTENTHDR_TYPE_ALIGN, |
| 0, |
| 0, |
| (off64_t) |
| ((size_t)cnt_to_align |
| - |
| sizeof(extenthdr_t))); |
| if (rv != RV_OK) { |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; |
| return rv; |
| } |
| bytecnt += sizeof(extenthdr_t); |
| cnt_to_align -= sizeof(extenthdr_t); |
| rv = write_pad(drivep, cnt_to_align); |
| if (rv != RV_OK) { |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; /* moot: rv != OK */ |
| return rv; |
| } |
| bytecnt += (off64_t)cnt_to_align; |
| } |
| /* adjust the next offset |
| */ |
| assert((offset & (off64_t)(BBSIZE - 1)) == 0); |
| assert((extsz & (off64_t)(BBSIZE - 1)) == 0); |
| nextoffset = offset + extsz; |
| |
| /* dump the extent header |
| */ |
| rv = dump_extenthdr(drivep, |
| contextp, |
| EXTENTHDR_TYPE_DATA, |
| 0, |
| offset, |
| extsz); |
| if (rv != RV_OK) { |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; /* moot since rv != OK */ |
| return rv; |
| } |
| bytecnt += sizeof(extenthdr_t); |
| |
| /* dump the extent. if read fails to return all |
| * asked for, pad out the extent with zeros. necessary |
| * because the extent hdr is already out there! |
| */ |
| while (extsz) { |
| off64_t new_off; |
| int nread; |
| size_t reqsz; |
| size_t actualsz; |
| char *bufp; |
| |
| reqsz = extsz > (off64_t)INTGENMAX |
| ? |
| INTGENMAX |
| : |
| (size_t)extsz; |
| bufp = (*dop->do_get_write_buf)(drivep, |
| reqsz, |
| &actualsz); |
| assert(actualsz <= reqsz); |
| new_off = lseek64(gcp->eg_fd, offset, SEEK_SET); |
| if (new_off == (off64_t)(-1)) { |
| mlog(MLOG_NORMAL, _( |
| "can't lseek ino %llu\n"), |
| statp->bs_ino); |
| nread = 0; |
| } else { |
| nread = read(gcp->eg_fd, bufp, actualsz); |
| } |
| if (nread < 0) { |
| #ifdef HIDDEN |
| struct statvfs64 s; |
| |
| /* Be quiet if this is a swap file - #510197 */ |
| if ((fstatvfs64(gcp->eg_fd, &s) < 0) || |
| ((s.f_flag & ST_LOCAL) != 0)) |
| mlog(MLOG_NORMAL, _( |
| "can't read ino %llu at offset %d (act=%d req=%d) rt=%d\n"), |
| statp->bs_ino, new_off, actualsz, reqsz, isrealtime); |
| #endif /* HIDDEN */ |
| |
| nread = 0; |
| } |
| assert((size_t)nread <= actualsz); |
| mlog(MLOG_NITTY, |
| "read ino %llu offset %lld sz %d actual %d\n", |
| statp->bs_ino, |
| offset, |
| actualsz, |
| nread); |
| |
| /* must return entire buffer supplied by call to |
| * do_get_write_buf; so pad end with zeros. below |
| * we assume the short read implies EOF, so will |
| * then pad out remainder of extent as well. |
| */ |
| if ((size_t)nread < actualsz) { |
| memset((void *)(bufp + nread), |
| 0, |
| actualsz - (size_t)nread); |
| } |
| |
| rval = (*dop->do_write)(drivep, |
| bufp, |
| actualsz); |
| switch (rval) { |
| case 0: |
| rv = RV_OK; |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOM; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| break; |
| } |
| if (rv != RV_OK) { |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; /* moot: rv != OK */ |
| return rv; |
| } |
| bytecnt += (off64_t)actualsz; |
| extsz -= (off64_t)actualsz; |
| offset += (off64_t)actualsz; |
| |
| /* if we got a short read, assume we are at the |
| * end of the file; pad out the remainder of the |
| * extent to match the header. |
| */ |
| if ((size_t)nread < actualsz) { |
| mlog(MLOG_NITTY, |
| "padding remaind %lld bytes of extent\n", |
| extsz); |
| while (extsz) { |
| reqsz = extsz > (off64_t)INTGENMAX |
| ? |
| INTGENMAX |
| : |
| (size_t)extsz; |
| rv = write_pad(drivep, |
| (size_t)reqsz); |
| if (rv != RV_OK) { |
| *nextoffsetp = nextoffset; |
| *bytecntp = bytecnt; |
| *cmpltflgp = BOOL_TRUE; |
| return rv; |
| } |
| bytecnt += (off64_t)reqsz; |
| extsz -= (off64_t)reqsz; |
| offset += (off64_t)reqsz; |
| } |
| } |
| } |
| |
| /* made it! advance to the next extent if the current |
| * extent is completely dumped. |
| * !!! not be necessary, taken care of near the |
| * !!! top of the loop. |
| */ |
| if (nextoffset |
| >= |
| gcp->eg_nextbmapp->bmv_offset * (off64_t)BBSIZE |
| + |
| gcp->eg_nextbmapp->bmv_length * (off64_t)BBSIZE) { |
| mlog(MLOG_NITTY, |
| "advancing to next extent in bmap\n"); |
| gcp->eg_nextbmapp++; |
| gcp->eg_bmapix++; |
| } |
| } |
| /* NOTREACHED */ |
| } |
| |
| /* Note: assumes the pad fields in dst have been zeroed. */ |
| static void |
| copy_xfs_bstat(bstat_t *dst, struct xfs_bstat *src) |
| { |
| dst->bs_ino = src->bs_ino; |
| dst->bs_mode = src->bs_mode; |
| dst->bs_nlink = src->bs_nlink; |
| dst->bs_uid = src->bs_uid; |
| dst->bs_gid = src->bs_gid; |
| dst->bs_rdev = src->bs_rdev; |
| dst->bs_blksize = src->bs_blksize; |
| dst->bs_size = src->bs_size; |
| dst->bs_atime.tv_sec = src->bs_atime.tv_sec; |
| dst->bs_atime.tv_nsec = src->bs_atime.tv_nsec; |
| dst->bs_mtime.tv_sec = src->bs_mtime.tv_sec; |
| dst->bs_mtime.tv_nsec = src->bs_mtime.tv_nsec; |
| dst->bs_ctime.tv_sec = src->bs_ctime.tv_sec; |
| dst->bs_ctime.tv_nsec = src->bs_ctime.tv_nsec; |
| dst->bs_blocks = src->bs_blocks; |
| dst->bs_xflags = src->bs_xflags; |
| dst->bs_extsize = src->bs_extsize; |
| dst->bs_extents = src->bs_extents; |
| dst->bs_gen = src->bs_gen; |
| dst->bs_projid_lo = src->bs_projid_lo; |
| dst->bs_forkoff = 0; |
| dst->bs_projid_hi = src->bs_projid_hi; |
| dst->bs_dmevmask = src->bs_dmevmask; |
| dst->bs_dmstate = src->bs_dmstate; |
| } |
| |
| static rv_t |
| dump_filehdr(drive_t *drivep, |
| context_t *contextp, |
| struct xfs_bstat *statp, |
| off64_t offset, |
| int flags) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| register filehdr_t *fhdrp = contextp->cc_filehdrp; |
| filehdr_t tmpfhdrp; |
| int rval; |
| rv_t rv; |
| |
| (void)memset((void *)fhdrp, 0, sizeof(*fhdrp)); |
| if (statp) { |
| if (hsm_fs_ctxtp) { |
| HsmModifyInode(contextp->cc_hsm_f_ctxtp, statp); |
| } |
| copy_xfs_bstat(&fhdrp->fh_stat, statp); |
| } |
| fhdrp->fh_offset = offset; |
| fhdrp->fh_flags = flags | FILEHDR_FLAGS_CHECKSUM; |
| fhdrp->fh_checksum = calc_checksum(fhdrp, FILEHDR_SZ); |
| |
| xlate_filehdr(fhdrp, &tmpfhdrp, 1); |
| rval = write_buf((char *)&tmpfhdrp, |
| sizeof(tmpfhdrp), |
| (void *)drivep, |
| (gwbfp_t)dop->do_get_write_buf, |
| (wfp_t)dop->do_write); |
| |
| switch (rval) { |
| case 0: |
| rv = RV_OK; |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOM; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| break; |
| } |
| |
| return rv; |
| } |
| |
| static rv_t |
| dump_extenthdr(drive_t *drivep, |
| context_t *contextp, |
| int32_t type, |
| int32_t flags, |
| off64_t offset, |
| off64_t sz) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| register extenthdr_t *ehdrp = contextp->cc_extenthdrp; |
| extenthdr_t tmpehdrp; |
| int rval; |
| rv_t rv; |
| char typestr[20]; |
| |
| switch(type) { |
| case EXTENTHDR_TYPE_LAST: |
| strcpy(typestr, "LAST"); |
| break; |
| case EXTENTHDR_TYPE_ALIGN: |
| strcpy(typestr, "ALIGN"); |
| break; |
| case EXTENTHDR_TYPE_DATA: |
| strcpy(typestr, "DATA"); |
| break; |
| case EXTENTHDR_TYPE_HOLE: |
| strcpy(typestr, "HOLE"); |
| break; |
| default: |
| strcpy(typestr, "UNKNOWN"); |
| } |
| |
| mlog(MLOG_DEBUG, |
| "dumping extent type = %s offset = %lld size = %lld\n", |
| typestr, |
| offset, |
| sz); |
| |
| (void)memset((void *)ehdrp, 0, sizeof(*ehdrp)); |
| ehdrp->eh_type = type; |
| ehdrp->eh_flags = flags | EXTENTHDR_FLAGS_CHECKSUM; |
| ehdrp->eh_offset = offset; |
| ehdrp->eh_sz = sz; |
| ehdrp->eh_checksum = calc_checksum(ehdrp, EXTENTHDR_SZ); |
| |
| xlate_extenthdr(ehdrp, &tmpehdrp, 1); |
| rval = write_buf((char *)&tmpehdrp, |
| sizeof(tmpehdrp), |
| (void *)drivep, |
| (gwbfp_t)dop->do_get_write_buf, |
| (wfp_t)dop->do_write); |
| |
| switch (rval) { |
| case 0: |
| rv = RV_OK; |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOM; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| break; |
| } |
| |
| return rv; |
| } |
| |
| static rv_t |
| dump_dirent(drive_t *drivep, |
| context_t *contextp, |
| struct xfs_bstat *statp, |
| xfs_ino_t ino, |
| gen_t gen, |
| char *name, |
| size_t namelen) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| char *outbufp; |
| size_t direntbufsz = contextp->cc_mdirentbufsz; |
| size_t sz; |
| size_t name_offset; |
| int rval; |
| rv_t rv; |
| |
| if (sc_use_old_direntpr) { |
| name_offset = offsetofmember(direnthdr_v1_t, dh_name); |
| } else { |
| name_offset = offsetofmember(direnthdr_t, dh_name); |
| } |
| |
| sz = name_offset |
| + |
| namelen |
| + |
| 1; |
| sz = (sz + DIRENTHDR_ALIGN - 1) |
| & |
| ~(DIRENTHDR_ALIGN - 1); |
| |
| if (sz > direntbufsz) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "unable to dump " |
| "directory %llu entry %s (%llu): " |
| "name too long\n"), |
| statp->bs_ino, |
| name, |
| ino); |
| mlog_exit_hint(RV_INCOMPLETE); |
| return RV_OK; |
| } |
| |
| assert(sz <= UINT16MAX); |
| assert(sz >= DIRENTHDR_SZ); |
| |
| outbufp = malloc(sz); |
| |
| if (sc_use_old_direntpr) { |
| direnthdr_v1_t *dhdrp = (direnthdr_v1_t *)contextp->cc_mdirentbufp; |
| direnthdr_v1_t *tmpdhdrp = (direnthdr_v1_t *)outbufp; |
| |
| memset((void *)dhdrp, 0, sz); |
| dhdrp->dh_ino = ino; |
| dhdrp->dh_sz = (uint16_t)sz; |
| dhdrp->dh_gen = (uint16_t)(gen & DENTGENMASK); |
| if (name) { |
| strcpy(dhdrp->dh_name, name); |
| } |
| |
| dhdrp->dh_checksum = calc_checksum(dhdrp, DIRENTHDR_SZ); |
| |
| xlate_direnthdr_v1(dhdrp, tmpdhdrp, 1); |
| if (name) { |
| strcpy(tmpdhdrp->dh_name, name); |
| } |
| } else { |
| direnthdr_t *dhdrp = (direnthdr_t *)contextp->cc_mdirentbufp; |
| direnthdr_t *tmpdhdrp = (direnthdr_t *)outbufp; |
| |
| memset((void *)dhdrp, 0, sz); |
| dhdrp->dh_ino = ino; |
| dhdrp->dh_gen = gen; |
| dhdrp->dh_sz = (uint16_t)sz; |
| if (name) { |
| strcpy(dhdrp->dh_name, name); |
| } |
| |
| dhdrp->dh_checksum = calc_checksum(dhdrp, DIRENTHDR_SZ); |
| |
| xlate_direnthdr(dhdrp, tmpdhdrp, 1); |
| if (name) { |
| strcpy(tmpdhdrp->dh_name, name); |
| } |
| } |
| |
| rval = write_buf(outbufp, |
| sz, |
| (void *)drivep, |
| (gwbfp_t)dop->do_get_write_buf, |
| (wfp_t)dop->do_write); |
| free(outbufp); |
| switch (rval) { |
| case 0: |
| rv = RV_OK; |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOM; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| break; |
| } |
| |
| return rv; |
| } |
| |
| static bool_t |
| dump_session_inv(drive_t *drivep, |
| context_t *contextp, |
| media_hdr_t *mwhdrp, |
| content_inode_hdr_t *scwhdrp) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| ix_t strmix = drivep->d_index; |
| inv_stmtoken_t inv_stmt; |
| char *inv_sbufp; |
| size_t inv_sbufsz; |
| off64_t ncommitted; |
| bool_t ok; |
| bool_t done; |
| |
| /* if the inventory session token is null, skip |
| */ |
| if (sc_inv_sestoken == INV_TOKEN_NULL) { |
| return BOOL_TRUE; |
| } |
| |
| mlog(MLOG_VERBOSE, _( |
| "dumping session inventory\n")); |
| |
| /* get a buffer from the inventory manager |
| */ |
| inv_sbufp = 0; |
| inv_sbufsz = 0; |
| ok = inv_get_sessioninfo(sc_inv_sestoken, (void *)&inv_sbufp, &inv_sbufsz); |
| if (!ok) { |
| mlog(MLOG_NORMAL | MLOG_WARNING, _( |
| "unable to get session inventory to dump\n")); |
| return BOOL_TRUE; |
| } |
| assert(inv_sbufp); |
| |
| /* modify the write header to indicate the media file type. |
| */ |
| scwhdrp->cih_mediafiletype = CIH_MEDIAFILETYPE_INVENTORY; |
| |
| /* loop attempting to write a complete media file, |
| * until we are successful or until the media layer |
| * tells us to give up. |
| */ |
| for (done = BOOL_FALSE; !done;) { |
| uuid_t mediaid; |
| char medialabel[GLOBAL_HDR_STRING_SZ]; |
| bool_t partial; |
| int mediafileix; |
| int rval; |
| rv_t rv; |
| |
| mlog(MLOG_VERBOSE, _( |
| "beginning inventory media file\n")); |
| |
| partial = BOOL_FALSE; |
| rv = Media_mfile_begin(drivep, contextp, BOOL_FALSE); |
| switch(rv) { |
| case RV_OK: |
| break; |
| case RV_TIMEOUT: |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "media change timeout: " |
| "session inventory not dumped\n")); |
| mlog_exit_hint(RV_INV); |
| return BOOL_FALSE; |
| case RV_QUIT: |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "media change declined: " |
| "session inventory not dumped\n")); |
| mlog_exit_hint(RV_INV); |
| return BOOL_FALSE; |
| case RV_DRIVE: |
| case RV_ERROR: |
| case RV_CORE: |
| default: |
| return BOOL_FALSE; |
| } |
| |
| mlog(MLOG_VERBOSE, _( |
| "media file %u (media %u, file %u)\n"), |
| mwhdrp->mh_dumpfileix, |
| mwhdrp->mh_mediaix, |
| mwhdrp->mh_mediafileix); |
| |
| uuid_copy(mediaid, mwhdrp->mh_mediaid); |
| strcpy(medialabel, mwhdrp->mh_medialabel); |
| mediafileix = (int)mwhdrp->mh_mediafileix; |
| |
| rval = write_buf(inv_sbufp, |
| inv_sbufsz, |
| (void *)drivep, |
| (gwbfp_t)dop->do_get_write_buf, |
| (wfp_t)dop->do_write); |
| switch (rval) { |
| case 0: |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| partial = BOOL_TRUE; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| case DRIVE_ERROR_CORE: |
| default: |
| return BOOL_FALSE; |
| } |
| |
| mlog(MLOG_VERBOSE, _( |
| "ending inventory media file\n")); |
| ncommitted = 0; |
| rv = Media_mfile_end(drivep, |
| contextp, |
| mwhdrp, |
| &ncommitted, |
| partial); |
| switch(rv) { |
| case RV_OK: |
| break; |
| case RV_EOM: |
| partial = BOOL_TRUE; |
| break; |
| default: |
| return BOOL_FALSE; |
| } |
| |
| if (partial) { |
| mlog(MLOG_VERBOSE, _( |
| "encountered EOM while writing " |
| "inventory media file size %lld bytes\n"), |
| ncommitted); |
| mlog_exit_hint(RV_INV); |
| } else { |
| mlog(MLOG_VERBOSE, _( |
| "inventory media file size %lld bytes\n"), |
| ncommitted); |
| } |
| if (sc_inv_stmtokenp) { |
| inv_stmt = sc_inv_stmtokenp[strmix]; |
| } else { |
| inv_stmt = INV_TOKEN_NULL; |
| } |
| |
| if (inv_stmt != INV_TOKEN_NULL) { |
| mlog(MLOG_DEBUG, |
| "giving inventory " |
| "session dump media file\n"); |
| ok = inv_put_mediafile(inv_stmt, |
| &mediaid, |
| medialabel, |
| (uint)mediafileix, |
| (xfs_ino_t)0, |
| (off64_t)0, |
| (xfs_ino_t)0, |
| (off64_t)0, |
| ncommitted, |
| !partial, |
| BOOL_TRUE); |
| if (!ok) { |
| mlog(MLOG_NORMAL, _( |
| "inventory session media file " |
| "put failed\n")); |
| return BOOL_FALSE; |
| } |
| } |
| |
| done = !partial; |
| } |
| |
| return BOOL_TRUE; |
| } |
| |
| static void |
| dump_terminator(drive_t *drivep, context_t *contextp, media_hdr_t *mwhdrp) |
| { |
| off64_t ncommitted; |
| bool_t done; |
| |
| /* if the drive doesn't support use of stream terminators, don't bother |
| */ |
| if (!contextp->cc_Media_useterminatorpr) { |
| return; |
| } |
| |
| mlog(MLOG_VERBOSE, _( |
| "writing stream terminator\n")); |
| |
| /* modify the write header to indicate a terminator |
| */ |
| MEDIA_TERMINATOR_SET(mwhdrp); |
| |
| /* loop attempting to write a complete media file header |
| * until we are successful or until the media layer |
| * tells us to give up. |
| */ |
| for (done = BOOL_FALSE; !done;) { |
| bool_t partial; |
| rv_t rv; |
| |
| mlog(MLOG_VERBOSE, _( |
| "beginning media stream terminator\n")); |
| |
| partial = BOOL_FALSE; |
| rv = Media_mfile_begin(drivep, contextp, BOOL_FALSE); |
| switch(rv) { |
| case RV_OK: |
| break; |
| case RV_TIMEOUT: |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "media change timeout: " |
| "media stream terminator not written\n")); |
| return; |
| case RV_QUIT: |
| mlog(MLOG_VERBOSE | MLOG_WARNING, _( |
| "media change declined: " |
| "media stream terminator not written\n")); |
| return; |
| case RV_DRIVE: |
| case RV_ERROR: |
| case RV_CORE: |
| default: |
| return; |
| } |
| |
| mlog(MLOG_VERBOSE, _( |
| "media file %u (media %u, file %u)\n"), |
| mwhdrp->mh_dumpfileix, |
| mwhdrp->mh_mediaix, |
| mwhdrp->mh_mediafileix); |
| |
| mlog(MLOG_VERBOSE, _( |
| "ending media stream terminator\n")); |
| ncommitted = 0; |
| rv = Media_mfile_end(drivep, |
| contextp, |
| mwhdrp, |
| &ncommitted, |
| BOOL_FALSE); |
| switch(rv) { |
| case RV_OK: |
| break; |
| case RV_EOM: |
| partial = BOOL_TRUE; |
| break; |
| default: |
| return; |
| } |
| |
| if (partial) { |
| mlog(MLOG_VERBOSE, _( |
| "encountered EOM while writing " |
| "media stream terminator size %lld bytes\n"), |
| ncommitted); |
| } else { |
| mlog(MLOG_VERBOSE, _( |
| "media stream terminator size %lld bytes\n"), |
| ncommitted); |
| } |
| |
| done = !partial; |
| } |
| } |
| |
| static rv_t |
| write_pad(drive_t *drivep, size_t sz) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| int rval; |
| rv_t rv; |
| |
| rval = write_buf(0, |
| sz, |
| (void *)drivep, |
| (gwbfp_t)dop->do_get_write_buf, |
| (wfp_t)dop->do_write); |
| |
| switch (rval) { |
| case 0: |
| rv = RV_OK; |
| break; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| rv = RV_EOM; |
| break; |
| case DRIVE_ERROR_DEVICE: |
| rv = RV_DRIVE; |
| break; |
| case DRIVE_ERROR_CORE: |
| default: |
| rv = RV_CORE; |
| break; |
| } |
| |
| return rv; |
| } |
| |
| static void |
| inv_cleanup(void) |
| { |
| /* REFERENCED */ |
| bool_t ok; |
| |
| if (sc_inv_stmtokenp && sc_contextp) { |
| size_t strmix; |
| inv_stmtoken_t *inv_stmtp; |
| context_t *contextp; |
| for (strmix = 0, |
| inv_stmtp = sc_inv_stmtokenp, |
| contextp = sc_contextp |
| ; |
| strmix < drivecnt |
| ; |
| strmix++, |
| inv_stmtp++, |
| contextp++) { |
| bool_t interrupted; |
| interrupted = !contextp->cc_completepr; |
| if (*inv_stmtp == INV_TOKEN_NULL) { |
| continue; |
| } |
| mlog(MLOG_DEBUG, |
| "closing inventory stream %d%s\n", |
| strmix, |
| interrupted ? ": interrupted" : ""); |
| if (interrupted) mlog_exit_hint(RV_INTR); |
| ok = inv_stream_close(*inv_stmtp, interrupted); |
| assert(ok); |
| } |
| } |
| |
| if (sc_inv_sestoken != INV_TOKEN_NULL) { |
| ok = inv_writesession_close(sc_inv_sestoken); |
| assert(ok); |
| } |
| |
| if (sc_inv_idbtoken != INV_TOKEN_NULL) { |
| ok = inv_close(sc_inv_idbtoken); |
| assert(ok); |
| } |
| } |
| |
| /* This function returns with the proper media positioned at the proper place |
| * in the specified drive, with a write header layed down. The caller can |
| * immediately dump. The caller is expected to call Media_mfile_end when |
| * the media file is complete or EOM is encountered. |
| * |
| * Media_mfile_begin is partitioned into 4 coroutines, between which it |
| * readily jumps back and forth using gotos. So be careful about the scope |
| * of automatic variables. |
| */ |
| static rv_t |
| Media_mfile_begin(drive_t *drivep, context_t *contextp, bool_t intr_allowed) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| int dcaps = drivep->d_capabilities; |
| global_hdr_t *gwhdrp = drivep->d_gwritehdrp; |
| drive_hdr_t *dwhdrp = drivep->d_writehdrp; |
| media_hdr_t *mwhdrp = (media_hdr_t *)dwhdrp->dh_upper; |
| drive_hdr_t *drhdrp = drivep->d_readhdrp; |
| media_hdr_t *mrhdrp = (media_hdr_t *)drhdrp->dh_upper; |
| char *cmdlinemedialabel; |
| bool_t virginmediapr; |
| bool_t mediapresentpr; |
| bool_t prevmediapresentpr; |
| bool_t mediawrittentopr; |
| global_hdr_t saved_gwhdr; |
| int rval; |
| bool_t ok; |
| |
| /* sanity checks |
| */ |
| assert(BES_INIT == 0); |
| |
| mlog(MLOG_DEBUG | MLOG_MEDIA, |
| "Media op: begin media file\n"); |
| |
| /* the command line-specified media label is good only for the |
| * first media object written to. after that, the operator will |
| * be prompted for a label. To enforce this, cc_Media_firstlabel |
| * is saved in a temp var and NULLed. |
| */ |
| cmdlinemedialabel = contextp->cc_Media_firstlabel; |
| contextp->cc_Media_firstlabel = 0; |
| |
| /* dispatch based on entry state. invalidate entry state to assert |
| * each Media_mfile_begin is followed by and Media_mfile_end. |
| */ |
| prevmediapresentpr = BOOL_UNKNOWN; |
| { |
| bes_t entrystate; |
| entrystate = contextp->cc_Media_begin_entrystate; |
| contextp->cc_Media_begin_entrystate = BES_INVAL; |
| switch (entrystate) { |
| case BES_INIT: |
| mediawrittentopr = BOOL_FALSE; |
| mwhdrp->mh_mediaix = (uint32_t)(-1); |
| mwhdrp->mh_dumpfileix = (uint32_t)(-1); |
| if (dcaps & DRIVE_CAP_READ) { |
| mediapresentpr = BOOL_UNKNOWN; |
| virginmediapr = BOOL_UNKNOWN; |
| goto position; |
| } else { |
| mediapresentpr = BOOL_TRUE; |
| virginmediapr = BOOL_TRUE; |
| goto write; |
| } |
| case BES_ENDOK: |
| mediapresentpr = BOOL_TRUE; |
| virginmediapr = BOOL_FALSE; |
| mediawrittentopr = BOOL_TRUE; |
| goto write; |
| case BES_ENDEOM: |
| mediapresentpr = BOOL_TRUE; |
| virginmediapr = BOOL_FALSE; |
| mediawrittentopr = BOOL_TRUE; |
| goto changemedia; |
| default: |
| assert(0); |
| return RV_CORE; |
| } |
| } |
| |
| position: |
| /* loop until we are positioned either at end of recorded data |
| * or at a terminator, as appropriate, of some media object, or hit EOM. |
| * goto write or changemedia to get out of loop (or return on |
| * catastrophic condition). ensure that all but the first media file |
| * of a stream begins on virgin media. that is, dump streams may |
| * be concatenated but not jumbled. a dump stream must be virtually |
| * contiguous. |
| */ |
| for (;;) { |
| /* check if a stop has been requested |
| */ |
| if (intr_allowed && cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| |
| /* do a begin_read to see the disposition of the drive/media. |
| */ |
| rval = (*dop->do_begin_read)(drivep); |
| |
| /* update cc_Media_useterminatorpr after every begin_read, |
| * since begin_read will cause some unknown drive params |
| * to be resolved. |
| */ |
| update_cc_Media_useterminatorpr(drivep, contextp); |
| |
| switch(rval) { |
| case 0: |
| mlog_lock(); |
| mlog(MLOG_VERBOSE | MLOG_NOLOCK | MLOG_MEDIA, _( |
| "positioned at media file %u: " |
| "dump %u, " |
| "stream %u\n"), |
| mrhdrp->mh_mediafileix, |
| mrhdrp->mh_dumpmediaix, |
| drhdrp->dh_driveix); |
| mlog(MLOG_TRACE | MLOG_NOLOCK | MLOG_MEDIA, |
| "stream media file %u (%u in object), " |
| "stream media object %d\n", |
| mrhdrp->mh_dumpfileix, |
| mrhdrp->mh_dumpmediafileix, |
| mrhdrp->mh_mediaix); |
| mlog_unlock(); |
| |
| /* successfully read media file header. |
| * we know media must be present in drive, and |
| * contains at least one valid xfsdump, hence |
| * is not virgin. |
| */ |
| prevmediapresentpr = mediapresentpr; |
| mediapresentpr = BOOL_TRUE; |
| virginmediapr = BOOL_FALSE; |
| |
| /* do an end_read. the next begin_read will |
| * position in preparation for appending. |
| * if terminator, back up, we'll overwrite it. |
| * also be sure we can append dumps. |
| * if we back over a terminator which is the |
| * first media file on the media object, make the |
| * media object a virgin. |
| * also, check for erase option. |
| */ |
| (*dop->do_end_read)(drivep); |
| |
| switch(Media_erasechk(drivep, |
| dcaps, |
| intr_allowed, |
| prevmediapresentpr)) { |
| case RV_OK: |
| goto erasemedia; |
| case RV_INTR: |
| return RV_INTR; |
| default: |
| break; |
| } |
| |
| if ((int32_t)mwhdrp->mh_mediaix >= 0) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "cannot interleave dump streams: " |
| "must supply a blank media object\n")); |
| goto changemedia; |
| } |
| if (!(dcaps & DRIVE_CAP_APPEND)) { |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_MEDIA, _( |
| "media contains valid xfsdump " |
| "but does not support append\n")); |
| goto changemedia; |
| } |
| if (MEDIA_TERMINATOR_CHK(mrhdrp)) { |
| int status; |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "stream terminator found\n")); |
| assert(contextp->cc_Media_useterminatorpr); |
| assert(dcaps & DRIVE_CAP_BSF); /* redundant */ |
| status = 0; |
| rval = (*dop->do_bsf)(drivep, 0, &status); |
| assert(rval == 0); |
| if (status == DRIVE_ERROR_DEVICE) { |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_MEDIA, _( |
| "encountered media error " |
| "attempting BSF\n")); |
| goto changemedia; |
| } |
| if (mrhdrp->mh_mediafileix == 0) { |
| virginmediapr = BOOL_TRUE; |
| } |
| goto write; |
| } |
| continue; |
| case DRIVE_ERROR_FOREIGN: |
| prevmediapresentpr = mediapresentpr; |
| mediapresentpr = BOOL_TRUE; |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "media contains non-xfsdump data " |
| "or a corrupt xfsdump media file header " |
| "at beginning of media\n")); |
| mlog_exit_hint(RV_CORRUPT); |
| |
| switch(Media_erasechk(drivep, |
| dcaps, |
| intr_allowed, |
| prevmediapresentpr)) { |
| case RV_OK: |
| goto erasemedia; |
| case RV_INTR: |
| return RV_INTR; |
| default: |
| break; |
| } |
| |
| if (dlog_allowed()) { |
| bool_t ok; |
| ok = Media_prompt_overwrite(drivep); |
| if (intr_allowed && cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| if (!ok) { |
| goto changemedia; |
| } |
| } |
| |
| if (!(dcaps & DRIVE_CAP_OVERWRITE)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "unable to overwrite\n")); |
| goto changemedia; |
| } else { |
| int status; |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "repositioning to overwrite\n")); |
| assert(dcaps & DRIVE_CAP_BSF); |
| status = 0; |
| rval = (*dop->do_bsf)(drivep, 0, &status); |
| assert(rval == 0); |
| if (status == DRIVE_ERROR_DEVICE) { |
| return RV_DRIVE; |
| } |
| |
| virginmediapr = BOOL_TRUE; |
| |
| goto write; |
| } |
| |
| case DRIVE_ERROR_OVERWRITE: |
| prevmediapresentpr = mediapresentpr; |
| mediapresentpr = BOOL_TRUE; |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "media may contain data. " |
| "Overwrite option specified\n")); |
| |
| if (dlog_allowed()) { |
| bool_t ok; |
| ok = Media_prompt_overwrite(drivep); |
| if (intr_allowed && cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| if (!ok) { |
| goto changemedia; |
| } |
| } |
| virginmediapr = BOOL_TRUE; /* like a virgin */ |
| goto write; |
| case DRIVE_ERROR_BLANK: |
| prevmediapresentpr = mediapresentpr; |
| mediapresentpr = BOOL_TRUE; |
| virginmediapr = BOOL_TRUE; |
| goto write; |
| case DRIVE_ERROR_MEDIA: |
| prevmediapresentpr = mediapresentpr; |
| mediapresentpr = BOOL_TRUE; |
| goto changemedia; |
| case DRIVE_ERROR_DEVICE: |
| return RV_DRIVE; |
| case DRIVE_ERROR_EOD: |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "at end of data\n")); |
| prevmediapresentpr = mediapresentpr; |
| mediapresentpr = BOOL_TRUE; |
| virginmediapr = BOOL_FALSE; |
| |
| switch(Media_erasechk(drivep, |
| dcaps, |
| intr_allowed, |
| prevmediapresentpr)) { |
| case RV_OK: |
| goto erasemedia; |
| case RV_INTR: |
| return RV_INTR; |
| default: |
| break; |
| } |
| |
| if (contextp->cc_Media_useterminatorpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "encountered EOD but expecting a media " |
| "stream terminator: " |
| "assuming full media\n")); |
| goto changemedia; |
| } else { |
| goto write; |
| } |
| case DRIVE_ERROR_EOM: |
| prevmediapresentpr = mediapresentpr; |
| mediapresentpr = BOOL_TRUE; |
| virginmediapr = BOOL_FALSE; |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "encountered EOM: media is full\n")); |
| |
| switch(Media_erasechk(drivep, |
| dcaps, |
| intr_allowed, |
| prevmediapresentpr)) { |
| case RV_OK: |
| goto erasemedia; |
| case RV_INTR: |
| return RV_INTR; |
| default: |
| break; |
| } |
| |
| goto changemedia; |
| case DRIVE_ERROR_CORRUPTION: |
| case DRIVE_ERROR_VERSION: |
| case DRIVE_ERROR_FORMAT: |
| prevmediapresentpr = mediapresentpr; |
| mediapresentpr = BOOL_TRUE; |
| virginmediapr = BOOL_FALSE; |
| |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "encountered corrupt or foreign data: " |
| "assuming corrupted media\n")); |
| mlog_exit_hint(RV_CORRUPT); |
| |
| switch(Media_erasechk(drivep, |
| dcaps, |
| intr_allowed, |
| prevmediapresentpr)) { |
| case RV_OK: |
| goto erasemedia; |
| case RV_INTR: |
| return RV_INTR; |
| default: |
| break; |
| } |
| |
| if (contextp->cc_Media_useterminatorpr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "encountered corrupt or foreign data " |
| "but expecting a media " |
| "stream terminator: " |
| "assuming corrupted media\n")); |
| mlog_exit_hint(RV_CORRUPT); |
| goto changemedia; |
| } else if (!(dcaps & DRIVE_CAP_OVERWRITE)) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "encountered corrupt or foreign data: " |
| "unable to overwrite: " |
| "assuming corrupted media\n")); |
| mlog_exit_hint(RV_CORRUPT); |
| goto changemedia; |
| } else { |
| int status; |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "encountered corrupt or foreign data: " |
| "repositioning to overwrite\n")); |
| mlog_exit_hint(RV_CORRUPT); |
| assert(dcaps & DRIVE_CAP_BSF); |
| status = 0; |
| rval = (*dop->do_bsf)(drivep, 0, &status); |
| assert(rval == 0); |
| if (status == DRIVE_ERROR_DEVICE) { |
| return RV_DRIVE; |
| } |
| goto write; |
| } |
| case DRIVE_ERROR_STOP: |
| return RV_INTR; |
| case DRIVE_ERROR_INVAL: |
| return RV_ERROR; |
| case DRIVE_ERROR_EOF: |
| default: |
| mlog(MLOG_NORMAL | MLOG_ERROR | MLOG_MEDIA, _( |
| "unexpected error from do_begin_read: %d\n"), |
| rval); |
| return RV_CORE; |
| } |
| } |
| /* NOTREACHED */ |
| |
| erasemedia: |
| mlog(MLOG_VERBOSE | MLOG_WARNING | MLOG_MEDIA, _( |
| "erasing media\n")); |
| rval = (*dop->do_erase)(drivep); |
| if (rval) { |
| return RV_DRIVE; |
| } |
| prevmediapresentpr = mediapresentpr; |
| mediapresentpr = BOOL_UNKNOWN; |
| virginmediapr = BOOL_UNKNOWN; |
| mediawrittentopr = BOOL_FALSE; |
| goto position; |
| |
| changemedia: |
| /* if the drive does not support media change, quit. |
| */ |
| if (!(dcaps & DRIVE_CAP_REMOVABLE)) { |
| return RV_ERROR; |
| } |
| |
| /* first eject the current media object if capability supported |
| */ |
| assert(mediapresentpr != BOOL_UNKNOWN); |
| if (mediapresentpr == BOOL_TRUE) { |
| if (dcaps & DRIVE_CAP_EJECT) { |
| rval = (*dop->do_eject_media)(drivep); |
| if (rval) { |
| return RV_DRIVE; |
| } |
| } |
| } |
| |
| /* if dialogs not allowed, we are done. |
| */ |
| if (!dlog_allowed()) { |
| return RV_QUIT; /* this return value will cause approp. msg */ |
| } |
| |
| /* If an alert program has been specified, run it |
| */ |
| if (media_change_alert_program != NULL) |
| system(media_change_alert_program); |
| |
| /* if media change prompt declined or times out, |
| * we are done |
| */ |
| if (drivecnt > 1 && !stdoutpiped) { |
| ix_t thrdix = drivep->d_index; |
| assert(sistr); |
| mlog(MLOG_NORMAL | MLOG_NOTE | MLOG_MEDIA, _( |
| "please change media: " |
| "type %s to confirm media change\n"), |
| sistr); |
| set_mcflag(thrdix); |
| while (sc_mcflag[thrdix]) { |
| sleep(2); |
| if (cldmgr_stop_requested()) { |
| clr_mcflag(thrdix); |
| return RV_INTR; |
| } |
| } |
| ok = BOOL_TRUE; |
| } else { |
| ok = Media_prompt_change(drivep); |
| } |
| if (intr_allowed && cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| if (!ok) { |
| return RV_QUIT; |
| } |
| |
| /* we know nothing about the media after a media change |
| */ |
| prevmediapresentpr = mediapresentpr; |
| mediapresentpr = BOOL_UNKNOWN; |
| virginmediapr = BOOL_UNKNOWN; |
| mediawrittentopr = BOOL_FALSE; |
| |
| goto position; |
| |
| write: |
| assert(mediapresentpr == BOOL_TRUE); |
| assert(virginmediapr != BOOL_UNKNOWN); |
| |
| if (intr_allowed && cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| |
| /* bump the media header indices here. NOTE: will rescind these |
| * if the subsequent do_begin_write fails. this will be done by |
| * making a copy of the global write header, and copying it |
| * back on failure. |
| */ |
| saved_gwhdr = *gwhdrp; |
| |
| if (mediawrittentopr) { |
| mwhdrp->mh_dumpmediafileix++; |
| } else { |
| mwhdrp->mh_dumpmediafileix = 0; |
| } |
| mwhdrp->mh_dumpfileix++; /* pre-initialized to -1 */ |
| if (virginmediapr) { |
| mwhdrp->mh_mediafileix = 0; |
| mwhdrp->mh_dumpmediaix = 0; |
| } else { |
| if (mwhdrp->mh_dumpmediafileix == 0) { |
| mwhdrp->mh_dumpmediaix = mrhdrp->mh_dumpmediaix + 1; |
| } |
| if (mediawrittentopr) { |
| mwhdrp->mh_mediafileix++; |
| } else { |
| mwhdrp->mh_mediafileix = mrhdrp->mh_mediafileix; |
| if (!MEDIA_TERMINATOR_CHK(mrhdrp)) { |
| mwhdrp->mh_mediafileix++; |
| } |
| } |
| } |
| |
| if (!mediawrittentopr) { |
| mwhdrp->mh_mediaix++; /* pre-initialized to -1 */ |
| } |
| |
| assert(mwhdrp->mh_mediaix != (uint32_t)(-1)); |
| assert(mwhdrp->mh_dumpfileix != (uint32_t)(-1)); |
| |
| /* do not allow interleaving of media files from different xfsdumps. |
| */ |
| if (mwhdrp->mh_mediaix != 0 |
| && |
| mwhdrp->mh_dumpmediafileix == 0 |
| && |
| ! virginmediapr) { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "cannot interleave dump streams: must supply a blank " |
| "media object\n")); |
| *gwhdrp = saved_gwhdr; |
| goto changemedia; |
| } |
| |
| /* update the media object previous id and label |
| */ |
| if (!mediawrittentopr && mwhdrp->mh_dumpfileix != 0) { |
| uuid_copy(mwhdrp->mh_prevmediaid, mwhdrp->mh_mediaid); |
| (void)strncpyterm(mwhdrp->mh_prevmedialabel, |
| mwhdrp->mh_medialabel, |
| sizeof(mwhdrp->mh_medialabel)); |
| } |
| |
| /* update the media object current id and label |
| */ |
| if (!mediawrittentopr) { |
| if (mwhdrp->mh_mediafileix == 0) { |
| char labelbuf[GLOBAL_HDR_STRING_SZ]; |
| |
| uuid_generate(mwhdrp->mh_mediaid); |
| |
| if (!cmdlinemedialabel |
| && |
| !drivep->d_isnamedpipepr |
| && |
| !drivep->d_isunnamedpipepr |
| && |
| dlog_allowed()) { |
| cmdlinemedialabel = Media_prompt_label(drivep, |
| labelbuf, |
| sizeof(labelbuf)); |
| if (intr_allowed && cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| } |
| if (cmdlinemedialabel && strlen(cmdlinemedialabel)) { |
| (void)strncpyterm(mwhdrp->mh_medialabel, |
| cmdlinemedialabel, |
| sizeof(mwhdrp->mh_medialabel)); |
| } else { |
| (void)memset((void *)mwhdrp->mh_medialabel, |
| 0, |
| sizeof(mwhdrp->mh_medialabel)); |
| if (!pipeline) { |
| mlog(MLOG_VERBOSE |
| | |
| MLOG_WARNING |
| | |
| MLOG_MEDIA, _( |
| "no media label specified\n")); |
| } |
| } |
| } else { |
| assert(!virginmediapr); |
| uuid_copy(mwhdrp->mh_mediaid, mrhdrp->mh_mediaid); |
| (void)strncpyterm(mwhdrp->mh_medialabel, |
| mrhdrp->mh_medialabel, |
| sizeof(mwhdrp->mh_medialabel)); |
| } |
| } |
| |
| mediawrittentopr = BOOL_TRUE; |
| |
| /* write hdr is prepared. place it on media |
| */ |
| if (intr_allowed && cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| rval = (*dop->do_begin_write)(drivep); |
| switch(rval) { |
| case 0: |
| return RV_OK; |
| case DRIVE_ERROR_EOM: |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "encountered end of media " |
| "while attempting to begin new media file\n")); |
| *gwhdrp = saved_gwhdr; |
| goto changemedia; |
| case DRIVE_ERROR_MEDIA: |
| *gwhdrp = saved_gwhdr; |
| goto changemedia; |
| case DRIVE_ERROR_DEVICE: |
| return RV_DRIVE; |
| default: |
| return RV_CORE; |
| } |
| } |
| |
| /* ARGSUSED */ |
| static rv_t |
| Media_mfile_end(drive_t *drivep, |
| context_t *contextp, |
| media_hdr_t *mwhdrp, |
| off64_t *ncommittedp, |
| bool_t hit_eom) |
| { |
| drive_ops_t *dop = drivep->d_opsp; |
| int rval; |
| |
| mlog(MLOG_DEBUG | MLOG_MEDIA, |
| "Media op: end media file\n"); |
| |
| assert(contextp->cc_Media_begin_entrystate == BES_INVAL); |
| |
| /* call drive's end_write op to flush the tail of the media file |
| * if has previously hit EOM, this is moot. |
| */ |
| rval = (dop->do_end_write)(drivep, ncommittedp); |
| if (hit_eom) { |
| assert(!rval); |
| contextp->cc_Media_begin_entrystate = BES_ENDEOM; |
| return RV_EOM; |
| } |
| switch(rval) { |
| case 0: |
| contextp->cc_Media_begin_entrystate = BES_ENDOK; |
| return RV_OK; |
| case DRIVE_ERROR_MEDIA: |
| case DRIVE_ERROR_EOM: |
| mlog(MLOG_VERBOSE | MLOG_MEDIA, _( |
| "encountered end of media " |
| "while ending media file\n")); |
| mlog_exit_hint(RV_EOM); |
| contextp->cc_Media_begin_entrystate = BES_ENDEOM; |
| return RV_EOM; |
| case DRIVE_ERROR_DEVICE: |
| contextp->cc_Media_begin_entrystate = BES_INVAL; |
| return RV_DRIVE; |
| default: |
| contextp->cc_Media_begin_entrystate = BES_INVAL; |
| return RV_CORE; |
| } |
| |
| } |
| |
| static bool_t |
| Media_prompt_overwrite(drive_t *drivep) |
| { |
| 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 responseix; |
| ix_t sigintix; |
| |
| retry: |
| preamblecnt = 0; |
| fold_init(fold, "media overwrite dialog", '='); |
| preamblestr[preamblecnt++ ] = "\n"; |
| preamblestr[preamblecnt++] = fold; |
| preamblestr[preamblecnt++ ] = "\n\n"; |
| assert(preamblecnt <= PREAMBLEMAX); |
| dlog_begin(preamblestr, preamblecnt); |
| |
| /* query: ask if overwrite ok |
| */ |
| sprintf(question, |
| "overwrite data on media in " |
| "drive %u?\n", |
| (unsigned int)drivep->d_index); |
| querycnt = 0; |
| querystr[querycnt++] = question; |
| assert(querycnt <= QUERYMAX); |
| choicecnt = 0; |
| dontix = choicecnt; |
| choicestr[choicecnt++ ] = "don't overwrite"; |
| doix = choicecnt; |
| choicestr[choicecnt++ ] = "overwrite"; |
| 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++ ] = "media will be overwritten\n"; |
| } else if (responseix == dontix) { |
| ackstr[ackcnt++ ] = "media will NOT be overwritten\n"; |
| } else { |
| 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 overwrite dialog\n"); |
| goto retry; |
| } |
| |
| |
| return responseix == doix; |
| } |
| |
| static rv_t |
| Media_erasechk(drive_t *drivep, |
| int dcaps, |
| bool_t intr_allowed, |
| bool_t prevmediapresentpr) |
| { |
| if (prevmediapresentpr == BOOL_TRUE) { |
| return RV_NOTOK; |
| } |
| |
| if (sc_preerasepr) { |
| if (dcaps & DRIVE_CAP_ERASE) { |
| if (dlog_allowed()) { |
| bool_t ok; |
| ok = Media_prompt_erase(drivep); |
| if (intr_allowed && cldmgr_stop_requested()) { |
| return RV_INTR; |
| } |
| if (ok) { |
| return RV_OK; |
| } else { |
| return RV_NOTOK; |
| } |
| } else { |
| return RV_OK; |
| } |
| } else { |
| mlog(MLOG_NORMAL | MLOG_WARNING | MLOG_MEDIA, _( |
| "drive does not support media erase: " |
| "ignoring -%c option\n"), |
| GETOPT_ERASE); |
| return RV_NOTOK; |
| } |
| } else { |
| return RV_NOTOK; |
| } |
| } |
| |
| static bool_t |
| Media_prompt_erase(drive_t *drivep) |
| { |
| 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 responseix; |
| ix_t sigintix; |
| |
| retry: |
| preamblecnt = 0; |
| fold_init(fold, "media erase dialog", '='); |
| preamblestr[preamblecnt++ ] = "\n"; |
| preamblestr[preamblecnt++] = fold; |
| preamblestr[preamblecnt++ ] = "\n\n"; |
| assert(preamblecnt <= PREAMBLEMAX); |
| dlog_begin(preamblestr, preamblecnt); |
| |
| /* query: ask if overwrite ok |
| */ |
| sprintf(question, |
| "pre-erase (-%c) option specified " |
| "and non-blank media encountered:\n" |
| "please confirm media erase " |
| "drive %u\n", |
| GETOPT_ERASE, |
| (unsigned int)drivep->d_index); |
| querycnt = 0; |
| querystr[querycnt++] = question; |
| assert(querycnt <= QUERYMAX); |
| choicecnt = 0; |
| dontix = choicecnt; |
| choicestr[choicecnt++ ] = "don't erase"; |
| doix = choicecnt; |
| choicestr[choicecnt++ ] = "erase"; |
| 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++ ] = "media will be erased\n"; |
| } else if (responseix == dontix) { |
| ackstr[ackcnt++ ] = "media will NOT be erased\n"; |
| } else { |
| 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 erase dialog\n"); |
| goto retry; |
| } |
| |
| |
| return responseix == doix; |
| } |
| |
| static void |
| Media_prompt_label_cb(void *uctxp, dlog_pcbp_t pcb, void *pctxp) |
| { |
| drive_t *drivep = (drive_t *)uctxp; |
| |
| /* query: ask for a label |
| */ |
| (*pcb)(pctxp, |
| "please enter label for media in " |
| "drive %u", |
| drivep->d_index); |
| } |
| |
| static char * |
| Media_prompt_label(drive_t *drivep, char *bufp, size_t bufsz) |
| { |
| fold_t fold; |
| char *preamblestr[PREAMBLEMAX]; |
| size_t preamblecnt; |
| char *ackstr[ACKMAX]; |
| size_t ackcnt; |
| char *postamblestr[POSTAMBLEMAX]; |
| size_t postamblecnt; |
| const ix_t timeoutix = 1; |
| const ix_t abortix = 2; |
| const ix_t sigintix = 3; |
| const ix_t okix = 4; |
| ix_t responseix; |
| |
| retry: |
| preamblecnt = 0; |
| fold_init(fold, "media label dialog", '='); |
| preamblestr[preamblecnt++ ] = "\n"; |
| preamblestr[preamblecnt++] = fold; |
| preamblestr[preamblecnt++ ] = "\n\n"; |
| assert(preamblecnt <= PREAMBLEMAX); |
| dlog_begin(preamblestr, preamblecnt); |
| |
| responseix = dlog_string_query(Media_prompt_label_cb, |
| (void *)drivep, |
| bufp, |
| bufsz, |
| DLOG_TIMEOUT, |
| timeoutix,/* timeout ix */ |
| sigintix, /* sigint ix */ |
| abortix, /* sighup ix */ |
| abortix, /* sigquit ix */ |
| okix); /* ok ix */ |
| ackcnt = 0; |
| if (responseix == okix) { |
| ackstr[ackcnt++ ] = "media label entered: \""; |
| ackstr[ackcnt++] = bufp; |
| ackstr[ackcnt++ ] = "\"\n"; |
| } else if (responseix == timeoutix) { |
| ackstr[ackcnt++ ] = "timeout: media label left blank\n"; |
| } else if (responseix == sigintix) { |
| ackstr[ackcnt++ ] = "keyboard interrupt\n"; |
| } else { |
| ackstr[ackcnt++ ] = "abort\n"; |
| } |
| |
| assert(ackcnt <= ACKMAX); |
| dlog_string_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 0; |
| } |
| 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 0; |
| } |
| mlog(MLOG_DEBUG, |
| "retrying media label dialog\n"); |
| goto retry; |
| } |
| |
| |
| if (responseix == okix) { |
| return bufp; |
| } else { |
| return 0; |
| } |
| } |
| |
| static void |
| set_mcflag(ix_t thrdix) |
| { |
| lock(); |
| sc_mcflag[thrdix] = BOOL_TRUE; |
| content_media_change_needed = BOOL_TRUE; |
| unlock(); |
| } |
| |
| static void |
| clr_mcflag(ix_t thrdix) |
| { |
| lock(); |
| sc_mcflag[thrdix] = BOOL_FALSE; |
| for (thrdix = 0; thrdix < drivecnt; thrdix++) { |
| if (sc_mcflag[thrdix]) { |
| unlock(); |
| return; |
| } |
| } |
| content_media_change_needed = BOOL_FALSE; |
| unlock(); |
| } |
| |
| static bool_t |
| check_complete_flags(void) |
| { |
| ix_t strmix; |
| bool_t completepr = BOOL_TRUE; |
| |
| for (strmix = 0; strmix < drivecnt; strmix++) { |
| context_t *contextp = &sc_contextp[strmix]; |
| if (!contextp->cc_completepr) { |
| completepr = BOOL_FALSE; |
| break; |
| } |
| } |
| |
| return completepr; |
| } |
| |
| extern bool_t |
| is_quota_file(ino_t ino) |
| { |
| int i; |
| |
| for(i = 0; i < (sizeof(quotas) / sizeof(quotas[0])); i++) { |
| if (quotas[i].savequotas && ino == quotas[i].quotaino) |
| return BOOL_TRUE; |
| } |
| return BOOL_FALSE; |
| } |
| |
| #define REPQUOTA "xfs_quota" |
| |
| static bool_t |
| save_quotas(char *mntpnt, quota_info_t *quotainfo) |
| { |
| int sts = 0; |
| char buf[1024] = ""; |
| int fd; |
| char tmp; |
| struct stat statb; |
| |
| mlog(MLOG_VERBOSE, _( |
| "saving %s information for: %s\n"), quotainfo->desc, mntpnt); |
| |
| if(unlink(quotainfo->quotapath) == 0) { |
| mlog(MLOG_WARNING, _("overwriting: %s\n"), quotainfo->quotapath); |
| } |
| else { |
| if(errno != ENOENT) { |
| mlog(MLOG_ERROR, _( |
| "unable to remove %s: %s\n"), |
| quotainfo->quotapath, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| } |
| |
| sprintf(buf, |
| "%s -x -c 'dump %s %s' %s 2> /dev/null", |
| REPQUOTA, |
| quotainfo->repquotaargs, |
| quotainfo->quotapath, |
| mntpnt); |
| |
| mlog(MLOG_NITTY, "saving quotas: %s\n", buf); |
| |
| sts = system(buf); |
| if(sts != 0) { |
| mlog(MLOG_ERROR, _( |
| "%s failed with exit status: %d\n"), REPQUOTA, |
| sts == -1 ? -1 : WEXITSTATUS(sts)); |
| return BOOL_FALSE; |
| } |
| if((fd = open(quotainfo->quotapath, O_RDONLY|O_DSYNC)) < 0) { |
| mlog(MLOG_ERROR, _( |
| "open failed %s: %s\n"), |
| quotainfo->quotapath, |
| strerror(errno)); |
| return BOOL_FALSE; |
| } |
| if(fstat(fd, &statb) < 0) { |
| mlog(MLOG_ERROR, _( |
| "stat failed %s: %s\n"), |
| quotainfo->quotapath, |
| strerror(errno)); |
| close(fd); |
| return BOOL_FALSE; |
| } |
| quotainfo->quotaino = statb.st_ino; |
| |
| /* open and read dump file to ensure it is in the dump */ |
| read(fd, &tmp, 1); |
| close(fd); |
| return BOOL_TRUE; |
| } |
| |
| static int |
| getxfsqstat(char *fsname) |
| { |
| fs_quota_stat_t qstat; |
| |
| /* |
| * See if quotas is on. If not, nada. |
| */ |
| memset(&qstat, 0, sizeof(fs_quota_stat_t)); |
| if (quotactl(QCMD(Q_XGETQSTAT, 0), fsname, 0, |
| (caddr_t)&qstat) < 0) { |
| return (-1); |
| } |
| return ((int)qstat.qs_flags); |
| } |