blob: 64622674bcb8aad35c3d6930faadd934fbc59f77 [file] [log] [blame]
/*
* 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, &quotas[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