freebsd-nq/sbin/dump/main.c
Kirk McKusick d46b52859a Convert the live dump command (`dump -L') to use mksnap_ffs instead
of trying to directly create the snapshot itself. This change allows
users logged into the system as operator to run live dumps.

Note that dump no longer tries to create the snapshot in the root of
the filesystem, but rather in a .snap directory in the root of the
filesystem. The reason is that the operator is usually not permitted
to write into the root of the filesystem. The newfs command and
background fsck have both been modified to create a .snap directory
in the root of the filesystem, but if neither of these have been run,
then the .snap directory must be created manually by the superuser
before a live dump can be run. The .snap directory should be owned
by user root and group operator and set to mode 770.
2003-11-16 08:01:58 +00:00

732 lines
19 KiB
C

/*-
* Copyright (c) 1980, 1991, 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1980, 1991, 1993, 1994\n\
The Regents of the University of California. All rights reserved.\n";
#endif /* not lint */
#ifndef lint
#if 0
static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/1/95";
#endif
static const char rcsid[] =
"$FreeBSD$";
#endif /* not lint */
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/mount.h>
#include <sys/disklabel.h>
#include <ufs/ufs/dinode.h>
#include <ufs/ufs/ufsmount.h>
#include <ufs/ffs/fs.h>
#include <protocols/dumprestore.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <fstab.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <timeconv.h>
#include <unistd.h>
#include "dump.h"
#include "pathnames.h"
int notify = 0; /* notify operator flag */
int snapdump = 0; /* dumping live filesystem, so use snapshot */
int blockswritten = 0; /* number of blocks written on current tape */
int tapeno = 0; /* current tape number */
int density = 0; /* density in bytes/0.1" " <- this is for hilit19 */
int ntrec = NTREC; /* # tape blocks in each tape record */
int cartridge = 0; /* Assume non-cartridge tape */
int cachesize = 0; /* block cache size (in bytes), defaults to 0 */
long dev_bsize = 1; /* recalculated below */
long blocksperfile; /* output blocks per file */
char *host = NULL; /* remote host (if any) */
/*
* Possible superblock locations ordered from most to least likely.
*/
static int sblock_try[] = SBLOCKSEARCH;
static char *getmntpt(char *, int *);
static long numarg(const char *, long, long);
static void obsolete(int *, char **[]);
static void usage(void) __dead2;
int
main(int argc, char *argv[])
{
struct stat sb;
ino_t ino;
int dirty;
union dinode *dp;
struct fstab *dt;
char *map, *mntpt;
int ch, mode, mntflags;
int i, anydirskipped, bflag = 0, Tflag = 0, honorlevel = 1;
int just_estimate = 0;
ino_t maxino;
char *tmsg;
spcl.c_date = _time_to_time64(time(NULL));
tsize = 0; /* Default later, based on 'c' option for cart tapes */
if ((tape = getenv("TAPE")) == NULL)
tape = _PATH_DEFTAPE;
dumpdates = _PATH_DUMPDATES;
temp = _PATH_DTMP;
if (TP_BSIZE / DEV_BSIZE == 0 || TP_BSIZE % DEV_BSIZE != 0)
quit("TP_BSIZE must be a multiple of DEV_BSIZE\n");
level = '0';
if (argc < 2)
usage();
obsolete(&argc, &argv);
while ((ch = getopt(argc, argv,
"0123456789aB:b:C:cD:d:f:h:LnSs:T:uWw")) != -1)
switch (ch) {
/* dump level */
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
level = ch;
break;
case 'a': /* `auto-size', Write to EOM. */
unlimited = 1;
break;
case 'B': /* blocks per output file */
blocksperfile = numarg("number of blocks per file",
1L, 0L);
break;
case 'b': /* blocks per tape write */
ntrec = numarg("number of blocks per write",
1L, 1000L);
break;
case 'C':
cachesize = numarg("cachesize", 0, 0) * 1024 * 1024;
break;
case 'c': /* Tape is cart. not 9-track */
cartridge = 1;
break;
case 'D':
dumpdates = optarg;
break;
case 'd': /* density, in bits per inch */
density = numarg("density", 10L, 327670L) / 10;
if (density >= 625 && !bflag)
ntrec = HIGHDENSITYTREC;
break;
case 'f': /* output file */
tape = optarg;
break;
case 'h':
honorlevel = numarg("honor level", 0L, 10L);
break;
case 'L':
snapdump = 1;
break;
case 'n': /* notify operators */
notify = 1;
break;
case 'S': /* exit after estimating # of tapes */
just_estimate = 1;
break;
case 's': /* tape size, feet */
tsize = numarg("tape size", 1L, 0L) * 12 * 10;
break;
case 'T': /* time of last dump */
spcl.c_ddate = unctime(optarg);
if (spcl.c_ddate < 0) {
(void)fprintf(stderr, "bad time \"%s\"\n",
optarg);
exit(X_STARTUP);
}
Tflag = 1;
lastlevel = '?';
break;
case 'u': /* update /etc/dumpdates */
uflag = 1;
break;
case 'W': /* what to do */
case 'w':
lastdump(ch);
exit(X_FINOK); /* do nothing else */
default:
usage();
}
argc -= optind;
argv += optind;
if (argc < 1) {
(void)fprintf(stderr, "Must specify disk or file system\n");
exit(X_STARTUP);
}
disk = *argv++;
argc--;
if (argc >= 1) {
(void)fprintf(stderr, "Unknown arguments to dump:");
while (argc--)
(void)fprintf(stderr, " %s", *argv++);
(void)fprintf(stderr, "\n");
exit(X_STARTUP);
}
if (Tflag && uflag) {
(void)fprintf(stderr,
"You cannot use the T and u flags together.\n");
exit(X_STARTUP);
}
if (strcmp(tape, "-") == 0) {
pipeout++;
tape = "standard output";
}
if (blocksperfile)
blocksperfile = blocksperfile / ntrec * ntrec; /* round down */
else if (!unlimited) {
/*
* Determine how to default tape size and density
*
* density tape size
* 9-track 1600 bpi (160 bytes/.1") 2300 ft.
* 9-track 6250 bpi (625 bytes/.1") 2300 ft.
* cartridge 8000 bpi (100 bytes/.1") 1700 ft.
* (450*4 - slop)
* hilit19 hits again: "
*/
if (density == 0)
density = cartridge ? 100 : 160;
if (tsize == 0)
tsize = cartridge ? 1700L*120L : 2300L*120L;
}
if (strchr(tape, ':')) {
host = tape;
tape = strchr(host, ':');
*tape++ = '\0';
#ifdef RDUMP
if (index(tape, '\n')) {
(void)fprintf(stderr, "invalid characters in tape\n");
exit(X_STARTUP);
}
if (rmthost(host) == 0)
exit(X_STARTUP);
#else
(void)fprintf(stderr, "remote dump not enabled\n");
exit(X_STARTUP);
#endif
}
(void)setuid(getuid()); /* rmthost() is the only reason to be setuid */
if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
signal(SIGHUP, sig);
if (signal(SIGTRAP, SIG_IGN) != SIG_IGN)
signal(SIGTRAP, sig);
if (signal(SIGFPE, SIG_IGN) != SIG_IGN)
signal(SIGFPE, sig);
if (signal(SIGBUS, SIG_IGN) != SIG_IGN)
signal(SIGBUS, sig);
if (signal(SIGSEGV, SIG_IGN) != SIG_IGN)
signal(SIGSEGV, sig);
if (signal(SIGTERM, SIG_IGN) != SIG_IGN)
signal(SIGTERM, sig);
if (signal(SIGINT, interrupt) == SIG_IGN)
signal(SIGINT, SIG_IGN);
dump_getfstab(); /* /etc/fstab snarfed */
/*
* disk can be either the full special file name,
* the suffix of the special file name,
* the special name missing the leading '/',
* the file system name with or without the leading '/'.
*/
dt = fstabsearch(disk);
if (dt != NULL) {
disk = rawname(dt->fs_spec);
(void)strncpy(spcl.c_dev, dt->fs_spec, NAMELEN);
(void)strncpy(spcl.c_filesys, dt->fs_file, NAMELEN);
} else {
(void)strncpy(spcl.c_dev, disk, NAMELEN);
(void)strncpy(spcl.c_filesys, "an unlisted file system",
NAMELEN);
}
spcl.c_dev[NAMELEN-1]='\0';
spcl.c_filesys[NAMELEN-1]='\0';
if ((mntpt = getmntpt(disk, &mntflags)) != 0) {
if (snapdump == 0) {
msg("WARNING: %s\n",
"should use -L when dumping live filesystems!");
} else {
char snapname[BUFSIZ], snapcmd[BUFSIZ];
snprintf(snapname, sizeof snapname,
"%s/.snap/dump_snapshot", mntpt);
snprintf(snapcmd, sizeof snapcmd,
"mksnap_ffs %s %s", mntpt, snapname);
unlink(snapname);
if (system(snapcmd) != 0)
errx(X_STARTUP, "Cannot create %s: %s\n",
snapname, strerror(errno));
if ((diskfd = open(snapname, O_RDONLY)) < 0) {
unlink(snapname);
errx(X_STARTUP, "Cannot open %s: %s\n",
snapname, strerror(errno));
}
unlink(snapname);
if (fstat(diskfd, &sb) != 0)
err(X_STARTUP, "%s: stat", snapname);
spcl.c_date = _time_to_time64(sb.st_mtime);
}
} else if (snapdump != 0) {
msg("WARNING: Cannot use -L on an unmounted filesystem.\n");
snapdump = 0;
}
if (snapdump == 0) {
if ((diskfd = open(disk, O_RDONLY)) < 0)
err(X_STARTUP, "Cannot open %s", disk);
if (fstat(diskfd, &sb) != 0)
err(X_STARTUP, "%s: stat", disk);
if (S_ISDIR(sb.st_mode))
errx(X_STARTUP, "%s: unknown file system", disk);
}
(void)strcpy(spcl.c_label, "none");
(void)gethostname(spcl.c_host, NAMELEN);
spcl.c_level = level - '0';
spcl.c_type = TS_TAPE;
if (spcl.c_date == 0) {
tmsg = "the epoch\n";
} else {
time_t t = _time64_to_time(spcl.c_date);
tmsg = ctime(&t);
}
msg("Date of this level %c dump: %s", level, tmsg);
if (!Tflag)
getdumptime(); /* /etc/dumpdates snarfed */
if (spcl.c_ddate == 0) {
tmsg = "the epoch\n";
} else {
time_t t = _time64_to_time(spcl.c_ddate);
tmsg = ctime(&t);
}
msg("Date of last level %c dump: %s", lastlevel, tmsg);
msg("Dumping %s%s ", snapdump ? "snapshot of ": "", disk);
if (dt != NULL)
msgtail("(%s) ", dt->fs_file);
if (host)
msgtail("to %s on host %s\n", tape, host);
else
msgtail("to %s\n", tape);
sync();
sblock = (struct fs *)sblock_buf;
for (i = 0; sblock_try[i] != -1; i++) {
sblock->fs_fsize = SBLOCKSIZE; /* needed in bread */
bread(sblock_try[i] >> dev_bshift, (char *) sblock, SBLOCKSIZE);
if ((sblock->fs_magic == FS_UFS1_MAGIC ||
(sblock->fs_magic == FS_UFS2_MAGIC &&
sblock->fs_sblockloc == sblock_try[i])) &&
sblock->fs_bsize <= MAXBSIZE &&
sblock->fs_bsize >= sizeof(struct fs))
break;
}
if (sblock_try[i] == -1)
quit("Cannot find file system superblock\n");
dev_bsize = sblock->fs_fsize / fsbtodb(sblock, 1);
dev_bshift = ffs(dev_bsize) - 1;
if (dev_bsize != (1 << dev_bshift))
quit("dev_bsize (%ld) is not a power of 2", dev_bsize);
tp_bshift = ffs(TP_BSIZE) - 1;
if (TP_BSIZE != (1 << tp_bshift))
quit("TP_BSIZE (%d) is not a power of 2", TP_BSIZE);
maxino = sblock->fs_ipg * sblock->fs_ncg;
mapsize = roundup(howmany(maxino, CHAR_BIT), TP_BSIZE);
usedinomap = (char *)calloc((unsigned) mapsize, sizeof(char));
dumpdirmap = (char *)calloc((unsigned) mapsize, sizeof(char));
dumpinomap = (char *)calloc((unsigned) mapsize, sizeof(char));
tapesize = 3 * (howmany(mapsize * sizeof(char), TP_BSIZE) + 1);
nonodump = spcl.c_level < honorlevel;
passno = 1;
setproctitle("%s: pass 1: regular files", disk);
msg("mapping (Pass I) [regular files]\n");
anydirskipped = mapfiles(maxino, &tapesize);
passno = 2;
setproctitle("%s: pass 2: directories", disk);
msg("mapping (Pass II) [directories]\n");
while (anydirskipped) {
anydirskipped = mapdirs(maxino, &tapesize);
}
if (pipeout || unlimited) {
tapesize += 10; /* 10 trailer blocks */
msg("estimated %ld tape blocks.\n", tapesize);
} else {
double fetapes;
if (blocksperfile)
fetapes = (double) tapesize / blocksperfile;
else if (cartridge) {
/* Estimate number of tapes, assuming streaming stops at
the end of each block written, and not in mid-block.
Assume no erroneous blocks; this can be compensated
for with an artificially low tape size. */
fetapes =
( (double) tapesize /* blocks */
* TP_BSIZE /* bytes/block */
* (1.0/density) /* 0.1" / byte " */
+
(double) tapesize /* blocks */
* (1.0/ntrec) /* streaming-stops per block */
* 15.48 /* 0.1" / streaming-stop " */
) * (1.0 / tsize ); /* tape / 0.1" " */
} else {
/* Estimate number of tapes, for old fashioned 9-track
tape */
int tenthsperirg = (density == 625) ? 3 : 7;
fetapes =
( (double) tapesize /* blocks */
* TP_BSIZE /* bytes / block */
* (1.0/density) /* 0.1" / byte " */
+
(double) tapesize /* blocks */
* (1.0/ntrec) /* IRG's / block */
* tenthsperirg /* 0.1" / IRG " */
) * (1.0 / tsize ); /* tape / 0.1" " */
}
etapes = fetapes; /* truncating assignment */
etapes++;
/* count the dumped inodes map on each additional tape */
tapesize += (etapes - 1) *
(howmany(mapsize * sizeof(char), TP_BSIZE) + 1);
tapesize += etapes + 10; /* headers + 10 trailer blks */
msg("estimated %ld tape blocks on %3.2f tape(s).\n",
tapesize, fetapes);
}
/*
* If the user only wants an estimate of the number of
* tapes, exit now.
*/
if (just_estimate)
exit(0);
/*
* Allocate tape buffer.
*/
if (!alloctape())
quit(
"can't allocate tape buffers - try a smaller blocking factor.\n");
startnewtape(1);
(void)time((time_t *)&(tstart_writing));
dumpmap(usedinomap, TS_CLRI, maxino - 1);
passno = 3;
setproctitle("%s: pass 3: directories", disk);
msg("dumping (Pass III) [directories]\n");
dirty = 0; /* XXX just to get gcc to shut up */
for (map = dumpdirmap, ino = 1; ino < maxino; ino++) {
if (((ino - 1) % CHAR_BIT) == 0) /* map is offset by 1 */
dirty = *map++;
else
dirty >>= 1;
if ((dirty & 1) == 0)
continue;
/*
* Skip directory inodes deleted and maybe reallocated
*/
dp = getino(ino, &mode);
if (mode != IFDIR)
continue;
(void)dumpino(dp, ino);
}
passno = 4;
setproctitle("%s: pass 4: regular files", disk);
msg("dumping (Pass IV) [regular files]\n");
for (map = dumpinomap, ino = 1; ino < maxino; ino++) {
if (((ino - 1) % CHAR_BIT) == 0) /* map is offset by 1 */
dirty = *map++;
else
dirty >>= 1;
if ((dirty & 1) == 0)
continue;
/*
* Skip inodes deleted and reallocated as directories.
*/
dp = getino(ino, &mode);
if (mode == IFDIR)
continue;
(void)dumpino(dp, ino);
}
(void)time((time_t *)&(tend_writing));
spcl.c_type = TS_END;
for (i = 0; i < ntrec; i++)
writeheader(maxino - 1);
if (pipeout)
msg("DUMP: %jd tape blocks\n", (intmax_t)spcl.c_tapea);
else
msg("DUMP: %jd tape blocks on %d volume%s\n",
(intmax_t)spcl.c_tapea, spcl.c_volume,
(spcl.c_volume == 1) ? "" : "s");
/* report dump performance, avoid division through zero */
if (tend_writing - tstart_writing == 0)
msg("finished in less than a second\n");
else
msg("finished in %jd seconds, throughput %jd KBytes/sec\n",
(intmax_t)tend_writing - tstart_writing,
(intmax_t)(spcl.c_tapea /
(tend_writing - tstart_writing)));
putdumptime();
trewind();
broadcast("DUMP IS DONE!\a\a\n");
msg("DUMP IS DONE\n");
Exit(X_FINOK);
/* NOTREACHED */
}
static void
usage(void)
{
fprintf(stderr,
"usage: dump [-0123456789acLnSu] [-B records] [-b blocksize] [-C cachesize]\n"
" [-D dumpdates] [-d density] [-f file] [-h level] [-s feet]\n"
" [-T date] filesystem\n"
" dump -W | -w\n");
exit(X_STARTUP);
}
/*
* Check to see if a disk is currently mounted.
*/
static char *
getmntpt(char *name, int *mntflagsp)
{
long mntsize, i;
struct statfs *mntbuf;
mntsize = getmntinfo(&mntbuf, MNT_NOWAIT);
for (i = 0; i < mntsize; i++) {
if (!strcmp(mntbuf[i].f_mntfromname, name)) {
*mntflagsp = mntbuf[i].f_flags;
return (mntbuf[i].f_mntonname);
}
}
return (0);
}
/*
* Pick up a numeric argument. It must be nonnegative and in the given
* range (except that a vmax of 0 means unlimited).
*/
static long
numarg(const char *meaning, long vmin, long vmax)
{
char *p;
long val;
val = strtol(optarg, &p, 10);
if (*p)
errx(1, "illegal %s -- %s", meaning, optarg);
if (val < vmin || (vmax && val > vmax))
errx(1, "%s must be between %ld and %ld", meaning, vmin, vmax);
return (val);
}
void
sig(int signo)
{
switch(signo) {
case SIGALRM:
case SIGBUS:
case SIGFPE:
case SIGHUP:
case SIGTERM:
case SIGTRAP:
if (pipeout)
quit("Signal on pipe: cannot recover\n");
msg("Rewriting attempted as response to unknown signal.\n");
(void)fflush(stderr);
(void)fflush(stdout);
close_rewind();
exit(X_REWRITE);
/* NOTREACHED */
case SIGSEGV:
msg("SIGSEGV: ABORTING!\n");
(void)signal(SIGSEGV, SIG_DFL);
(void)kill(0, SIGSEGV);
/* NOTREACHED */
}
}
char *
rawname(char *cp)
{
static char rawbuf[MAXPATHLEN];
char *dp;
struct stat sb;
if (stat(cp, &sb) == 0) {
/*
* If the name already refers to a raw device, return
* it immediately without tampering.
*/
if ((sb.st_mode & S_IFMT) == S_IFCHR)
return (cp);
}
dp = strrchr(cp, '/');
if (dp == NULL)
return (NULL);
*dp = '\0';
(void)strlcpy(rawbuf, cp, MAXPATHLEN - 1);
*dp = '/';
(void)strlcat(rawbuf, "/r", MAXPATHLEN - 1 - strlen(rawbuf));
(void)strlcat(rawbuf, dp + 1, MAXPATHLEN - 1 - strlen(rawbuf));
return (rawbuf);
}
/*
* obsolete --
* Change set of key letters and ordered arguments into something
* getopt(3) will like.
*/
static void
obsolete(int *argcp, char **argvp[])
{
int argc, flags;
char *ap, **argv, *flagsp, **nargv, *p;
/* Setup. */
argv = *argvp;
argc = *argcp;
/* Return if no arguments or first argument has leading dash. */
ap = argv[1];
if (argc == 1 || *ap == '-')
return;
/* Allocate space for new arguments. */
if ((*argvp = nargv = malloc((argc + 1) * sizeof(char *))) == NULL ||
(p = flagsp = malloc(strlen(ap) + 2)) == NULL)
err(1, NULL);
*nargv++ = *argv;
argv += 2;
for (flags = 0; *ap; ++ap) {
switch (*ap) {
case 'B':
case 'b':
case 'd':
case 'f':
case 'D':
case 'C':
case 'h':
case 's':
case 'T':
if (*argv == NULL) {
warnx("option requires an argument -- %c", *ap);
usage();
}
if ((nargv[0] = malloc(strlen(*argv) + 2 + 1)) == NULL)
err(1, NULL);
nargv[0][0] = '-';
nargv[0][1] = *ap;
(void)strcpy(&nargv[0][2], *argv);
++argv;
++nargv;
break;
default:
if (!flags) {
*p++ = '-';
flags = 1;
}
*p++ = *ap;
break;
}
}
/* Terminate flags. */
if (flags) {
*p = '\0';
*nargv++ = flagsp;
}
/* Copy remaining arguments. */
while ((*nargv++ = *argv++));
/* Update argument count. */
*argcp = nargv - *argvp - 1;
}