diff options
author | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
---|---|---|
committer | stevel@tonic-gate <none@none> | 2005-06-14 00:00:00 -0700 |
commit | 7c478bd95313f5f23a4c958a745db2134aa03244 (patch) | |
tree | c871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/backup/dump/dumptape.c | |
download | illumos-gate-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz |
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/backup/dump/dumptape.c')
-rw-r--r-- | usr/src/cmd/backup/dump/dumptape.c | 2511 |
1 files changed, 2511 insertions, 0 deletions
diff --git a/usr/src/cmd/backup/dump/dumptape.c b/usr/src/cmd/backup/dump/dumptape.c new file mode 100644 index 0000000000..0d3020fb50 --- /dev/null +++ b/usr/src/cmd/backup/dump/dumptape.c @@ -0,0 +1,2511 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License, Version 1.0 only + * (the "License"). You may not use this file except in compliance + * with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + +/* + * Portions of this source code were derived from Berkeley 4.3 BSD + * under license from the Regents of the University of California. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include "dump.h" +#include <rmt.h> +#include <setjmp.h> +#include <sys/fdio.h> +#include <sys/mkdev.h> +#include <assert.h> +#include <limits.h> + +#define SLEEPMS 50 + +static uint_t writesize; /* size of malloc()ed buffer for tape */ +static ino_t inos[TP_NINOS]; /* starting inodes on each tape */ + +/* + * The req structure is used to pass commands from the parent + * process through the pipes to the slave processes. It comes + * in two flavors, depending on which mode dump is operating under: + * an inode request (on-line mode) and a disk block request ("old" mode). + */ +/* + * The inode request structure is used during on-line mode. + * The master passes inode numbers and starting offsets to + * the slaves. The tape writer passes out the current inode, + * offset, and number of tape records written after completing a volume. + */ +struct ireq { + ino_t inumber; /* inode number to open/dump */ + long igen; /* inode generation number */ + off_t offset; /* starting offset in inode */ + int count; /* count for 1st spclrec */ +}; +/* + * The block request structure is used in off-line mode to pass + * commands to dump disk blocks from the parent process through + * the pipes to the slave processes. + */ +struct breq { + diskaddr_t dblk; /* disk address to read */ + size_t size; /* number of bytes to read from disk */ + ulong_t spclrec[1]; /* actually longer */ +}; + +struct req { + short aflag; /* write data to archive process as well */ + short tflag; /* begin new tape */ + union reqdata { + struct ireq ino; /* used for on-line mode */ + struct breq blks; /* used for off-line mode */ + } data; +}; + +#define ir_inumber data.ino.inumber +#define ir_igen data.ino.igen +#define ir_offset data.ino.offset +#define ir_count data.ino.count + +#define br_dblk data.blks.dblk +#define br_size data.blks.size +#define br_spcl data.blks.spclrec + +static int reqsiz = 0; /* alloctape will initialize */ + +#define SLAVES 3 +struct slaves { + int sl_slavefd; /* pipe from master to slave */ + pid_t sl_slavepid; /* slave pid; used by killall() */ + ino_t sl_inos; /* inos, if this record starts tape */ + int sl_offset; /* logical blocks written for object */ + int sl_count; /* logical blocks left in spclrec */ + int sl_tapea; /* header number, if starting tape */ + int sl_firstrec; /* number of first block on tape */ + int sl_state; /* dump output state */ + struct req *sl_req; /* instruction packet to slave */ +}; +static struct slaves slaves[SLAVES]; /* one per slave */ +static struct slaves *slp; /* pointer to current slave */ +static struct slaves chkpt; /* checkpointed data */ + +struct bdesc { + char *b_data; /* pointer to buffer data */ + int b_flags; /* flags (see below) */ +}; + +/* + * The following variables are in shared memory, and must be + * explicitly checkpointed and/or reset. + */ +static caddr_t shared; /* pointer to block of shared memory */ +static struct bdesc *bufp; /* buffer descriptors */ +static struct bdesc **current; /* output buffer to fill */ +static int *tapea; /* logical record count */ + +#ifdef INSTRUMENT +static int *readmissp; /* number of times writer was idle */ +static int *idle; /* number of times slaves were idle */ +#endif /* INSTRUMENT */ + +/* + * Buffer flags + */ +#define BUF_EMPTY 0x0 /* nothing in buffer */ +#define BUF_FULL 0x1 /* data in buffer */ +#define BUF_SPCLREC 0x2 /* contains special record */ +#define BUF_ARCHIVE 0x4 /* dump to archive */ + +static int recsout; /* number of req's sent to slaves */ +static int totalrecsout; /* total number of req's sent to slaves */ +static int rotor; /* next slave to be instructed */ +static pid_t master; /* pid of master, for sending error signals */ +static int writer = -1; /* fd of tape writer */ +static pid_t writepid; /* pid of tape writer */ +static int arch; /* fd of output archiver */ +static pid_t archivepid; /* pid of output archiver */ +static int archivefd; /* fd of archive file (proper) */ +static offset_t lf_archoffset; /* checkpointed offset into archive file */ + +int caught; /* caught signal -- imported by mapfile() */ + +#ifdef DEBUG +extern int xflag; +#endif + +#ifdef __STDC__ +static void cmdwrterr(void); +static void cmdrderr(void); +static void freetape(void); +static void bufclear(void); +static pid_t setuparchive(void); +static pid_t setupwriter(void); +static void nextslave(void); +static void tperror(int); +static void rollforward(int); +static void nap(int); +static void alrm(int); +static void just_rewind(void); +static void killall(void); +static void proceed(int); +static void die(int); +static void enslave(void); +static void wait_our_turn(void); +static void dumpoffline(int, pid_t, int); +static void onxfsz(int); +static void dowrite(int); +static void checkpoint(struct bdesc *, int); +static ssize_t atomic(int (*)(), int, char *, int); +#else +static void cmdwrterr(); +static void cmdrderr(); +static void freetape(); +static void bufclear(); +static pid_t setuparchive(); +static pid_t setupwriter(); +static void nextslave(); +static void tperror(); +static void rollforward(); +static void nap(); +static void alrm(); +static void just_rewind(); +static void killall(); +static void proceed(); +static void die(); +static void enslave(); +static void wait_our_turn(); +static void dumpoffline(); +static void onxfsz(); +static void dowrite(); +static void checkpoint(); +static ssize_t atomic(); +#endif + +static size_t tapesize; + +/* + * Allocate buffers and shared memory variables. Tape buffers are + * allocated on page boundaries for tape write() efficiency. + */ +void +#ifdef __STDC__ +#else +#endif +alloctape(void) +{ + struct slaves *slavep; + ulong_t pgoff = (unsigned)(getpagesize() - 1); /* 2**n - 1 */ + int mapfd; + char *obuf; + int saverr; + int i, j; + + writesize = ntrec * tp_bsize; + if (!printsize) + msg(gettext("Writing %d Kilobyte records\n"), + writesize / TP_BSIZE_MIN); + + /* + * set up shared memory seg for here and child + */ + mapfd = open("/dev/zero", O_RDWR); + if (mapfd == -1) { + saverr = errno; + msg(gettext("Cannot open `%s': %s\n"), + "/dev/zero", strerror(saverr)); + dumpabort(); + /*NOTREACHED*/ + } + /* + * Allocate space such that buffers are page-aligned and + * pointers are aligned on 4-byte boundaries (for SPARC). + * This code assumes that (NBUF * writesize) is a multiple + * of the page size and that pages are aligned on 4-byte + * boundaries. Space is allocated as follows: + * + * (NBUF * writesize) for the actual buffers + * (pagesize - 1) for padding so the buffers are page-aligned + * (NBUF * ntrec * sizeof (struct bdesc)) for each buffer + * (n * sizeof (int)) for [n] debugging variables/pointers + * (n * sizeof (int)) for [n] miscellaneous variables/pointers + */ + tapesize = + (NBUF * writesize) /* output buffers */ + /* LINTED: pgoff fits into a size_t */ + + (size_t)pgoff /* page alignment */ + /* buffer descriptors */ + + (((size_t)sizeof (struct bdesc)) * NBUF * ntrec) +#ifdef INSTRUMENT + + (2 * (size_t)sizeof (int *)) /* instrumentation */ +#endif + /* shared variables */ + + (size_t)sizeof (struct bdesc **) + + (size_t)sizeof (int *) + + (3 * (size_t)sizeof (time_t)); + + shared = mmap((char *)0, tapesize, PROT_READ|PROT_WRITE, + MAP_SHARED, mapfd, (off_t)0); + if (shared == (caddr_t)-1) { + saverr = errno; + msg(gettext("Cannot memory map output buffers: %s\n"), + strerror(saverr)); + dumpabort(); + /*NOTREACHED*/ + } + (void) close(mapfd); + + /* + * Buffers and buffer headers + */ + obuf = (char *)(((ulong_t)shared + pgoff) & ~pgoff); + /* LINTED obuf and writesize are aligned */ + bufp = (struct bdesc *)(obuf + NBUF*writesize); + /* + * Shared memory variables + */ + current = (struct bdesc **)&bufp[NBUF*ntrec]; + tapea = (int *)(current + 1); + /* LINTED pointer alignment ok */ + telapsed = (time_t *)(tapea + 1); + tstart_writing = telapsed + 1; + tschedule = tstart_writing + 1; +#ifdef INSTRUMENT + /* + * Debugging and instrumentation variables + */ + readmissp = (int *)(tschedule + 1); + idle = readmissp + 1; +#endif + for (i = 0, j = 0; i < NBUF * ntrec; i++, j += tp_bsize) { + bufp[i].b_data = &obuf[j]; + } + + reqsiz = sizeof (struct req) + tp_bsize - sizeof (long); + for (slavep = slaves; slavep < &slaves[SLAVES]; slavep++) + slavep->sl_req = (struct req *)xmalloc(reqsiz); + + chkpt.sl_offset = 0; /* start at offset 0 */ + chkpt.sl_count = 0; + chkpt.sl_inos = UFSROOTINO; /* in root inode */ + chkpt.sl_firstrec = 1; + chkpt.sl_tapea = 0; +} + +static void +#ifdef __STDC__ +freetape(void) +#else +freetape() +#endif +{ + if (shared == NULL) + return; + (void) timeclock((time_t)0); + (void) munmap(shared, tapesize); + shared = NULL; +} + +/* + * Reset tape state variables -- called + * before a pass to dump active files. + */ +void +#ifdef __STDC__ +reset(void) +#else +reset() +#endif +{ + bufclear(); + +#ifdef INSTRUMENT + (*readmissp) = 0; + (*idle) = 0; +#endif + + spcl.c_flags = 0; + spcl.c_volume = 0; + tapeno = 0; + + chkpt.sl_offset = 0; /* start at offset 0 */ + chkpt.sl_count = 0; + chkpt.sl_inos = UFSROOTINO; /* in root inode */ + chkpt.sl_firstrec = 1; + chkpt.sl_tapea = 0; +} + +static void +#ifdef __STDC__ +bufclear(void) +#else +bufclear() +#endif +{ + struct bdesc *bp; + int i; + + for (i = 0, bp = bufp; i < NBUF * ntrec; i++, bp++) + bp->b_flags = BUF_EMPTY; + if ((caddr_t)current < shared || + (caddr_t)current > (shared + tapesize)) { + msg(gettext( + "bufclear: current pointer out of range of shared memory\n")); + dumpabort(); + /*NOTREACHED*/ + } + if ((*current != NULL) && + (*current < &bufp[0] || *current > &bufp[NBUF*ntrec])) { + /* ANSI string catenation, to shut cstyle up */ + msg(gettext("bufclear: current buffer pointer (0x%x) " + "out of range of buffer\naddresses (0x%x - 0x%x)\n"), + *current, &bufp[0], &bufp[NBUF*ntrec]); + dumpabort(); + /*NOTREACHED*/ + } + *current = bufp; +} + +/* + * Start a process to collect information describing the dump. + * This data takes two forms: + * the bitmap and directory information being written to + * the front of the tape (the "archive" file) + * information describing each directory and inode (to + * be included in the database tmp file) + * Write the data to the files as it is received so huge file + * systems don't cause dump to consume large amounts of memory. + */ +static pid_t +#ifdef __STDC__ +setuparchive(void) +#else +setuparchive() +#endif +{ + struct slaves *slavep; + int cmd[2]; + pid_t pid; + ssize_t size; + char *data; + char *errmsg; + int flags, saverr; + int punt = 0; + + /* + * Both the archive and database tmp files are + * checkpointed by taking their current offsets + * (sizes) after completing each volume. Restoring + * from a checkpoint involves truncating to the + * checkpointed size. + */ + if (archive && !doingactive) { + /* It's allowed/expected to exist, so can't use O_EXCL */ + archivefd = safe_file_open(archivefile, O_WRONLY, 0600); + if (archivefd < 0) { + saverr = errno; + msg(gettext("Cannot open archive file `%s': %s\n"), + archivefile, strerror(saverr)); + dumpabort(); + /*NOTREACHED*/ + } + + if (lseek64(archivefd, lf_archoffset, 0) < 0) { + saverr = errno; + msg(gettext( + "Cannot position archive file `%s' : %s\n"), + archivefile, strerror(saverr)); + dumpabort(); + /*NOTREACHED*/ + } + if (ftruncate64(archivefd, lf_archoffset) < 0) { + saverr = errno; + msg(gettext( + "Cannot truncate archive file `%s' : %s\n"), + archivefile, strerror(saverr)); + dumpabort(); + /*NOTREACHED*/ + } + } + + if (pipe(cmd) < 0) { + saverr = errno; + msg(gettext("%s: %s error: %s\n"), + "setuparchive", "pipe", strerror(saverr)); + return (0); + } + sighold(SIGINT); + if ((pid = fork()) < 0) { + saverr = errno; + msg(gettext("%s: %s error: %s\n"), + "setuparchive", "fork", strerror(saverr)); + return (0); + } + if (pid > 0) { + sigrelse(SIGINT); + /* parent process */ + (void) close(cmd[0]); + arch = cmd[1]; + return (pid); + } + /* + * child process + */ + (void) signal(SIGINT, SIG_IGN); /* master handles this */ +#ifdef TDEBUG + (void) sleep(4); /* allow time for parent's message to get out */ + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext("Archiver has pid = %ld\n"), (long)getpid()); +#endif + freeino(); /* release unneeded resources */ + freetape(); + for (slavep = &slaves[0]; slavep < &slaves[SLAVES]; slavep++) { + if (slavep->sl_slavefd != -1) { + (void) close(slavep->sl_slavefd); + slavep->sl_slavefd = -1; + } + } + (void) close(to); + (void) close(fi); + to = fi = -1; + (void) close(cmd[1]); + data = xmalloc(tp_bsize); + for (;;) { + size = atomic((int(*)())read, cmd[0], (char *)&flags, + sizeof (flags)); + if ((unsigned)size != sizeof (flags)) + break; + size = atomic((int(*)())read, cmd[0], data, tp_bsize); + if (size == tp_bsize) { + if (archive && flags & BUF_ARCHIVE && !punt && + (size = write(archivefd, data, tp_bsize)) + != tp_bsize) { + struct stat64 stats; + + if (size != -1) { + errmsg = strdup(gettext( + "Output truncated")); + if (errmsg == NULL) + errmsg = ""; + } else { + errmsg = strerror(errno); + } + + if (fstat64(archivefd, &stats) < 0) + stats.st_size = -1; + + /* cast to keep lint&printf happy */ + msg(gettext( + "Cannot write archive file `%s' at offset %lld: %s\n"), + archivefile, (longlong_t)stats.st_size, + errmsg); + msg(gettext( + "Archive file will be deleted, dump will continue\n")); + punt++; + if ((size != -1) && (*errmsg != '\0')) { + free(errmsg); + } + } + } else { + break; + } + } + (void) close(cmd[0]); + if (archive) { + (void) close(archivefd); + archivefd = -1; + } + if (punt) { + (void) unlink(archivefile); + Exit(X_ABORT); + } + Exit(X_FINOK); + /* NOTREACHED */ +} + +/* + * Start a process to read the output buffers and write the data + * to the output device. + */ +static pid_t +#ifdef __STDC__ +setupwriter(void) +#else +setupwriter() +#endif +{ + struct slaves *slavep; + int cmd[2]; + pid_t pid; + int saverr; + + caught = 0; + if (pipe(cmd) < 0) { + saverr = errno; + msg(gettext("%s: %s error: %s\n"), + "setupwriter", "pipe", strerror(saverr)); + return (0); + } + sighold(SIGINT); + if ((pid = fork()) < 0) { + saverr = errno; + msg(gettext("%s: %s error: %s\n"), + "setupwriter", "fork", strerror(saverr)); + return (0); + } + if (pid > 0) { + /* + * Parent process + */ + sigrelse(SIGINT); + (void) close(cmd[0]); + writer = cmd[1]; + return (pid); + } + /* + * Child (writer) process + */ + (void) signal(SIGINT, SIG_IGN); /* master handles this */ +#ifdef TDEBUG + (void) sleep(4); /* allow time for parent's message to get out */ + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext("Writer has pid = %ld\n"), (long)getpid()); +#endif + child_chdir(); + freeino(); /* release unneeded resources */ + for (slavep = &slaves[0]; slavep < &slaves[SLAVES]; slavep++) { + if (slavep->sl_slavefd != -1) { + (void) close(slavep->sl_slavefd); + slavep->sl_slavefd = -1; + } + } + (void) close(fi); + fi = -1; + (void) close(cmd[1]); + dowrite(cmd[0]); + if (arch >= 0) { + (void) close(arch); + arch = -1; + } + (void) close(cmd[0]); + Exit(X_FINOK); + /* NOTREACHED */ +} + +void +#ifdef __STDC__ +spclrec(void) +#else +spclrec() +#endif +{ + int s, i; + int32_t *ip; + int flags = BUF_SPCLREC; + + if ((BIT(ino, shamap)) && (spcl.c_type == TS_INODE)) { + spcl.c_type = TS_ADDR; + /* LINTED: result fits in a short */ + spcl.c_dinode.di_mode &= ~S_IFMT; + /* LINTED: result fits in a short */ + spcl.c_dinode.di_mode |= IFSHAD; + } + + /* + * Only TS_INODEs should have short metadata, if this + * isn't such a spclrec, clear the metadata flag and + * the c_shadow contents. + */ + if (!(spcl.c_type == TS_INODE && (spcl.c_flags & DR_HASMETA))) { + spcl.c_flags &= ~DR_HASMETA; + bcopy(c_shadow_save, &(spcl.c_shadow), + sizeof (spcl.c_shadow)); + } + + if (spcl.c_type == TS_END) { + spcl.c_count = 1; + spcl.c_flags |= DR_INODEINFO; + bcopy((char *)inos, (char *)spcl.c_inos, sizeof (inos)); + } else if (spcl.c_type == TS_TAPE) { + spcl.c_flags |= DR_NEWHEADER; + if (doingactive) + spcl.c_flags |= DR_REDUMP; + } else if (spcl.c_type != TS_INODE) + flags = BUF_SPCLREC; + spcl.c_tapea = *tapea; + /* LINTED for now, max inode # is 2**31 (ufs max size is 4TB) */ + spcl.c_inumber = (ino32_t)ino; + spcl.c_magic = (tp_bsize == TP_BSIZE_MIN) ? NFS_MAGIC : MTB_MAGIC; + spcl.c_checksum = 0; + ip = (int32_t *)&spcl; + s = CHECKSUM; + assert((tp_bsize % sizeof (*ip)) == 0); + i = tp_bsize / sizeof (*ip); + assert((i%8) == 0); + i /= 8; + do { + s -= *ip++; s -= *ip++; s -= *ip++; s -= *ip++; + s -= *ip++; s -= *ip++; s -= *ip++; s -= *ip++; + } while (--i > 0); + spcl.c_checksum = s; + taprec((uchar_t *)&spcl, flags, sizeof (spcl)); + if (spcl.c_type == TS_END) + spcl.c_flags &= ~DR_INODEINFO; + else if (spcl.c_type == TS_TAPE) + spcl.c_flags &= ~(DR_NEWHEADER|DR_REDUMP|DR_TRUEINC); +} + +/* + * Fill appropriate buffer + */ +void +taprec(dp, flags, size) + uchar_t *dp; + int flags; + int size; +{ + if (size > tp_bsize) { + msg(gettext( + "taprec: Unexpected buffer size, expected %d, got %d.\n"), + tp_bsize, size); + dumpabort(); + /*NOTREACHED*/ + } + + while ((*current)->b_flags & BUF_FULL) + nap(10); + + bcopy(dp, (*current)->b_data, (size_t)size); + if (size < tp_bsize) { + bzero((*current)->b_data + size, tp_bsize - size); + } + + if (dumptoarchive) + flags |= BUF_ARCHIVE; + + /* no locking as we assume only one reader and one writer active */ + (*current)->b_flags = (flags | BUF_FULL); + if (++*current >= &bufp[NBUF*ntrec]) + (*current) = &bufp[0]; + (*tapea)++; +} + +void +dmpblk(blkno, size, offset) + daddr32_t blkno; + size_t size; + off_t offset; +{ + diskaddr_t dblkno; + + assert((offset >> DEV_BSHIFT) <= INT32_MAX); + dblkno = fsbtodb(sblock, blkno) + (offset >> DEV_BSHIFT); + size = (size + DEV_BSIZE-1) & ~(DEV_BSIZE-1); + slp->sl_req->br_dblk = dblkno; + slp->sl_req->br_size = size; + if (dumptoarchive) { + /* LINTED: result fits in a short */ + slp->sl_req->aflag |= BUF_ARCHIVE; + } + toslave((void(*)())0, ino); +} + +/*ARGSUSED*/ +static void +tperror(sig) + int sig; +{ + char buf[3000]; + + if (pipeout) { + msg(gettext("Write error on %s\n"), tape); + msg(gettext("Cannot recover\n")); + dumpabort(); + /* NOTREACHED */ + } + if (!doingverify) { + broadcast(gettext("WRITE ERROR!\n")); + (void) snprintf(buf, sizeof (buf), + gettext("Do you want to restart?: (\"yes\" or \"no\") ")); + if (!query(buf)) { + dumpabort(); + /*NOTREACHED*/ + } + if (tapeout && (isrewind(to) || offline)) { + /* ANSI string catenation, to shut cstyle up */ + msg(gettext("This tape will rewind. After " + "it is rewound,\nreplace the faulty tape " + "with a new one;\nthis dump volume will " + "be rewritten.\n")); + } + } else { + broadcast(gettext("TAPE VERIFICATION ERROR!\n")); + (void) snprintf(buf, sizeof (buf), gettext( + "Do you want to rewrite?: (\"yes\" or \"no\") ")); + if (!query(buf)) { + dumpabort(); + /*NOTREACHED*/ + } + msg(gettext( + "This tape will be rewritten and then verified\n")); + } + killall(); + trewind(); + Exit(X_REWRITE); +} + +/* + * Called by master from pass() to send a request to dump files/blocks + * to one of the slaves. Slaves return whether the file was active + * when it was being dumped. The tape writer process sends checkpoint + * info when it completes a volume. + */ +void +toslave(fn, inumber) + void (*fn)(); + ino_t inumber; +{ + int wasactive; + + if (recsout >= SLAVES) { + if ((unsigned)atomic((int(*)())read, slp->sl_slavefd, + (char *)&wasactive, sizeof (wasactive)) != + sizeof (wasactive)) { + cmdrderr(); + dumpabort(); + /*NOTREACHED*/ + } + if (wasactive) { + active++; + msg(gettext( + "The file at inode `%lu' was active and will be recopied\n"), + slp->sl_req->ir_inumber); + /* LINTED: 32-bit to 8-bit assignment ok */ + BIS(slp->sl_req->ir_inumber, activemap); + } + } + slp->sl_req->aflag = 0; + if (dumptoarchive) { + /* LINTED: result fits in a short */ + slp->sl_req->aflag |= BUF_ARCHIVE; + } + if (fn) + (*fn)(inumber); + + if (atomic((int(*)())write, slp->sl_slavefd, (char *)slp->sl_req, + reqsiz) != reqsiz) { + cmdwrterr(); + dumpabort(); + /*NOTREACHED*/ + } + ++recsout; + nextslave(); +} + +void +dospcl(inumber) + ino_t inumber; +{ + /* LINTED for now, max inode # is 2**31 (ufs max size is 1TB) */ + spcl.c_inumber = (ino32_t)inumber; + slp->sl_req->br_dblk = 0; + bcopy((char *)&spcl, (char *)slp->sl_req->br_spcl, tp_bsize); +} + +static void +#ifdef __STDC__ +nextslave(void) +#else +nextslave() +#endif +{ + if (++rotor >= SLAVES) { + rotor = 0; + } + slp = &slaves[rotor]; +} + +void +#ifdef __STDC__ +flushcmds(void) +#else +flushcmds() +#endif +{ + int i; + int wasactive; + + /* + * Retrieve all slave status + */ + if (recsout < SLAVES) { + slp = slaves; + rotor = 0; + } + for (i = 0; i < (recsout < SLAVES ? recsout : SLAVES); i++) { + if ((unsigned)atomic((int(*)())read, slp->sl_slavefd, + (char *)&wasactive, sizeof (wasactive)) != + sizeof (wasactive)) { + cmdrderr(); + dumpabort(); + /*NOTREACHED*/ + } + if (wasactive) { + active++; + msg(gettext( + "inode %d was active and will be recopied\n"), + slp->sl_req->ir_inumber); + /* LINTED: 32-bit to 8-bit assignment ok */ + BIS(slp->sl_req->ir_inumber, activemap); + } + nextslave(); + } +} + +void +#ifdef __STDC__ +flusht(void) +#else +flusht() +#endif +{ + sigset_t block_set, oset; /* hold SIGUSR1 and atomically sleep */ + + (void) sigemptyset(&block_set); + (void) sigaddset(&block_set, SIGUSR1); + (void) sigprocmask(SIG_BLOCK, &block_set, &oset); + (void) kill(writepid, SIGUSR1); /* tell writer to flush */ + (void) sigpause(SIGUSR1); /* wait for SIGUSR1 from writer */ + /*NOTREACHED*/ +} + +jmp_buf checkpoint_buf; + +/* + * Roll forward to the next volume after receiving + * an EOT signal from writer. Get checkpoint data + * from writer and return if done, otherwise fork + * a new process and jump back to main state loop + * to begin the next volume. Installed as the master's + * signal handler for SIGUSR1. + */ +/*ARGSUSED*/ +static void +rollforward(sig) + int sig; +{ + int status; + (void) sighold(SIGUSR1); + + /* + * Writer sends us checkpoint information after + * each volume. A returned state of DS_DONE with no + * unwritten (left-over) records differentiates a + * clean flush from one in which EOT was encountered. + */ + if ((unsigned)atomic((int(*)())read, writer, (char *)&chkpt, + sizeof (struct slaves)) != sizeof (struct slaves)) { + cmdrderr(); + dumpabort(); + /*NOTREACHED*/ + } + if (atomic((int(*)())read, writer, (char *)&spcl, + TP_BSIZE_MIN) != TP_BSIZE_MIN) { + cmdrderr(); + dumpabort(); + /*NOTREACHED*/ + } + ino = chkpt.sl_inos - 1; + pos = chkpt.sl_offset; + leftover = chkpt.sl_count; + dumpstate = chkpt.sl_state; + blockswritten = ++chkpt.sl_tapea; + + if (dumpstate == DS_DONE) { + if (archivepid) { + /* + * If archiving (either archive or + * database), signal the archiver + * to finish up. This must happen + * before the writer exits in order + * to avoid a race. + */ + (void) kill(archivepid, SIGUSR1); + } + (void) signal(SIGUSR1, SIG_IGN); + (void) sigrelse(SIGUSR1); + (void) kill(writepid, SIGUSR1); /* tell writer to exit */ + + lf_archoffset = 0LL; + longjmp(checkpoint_buf, 1); + /*NOTREACHED*/ + } + + if (leftover) { + (void) memmove(spcl.c_addr, + &spcl.c_addr[spcl.c_count-leftover], leftover); + bzero(&spcl.c_addr[leftover], TP_NINDIR-leftover); + } + if (writepid) { + (void) kill(writepid, SIGUSR1); /* tell writer to exit */ + (void) close(writer); + writer = -1; + } + if (archivepid) { + (void) waitpid(archivepid, &status, 0); /* wait for archiver */ +#ifdef TDEBUG + + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext("Archiver %ld returns with status %d\n"), + (long)archivepid, status); +#endif + archivepid = 0; + } + /* + * Checkpoint archive file + */ + if (!doingverify && archive) { + lf_archoffset = lseek64(archivefd, (off64_t)0, 2); + if (lf_archoffset < 0) { + int saverr = errno; + msg(gettext("Cannot position archive file `%s': %s\n"), + archivefile, strerror(saverr)); + dumpabort(); + /*NOTREACHED*/ + } + (void) close(archivefd); + archivefd = -1; + } + resetino(ino); + + if (dumpstate == DS_START) { + msg(gettext( + "Tape too short: changing volumes and restarting\n")); + reset(); + } + + if (!pipeout) { + if (verify && !doingverify) + trewind(); + else { + close_rewind(); + changevol(); + } + } + + (void) sigrelse(SIGUSR1); + otape(0); + longjmp(checkpoint_buf, 1); + /*NOTREACHED*/ +} + +static void +nap(ms) + int ms; +{ + struct timeval tv; + + tv.tv_sec = ms / 1000; + tv.tv_usec = (ms - tv.tv_sec * 1000) * 1000; + (void) select(0, (fd_set *)0, (fd_set *)0, (fd_set *)0, &tv); +} + +static jmp_buf alrm_buf; + +/*ARGSUSED*/ +static void +alrm(sig) + int sig; +{ + longjmp(alrm_buf, 1); + /*NOTREACHED*/ +} + +void +#ifdef __STDC__ +nextdevice(void) +#else +nextdevice() +#endif +{ + char *cp; + + if (host != NULL) /* we set the host only once in ufsdump */ + return; + + host = NULL; + if (strchr(tape, ':')) { + if (diskette) { + msg(gettext("Cannot do remote dump to diskette\n")); + Exit(X_ABORT); + } + host = tape; + tape = strchr(host, ':'); + *tape++ = 0; + cp = strchr(host, '@'); /* user@host? */ + if (cp != (char *)0) + cp++; + else + cp = host; + } else + cp = spcl.c_host; + /* + * dumpdev is provided for use in prompts and is of + * the form: + * hostname:device + * sdumpdev is of the form: + * hostname:device + * for remote devices, and simply: + * device + * for local devices. + */ + if (dumpdev != (char *)NULL) { + /* LINTED: dumpdev is not NULL */ + free(dumpdev); + } + /*LINTED [cast to smaller integer]*/ + dumpdev = xmalloc((size_t)((sizeof (spcl.c_host) + strlen(tape) + 2))); + /* LINTED unsigned -> signed cast ok */ + (void) sprintf(dumpdev, "%.*s:%s", (int)sizeof (spcl.c_host), cp, tape); + if (cp == spcl.c_host) + sdumpdev = strchr(dumpdev, ':') + 1; + else + sdumpdev = dumpdev; +} + +/* + * Gross hack due to misfeature of mt tape driver that causes + * the device to rewind if we generate any signals. Guess + * whether tape is rewind device or not -- for local devices + * we can just look at the minor number. For rmt devices, + * make an educated guess. + */ +int +isrewind(f) + int f; /* fd, if local device */ +{ + struct stat64 sbuf; + char *c; + int unit; + int rewind; + + if (host) { + c = strrchr(tape, '/'); + if (c == NULL) + c = tape; + else + c++; + /* + * If the last component begins or ends with an 'n', it is + * assumed to be a non-rewind device. + */ + if (c[0] == 'n' || c[strlen(c)-1] == 'n') + rewind = 0; + else if ((strstr(tape, "mt") || strstr(tape, "st")) && + sscanf(tape, "%*[a-zA-Z/]%d", &unit) == 1 && + (unit & MT_NOREWIND)) + rewind = 0; + else + rewind = 1; + } else { + if (fstat64(f, &sbuf) < 0) { + msg(gettext( + "Cannot obtain status of output device `%s'\n"), + tape); + dumpabort(); + /*NOTREACHED*/ + } + rewind = minor(sbuf.st_rdev) & MT_NOREWIND ? 0 : 1; + } + return (rewind); +} + +static void +#ifdef __STDC__ +just_rewind(void) +#else +just_rewind() +#endif +{ + struct slaves *slavep; + char *rewinding = gettext("Tape rewinding\n"); + + for (slavep = &slaves[0]; slavep < &slaves[SLAVES]; slavep++) { + if (slavep->sl_slavepid > 0) /* signal normal exit */ + (void) kill(slavep->sl_slavepid, SIGTERM); + if (slavep->sl_slavefd >= 0) { + (void) close(slavep->sl_slavefd); + slavep->sl_slavefd = -1; + } + } + + /* wait for any signals from slaves */ + while (waitpid(0, (int *)0, 0) >= 0) + /*LINTED [empty body]*/ + continue; + + if (pipeout) + return; + + if (doingverify) { + /* + * Space to the end of the tape. + * Backup first in case we already read the EOF. + */ + if (host) { + (void) rmtioctl(MTBSR, 1); + if (rmtioctl(MTEOM, 1) < 0) + (void) rmtioctl(MTFSF, 1); + } else { + static struct mtop bsr = { MTBSR, 1 }; + static struct mtop eom = { MTEOM, 1 }; + static struct mtop fsf = { MTFSF, 1 }; + + (void) ioctl(to, MTIOCTOP, &bsr); + if (ioctl(to, MTIOCTOP, &eom) < 0) + (void) ioctl(to, MTIOCTOP, &fsf); + } + } + + /* + * Guess whether the tape is rewinding so we can tell + * the operator if it's going to take a long time. + */ + if (tapeout && isrewind(to)) { + /* tape is probably rewinding */ + msg(rewinding); + } +} + +void +#ifdef __STDC__ +trewind(void) +#else +trewind() +#endif +{ + (void) timeclock((time_t)0); + if (offline && (!verify || doingverify)) { + close_rewind(); + } else { + just_rewind(); + if (host) + rmtclose(); + else { + (void) close(to); + to = -1; + } + } +} + +void +#ifdef __STDC__ +close_rewind(void) +#else +close_rewind() +#endif +{ + char *rewinding = gettext("Tape rewinding\n"); + + (void) timeclock((time_t)0); + just_rewind(); + /* + * The check in just_rewind won't catch the case in + * which the current volume is being taken off-line + * and is not mounted on a no-rewind device (and is + * not the last volume, which is not taken off-line). + */ + if (tapeout && !isrewind(to) && offline) { + /* tape is probably rewinding */ + msg(rewinding); + } + if (host) { + if (offline || autoload) + (void) rmtioctl(MTOFFL, 0); + rmtclose(); + } else { + if (offline || autoload) { + static struct mtop offl = { MTOFFL, 0 }; + + (void) ioctl(to, MTIOCTOP, &offl); + if (diskette) + (void) ioctl(to, FDEJECT, 0); + } + (void) close(to); + to = -1; + } +} + +void +#ifdef __STDC__ +changevol(void) +#else +changevol() +#endif +{ + char buf1[3000], buf2[3000]; + char volname[LBLSIZE+1]; + + /*CONSTANTCONDITION*/ + assert(sizeof (spcl.c_label) < sizeof (volname)); + + filenum = 1; + nextdevice(); + (void) strcpy(spcl.c_label, tlabel); + if (host) { + char *rhost = host; + char *cp = strchr(host, '@'); + if (cp == (char *)0) + cp = host; + else + cp++; + + if (rmthost(rhost, ntrec) == 0) { + msg(gettext("Cannot connect to tape host `%s'\n"), cp); + dumpabort(); + /*NOTREACHED*/ + } + if (rhost != host) + free(rhost); + } + + /* + * Make volume switching as automatic as possible + * while avoiding overwriting volumes. We will + * switch automatically under the following condition: + * 1) The user specified autoloading from the + * command line. + * At one time, we (in the guise of hsmdump) had the + * concept of a sequence of devices to rotate through, + * but that's never been a ufsdump feature. + */ + if (autoload) { + int tries; + + /* + * Stop the clock for throughput calculations. + */ + if ((telapsed != NULL) && (tstart_writing != NULL)) { + *telapsed += time((time_t *)NULL) - *tstart_writing; + } + + (void) snprintf(volname, sizeof (volname), "#%d", tapeno+1); + (void) snprintf(buf1, sizeof (buf1), gettext( + "Mounting volume %s on %s\n"), volname, dumpdev); + msg(buf1); + broadcast(buf1); + + /* + * Wait for the tape to autoload. Note that the delay + * period doesn't take into account however long it takes + * for the open to fail (measured at 21 seconds for an + * Exabyte 8200 under 2.7 on an Ultra 2). + */ + for (tries = 0; tries < autoload_tries; tries++) { + if (host) { + if (rmtopen(tape, O_RDONLY) >= 0) { + rmtclose(); + return; + } + } else { + int f, m; + + m = (access(tape, F_OK) == 0) ? 0 : O_CREAT; + if ((f = doingverify ? + safe_device_open(tape, O_RDONLY, 0600) : + safe_device_open(tape, O_RDONLY|m, 0600)) + >= 0) { + (void) close(f); + return; + } + } + (void) sleep(autoload_period); + } + /* + * Autoload timed out, ask the operator to do it. + * Note that query() will update *telapsed, and we + * shouldn't charge for the autoload time. So, since + * we updated *telapsed ourselves above, we just set + * tstart_writing to the current time, and query() + * will end up making a null-effect change. This, + * of course, assumes that our caller will be resetting + * *tstart_writing. This is currently the case. + * If tstart_writing is NULL (should never happen), + * we're ok, since time(2) will accept a NULL pointer. + */ + (void) time(tstart_writing); + } + + if (strncmp(spcl.c_label, "none", 5)) { + (void) strncpy(volname, spcl.c_label, sizeof (spcl.c_label)); + volname[sizeof (spcl.c_label)] = '\0'; + } else + (void) snprintf(volname, sizeof (volname), "#%d", tapeno+1); + + timeest(1, spcl.c_tapea); + (void) snprintf(buf1, sizeof (buf1), gettext( + "Change Volumes: Mount volume `%s' on `%s'\n"), volname, dumpdev); + msg(buf1); + broadcast(gettext("CHANGE VOLUMES!\7\7\n")); + (void) snprintf(buf1, sizeof (buf1), gettext( + "Is the new volume (%s) mounted on `%s' and ready to go?: %s"), + volname, dumpdev, gettext("(\"yes\" or \"no\") ")); + while (!query(buf1)) { + (void) snprintf(buf2, sizeof (buf2), gettext( + "Do you want to abort dump?: (\"yes\" or \"no\") ")); + if (query(buf2)) { + dumpabort(); + /*NOTREACHED*/ + } + } +} + +/* + * We implement taking and restoring checkpoints on the tape level. + * When each tape is opened, a new process is created by forking; this + * saves all of the necessary context in the parent. The child + * continues the dump; the parent waits around, saving the context. + * If the child returns X_REWRITE, then it had problems writing that tape; + * this causes the parent to fork again, duplicating the context, and + * everything continues as if nothing had happened. + */ + +void +otape(top) + int top; +{ + static struct mtget mt; + char buf[3000]; + pid_t parentpid; + pid_t childpid; + pid_t waitproc; + int status; + struct sigvec sv, osv; + + sv.sv_flags = SA_RESTART; + (void) sigemptyset(&sv.sa_mask); + sv.sv_handler = SIG_IGN; + (void) sigvec(SIGINT, &sv, (struct sigvec *)0); + + parentpid = getpid(); + + if (verify) { + if (doingverify) + doingverify = 0; + else + Exit(X_VERIFY); + } +restore_check_point: + + sv.sv_handler = interrupt; + (void) sigvec(SIGINT, &sv, (struct sigvec *)0); + (void) fflush(stderr); + /* + * All signals are inherited... + */ + sighold(SIGINT); + childpid = fork(); + if (childpid < 0) { + msg(gettext( + "Context-saving fork failed in parent %ld\n"), + (long)parentpid); + Exit(X_ABORT); + } + if (childpid != 0) { + /* + * PARENT: + * save the context by waiting + * until the child doing all of the work returns. + * let the child catch user interrupts + */ + sv.sv_handler = SIG_IGN; + (void) sigvec(SIGINT, &sv, (struct sigvec *)0); + sigrelse(SIGINT); +#ifdef TDEBUG + + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext( + "Volume: %d; parent process: %ld child process %ld\n"), + tapeno+1, (long)parentpid, (long)childpid); +#endif /* TDEBUG */ + for (;;) { + waitproc = waitpid(0, &status, 0); + if (waitproc == childpid) + break; + msg(gettext( + "Parent %ld waiting for child %ld had another child %ld return\n"), + (long)parentpid, (long)childpid, (long)waitproc); + } + if (WIFSIGNALED(status)) { + msg(gettext("Process %ld killed by signal %d: %s\n"), + (long)childpid, WTERMSIG(status), + strsignal(WTERMSIG(status))); + status = X_ABORT; + } else + status = WEXITSTATUS(status); +#ifdef TDEBUG + switch (status) { + case X_FINOK: + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext( + "Child %ld finishes X_FINOK\n"), (long)childpid); + break; + case X_ABORT: + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext( + "Child %ld finishes X_ABORT\n"), (long)childpid); + break; + case X_REWRITE: + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext( + "Child %ld finishes X_REWRITE\n"), (long)childpid); + break; + case X_RESTART: + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext( + "Child %ld finishes X_RESTART\n"), (long)childpid); + break; + case X_VERIFY: + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext( + "Child %ld finishes X_VERIFY\n"), (long)childpid); + break; + default: + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext("Child %ld finishes unknown %d\n"), + (long)childpid, status); + break; + } +#endif /* TDEBUG */ + switch (status) { + case X_FINOK: + /* wait for children */ + while (waitpid(0, (int *)0, 0) >= 0) + /*LINTED [empty body]*/ + continue; + Exit(X_FINOK); + /*NOTREACHED*/ + case X_ABORT: + Exit(X_ABORT); + /*NOTREACHED*/ + case X_VERIFY: + doingverify++; + goto restore_check_point; + /*NOTREACHED*/ + case X_REWRITE: + doingverify = 0; + changevol(); + goto restore_check_point; + /* NOTREACHED */ + case X_RESTART: + doingverify = 0; + if (!top) { + Exit(X_RESTART); + } + if (!offline) + autoload = 0; + changevol(); + sv.sv_handler = interrupt; + (void) sigvec(SIGINT, &sv, (struct sigvec *)0); + return; + /* NOTREACHED */ + default: + msg(gettext("Bad return code from dump: %d\n"), status); + Exit(X_ABORT); + /*NOTREACHED*/ + } + /*NOTREACHED*/ + } else { /* we are the child; just continue */ + child_chdir(); + sigrelse(SIGINT); +#ifdef TDEBUG + (void) sleep(4); /* time for parent's message to get out */ + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext( + "Child on Volume %d has parent %ld, my pid = %ld\n"), + tapeno+1, (long)parentpid, (long)getpid()); +#endif + (void) snprintf(buf, sizeof (buf), gettext( +"Cannot open `%s'. Do you want to retry the open?: (\"yes\" or \"no\") "), + dumpdev); + if (doingverify) { + /* 1 for stdout */ + while ((to = host ? rmtopen(tape, O_RDONLY) : + pipeout ? 1 : + safe_device_open(tape, O_RDONLY, 0600)) < 0) { + perror(tape); + if (autoload) { + if (!query_once(buf, 1)) { + dumpabort(); + /*NOTREACHED*/ + } + } else { + if (!query(buf)) { + dumpabort(); + /*NOTREACHED*/ + } + } + } + + /* + * If we're using the non-rewinding tape device, + * the tape will be left positioned after the + * EOF mark. We need to back up to the beginning + * of this tape file (cross two tape marks in the + * reverse direction and one in the forward + * direction) before the verify pass. + */ + if (host) { + if (rmtioctl(MTBSF, 2) >= 0) + (void) rmtioctl(MTFSF, 1); + else + (void) rmtioctl(MTNBSF, 1); + } else { + static struct mtop bsf = { MTBSF, 2 }; + static struct mtop fsf = { MTFSF, 1 }; + static struct mtop nbsf = { MTNBSF, 1 }; + + if (ioctl(to, MTIOCTOP, &bsf) >= 0) + (void) ioctl(to, MTIOCTOP, &fsf); + else + (void) ioctl(to, MTIOCTOP, &nbsf); + } + } else { + /* + * XXX Add logic to test for "tape" being a + * XXX device or a non-existent file. + * Current behaviour is that it must exist, + * and we over-write whatever's there. + * This can be bad if tape == "/etc/passwd". + */ + if (!pipeout && doposition && (tapeno == 0)) { + positiontape(buf); + if (setjmp(alrm_buf)) { + /* + * The tape is rewinding; + * we're screwed. + */ + msg(gettext( + "Cannot position tape using rewind device!\n")); + dumpabort(); + /*NOTREACHED*/ + } else { + sv.sv_handler = alrm; + (void) sigvec(SIGALRM, &sv, &osv); + (void) alarm(15); + } + while ((to = host ? rmtopen(tape, O_WRONLY) : + safe_device_open(tape, O_WRONLY, 0600)) < 0) + (void) sleep(10); + (void) alarm(0); + (void) sigvec(SIGALRM, &osv, + (struct sigvec *)0); + } else { + int m; + m = (access(tape, F_OK) == 0) ? 0 : O_CREAT; + /* + * Only verify the tape label if label + * verification is on and we are at BOT + */ + if (pipeout) + to = 1; + else while ((to = host ? + rmtopen(tape, O_WRONLY) : + safe_device_open(tape, O_WRONLY|m, 0600)) + < 0) + if (!query_once(buf, 1)) { + dumpabort(); + /*NOTREACHED*/ + } + } + } + if (!pipeout) { + tapeout = host ? rmtstatus(&mt) >= 0 : + ioctl(to, MTIOCGET, &mt) >= 0; /* set state */ + /* + * Make sure the tape is positioned + * where it is supposed to be + */ + if (tapeout && (tapeno > 0) && + (mt.mt_fileno != (filenum-1))) { + (void) snprintf(buf, sizeof (buf), gettext( + "Warning - tape positioning error!\n\ +\t%s current file %ld, should be %ld\n"), + tape, mt.mt_fileno+1, filenum); + msg(buf); + dumpailing(); + } + } + tapeno++; /* current tape sequence */ + if (tapeno < TP_NINOS) + inos[tapeno] = chkpt.sl_inos; + spcl.c_firstrec = chkpt.sl_firstrec; + spcl.c_tapea = (*tapea) = chkpt.sl_tapea; + spcl.c_volume++; + + enslave(); /* Share tape buffers with slaves */ + +#ifdef DEBUG + if (xflag) { + /* XGETTEXT: #ifdef DEBUG only */ + msg(gettext("Checkpoint state:\n")); + msg(" blockswritten %u\n", blockswritten); + msg(" ino %u\n", ino); + msg(" pos %u\n", pos); + msg(" left %u\n", leftover); + msg(" tapea %u\n", (*tapea)); + msg(" state %d\n", dumpstate); + } +#endif + spcl.c_type = TS_TAPE; + spcl.c_tpbsize = tp_bsize; + if (leftover == 0) { + spcl.c_count = 0; + spclrec(); + newtape = 0; + } else + newtape++; /* new volume indication */ + if (doingverify) { + msg(gettext("Starting verify pass\n")); + } else if (tapeno > 1) { + msg(gettext( + "Volume %d begins with blocks from inode %lu\n"), + tapeno, chkpt.sl_inos); + } + (void) timeclock((time_t)1); + (void) time(tstart_writing); + timeest(0, spcl.c_tapea); + } +} + +void +#ifdef __STDC__ +dumpabort(void) +#else +dumpabort() +#endif +{ + + if (master && master != getpid()) + /* + * signal master to call dumpabort + */ + (void) kill(master, SIGTERM); + else { + killall(); + + if (archivefile) + (void) unlink(archivefile); + msg(gettext("The ENTIRE dump is aborted.\n")); + } + Exit(X_ABORT); +} + +void +dumpailing(void) +{ + + broadcast(gettext("DUMP IS AILING!\n")); + if (!query(gettext( + "Do you want to attempt to continue? (\"yes\" or \"no\") "))) { + dumpabort(); + /*NOTREACHED*/ + } +} + +void +Exit(status) +{ + /* + * Clean up message system + */ +#ifdef TDEBUG + + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext("pid = %ld exits with status %d\n"), + (long)getpid(), status); +#endif /* TDEBUG */ + exit(status); +} + +static void +#ifdef __STDC__ +killall(void) +#else +killall() +#endif +{ + struct slaves *slavep; + + for (slavep = &slaves[0]; slavep < &slaves[SLAVES]; slavep++) + if (slavep->sl_slavepid > 0) { + (void) kill(slavep->sl_slavepid, SIGKILL); +#ifdef TDEBUG + + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext("Slave child %ld killed\n"), + (long)slavep->sl_slavepid); +#endif + } + if (writepid) { + (void) kill(writepid, SIGKILL); +#ifdef TDEBUG + + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext("Writer child %ld killed\n"), (long)writepid); +#endif + } + if (archivepid) { + (void) kill(archivepid, SIGKILL); +#ifdef TDEBUG + + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext("Archiver child %ld killed\n"), (long)archivepid); +#endif + } +} + +/*ARGSUSED*/ +static void +proceed(sig) + int sig; +{ + caught++; +} + +/*ARGSUSED*/ +static void +die(sig) + int sig; +{ + Exit(X_FINOK); +} + +static void +#ifdef __STDC__ +enslave(void) +#else +enslave() +#endif +{ + int cmd[2]; /* file descriptors */ + int i; + struct sigvec sv; + struct slaves *slavep; + int saverr; + + sv.sv_flags = SA_RESTART; + (void) sigemptyset(&sv.sa_mask); + master = getpid(); + /* + * slave sends SIGTERM on dumpabort + */ + sv.sv_handler = (void(*)(int))dumpabort; + (void) sigvec(SIGTERM, &sv, (struct sigvec *)0); + sv.sv_handler = tperror; + (void) sigvec(SIGUSR2, &sv, (struct sigvec *)0); + sv.sv_handler = proceed; + (void) sigvec(SIGUSR1, &sv, (struct sigvec *)0); + totalrecsout += recsout; + caught = 0; + recsout = 0; + rotor = 0; + bufclear(); + for (slavep = &slaves[0]; slavep < &slaves[SLAVES]; slavep++) + slavep->sl_slavefd = -1; + archivefd = arch = writer = -1; + for (i = 0; i < SLAVES; i++) { + if (pipe(cmd) < 0) { + saverr = errno; + msg(gettext( + "Cannot create pipe for slave process: %s\n"), + strerror(saverr)); + dumpabort(); + /*NOTREACHED*/ + } + sighold(SIGUSR2); + sighold(SIGINT); + sighold(SIGTERM); + if ((slaves[i].sl_slavepid = fork()) < 0) { + saverr = errno; + msg(gettext("Cannot create slave process: %s\n"), + strerror(saverr)); + dumpabort(); + /*NOTREACHED*/ + } + slaves[i].sl_slavefd = cmd[1]; + if (slaves[i].sl_slavepid == 0) { /* Slave starts up here */ + pid_t next; /* pid of neighbor */ + + sv.sv_handler = SIG_DFL; + (void) sigvec(SIGUSR2, &sv, (struct sigvec *)0); + sv.sv_handler = SIG_IGN; /* master handler INT */ + (void) sigvec(SIGINT, &sv, (struct sigvec *)0); + sv.sv_handler = die; /* normal slave exit */ + (void) sigvec(SIGTERM, &sv, (struct sigvec *)0); + + child_chdir(); + sigrelse(SIGUSR2); + sigrelse(SIGINT); + sigrelse(SIGTERM); + + freeino(); /* release unneeded resources */ +#ifdef TDEBUG + (void) sleep(4); /* time for parent's message to get out */ + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext("Neighbor has pid = %ld\n"), (long)getpid()); +#endif + /* Closes cmd[1] as a side-effect */ + for (slavep = &slaves[0]; + slavep < &slaves[SLAVES]; + slavep++) + if (slavep->sl_slavefd >= 0) { + (void) close(slavep->sl_slavefd); + slavep->sl_slavefd = -1; + } + (void) close(to); + (void) close(fi); /* Need our own seek ptr */ + to = -1; + + fi = open(disk, O_RDONLY); + + if (fi < 0) { + saverr = errno; + msg(gettext( + "Cannot open dump device `%s': %s\n"), + disk, strerror(saverr)); + dumpabort(); + /*NOTREACHED*/ + } + + if ((unsigned)atomic((int(*)())read, cmd[0], + (char *)&next, sizeof (next)) != sizeof (next)) { + cmdrderr(); + dumpabort(); + /*NOTREACHED*/ + } + dumpoffline(cmd[0], next, i); + Exit(X_FINOK); + } + /* Parent continues here */ + sigrelse(SIGUSR2); + sigrelse(SIGINT); + sigrelse(SIGTERM); + (void) close(cmd[0]); + } + + if (archive) { + archivepid = setuparchive(); + if (!archivepid) { + dumpabort(); + /*NOTREACHED*/ + } + } + + writepid = setupwriter(); + if (!writepid) { + dumpabort(); + /*NOTREACHED*/ + } + + if (arch >= 0) { + (void) close(arch); /* only writer has this open */ + arch = -1; + } + + /* Tell each slave who follows it */ + for (i = 0; i < SLAVES; i++) { + if ((unsigned)atomic((int(*)())write, slaves[i].sl_slavefd, + (char *)&(slaves[(i + 1) % SLAVES].sl_slavepid), + sizeof (int)) != sizeof (int)) { + cmdwrterr(); + dumpabort(); + /*NOTREACHED*/ + } + } + sv.sv_handler = rollforward; /* rcvd from writer on EOT */ + (void) sigvec(SIGUSR1, &sv, (struct sigvec *)0); + slp = slaves; + (void) kill(slp->sl_slavepid, SIGUSR1); + master = 0; +} + +static void +#ifdef __STDC__ +wait_our_turn(void) +#else +wait_our_turn() +#endif +{ + (void) sighold(SIGUSR1); + + if (!caught) { +#ifdef INSTRUMENT + (*idle)++; +#endif + (void) sigpause(SIGUSR1); + } + caught = 0; + (void) sigrelse(SIGUSR1); +} + +static void +dumpoffline(cmd, next, mynum) + int cmd; + pid_t next; + int mynum; +{ + struct req *p = slaves[mynum].sl_req; + ulong_t i; + uchar_t *cp; + uchar_t *blkbuf; + int notactive = 0; + + blkbuf = xmalloc(sblock->fs_bsize); + + /*CONSTANTCONDITION*/ + assert(sizeof (spcl) == TP_BSIZE_MIN); + + while (atomic((int(*)())read, cmd, (char *)p, reqsiz) == reqsiz) { + if (p->br_dblk) { + bread(p->br_dblk, (uchar_t *)blkbuf, p->br_size); + } else { + bcopy((char *)p->br_spcl, (char *)&spcl, + sizeof (spcl)); + ino = spcl.c_inumber; + } + dumptoarchive = p->aflag & BUF_ARCHIVE; + wait_our_turn(); + if (p->br_dblk) { + for (i = p->br_size, cp = blkbuf; + i > 0; + /* LINTED character pointers aren't signed */ + cp += i > tp_bsize ? tp_bsize : i, + i -= i > tp_bsize ? tp_bsize : i) { + /* LINTED unsigned to signed conversion ok */ + taprec(cp, 0, i > tp_bsize ? tp_bsize : (int)i); + } + } else + spclrec(); + (void) kill(next, SIGUSR1); /* Next slave's turn */ + /* + * Note that we lie about file activity since we don't + * check for it. + */ + if ((unsigned)atomic((int(*)())write, cmd, (char *)¬active, + sizeof (notactive)) != sizeof (notactive)) { + cmdwrterr(); + dumpabort(); + /*NOTREACHED*/ + } + } + + free(blkbuf); +} + +static int count; /* tape blocks written since last spclrec */ + +/*ARGSUSED*/ +static void +onxfsz(sig) + int sig; +{ + msg(gettext("File size limit exceeded writing output volume %d\n"), + tapeno); + (void) kill(master, SIGUSR2); + Exit(X_REWRITE); +} + +static long lastnonaddr; /* last DS_{INODE,CLRI,BITS} written */ +static long lastnonaddrm; /* and the mode thereof */ +/* + * dowrite -- the main body of the output writer process + */ +static void +dowrite(cmd) + int cmd; +{ + struct bdesc *last = + &bufp[(NBUF*ntrec)-1]; /* last buffer in pool */ + struct bdesc *bp = bufp; /* current buf in tape block */ + struct bdesc *begin = bufp; /* first buf of tape block */ + struct bdesc *end = bufp + (ntrec-1); /* last buf of tape block */ + int siz; /* bytes written (block) */ + int trecs; /* records written (block) */ + long asize = 0; /* number of 0.1" units... */ + /* ...written on current tape */ + char *tp, *rbuf = NULL; + char *recmap = spcl.c_addr; /* current tape record map */ + char *endmp; /* end of valid map data */ + char *mp; /* current map entry */ + union u_spcl *sp; + + (void) signal(SIGXFSZ, onxfsz); + + bzero((char *)&spcl, sizeof (spcl)); + count = 0; + + if (doingverify) { + rbuf = (char *)malloc((uint_t)writesize); + if (rbuf == 0) { + /* Restart from checkpoint */ + (void) kill(master, SIGUSR2); + Exit(X_REWRITE); + } + } + + for (;;) { + /* START: wait until all buffers in tape block are full */ + if ((bp->b_flags & BUF_FULL) == 0) { + if (caught) { /* master signalled flush */ + (void) sighold(SIGUSR1); + caught = 0; + /* signal ready */ + (void) kill(master, SIGUSR1); + chkpt.sl_count = 0; /* signal not at EOT */ + checkpoint(bp-1, cmd); /* send data */ + (void) sigpause(SIGUSR1); + break; + } +#ifdef INSTRUMENT + (*readmissp)++; +#endif + nap(50); + continue; + } + if (bp < end) { + bp++; + continue; + } + /* END: wait until all buffers in tape block are full */ + + tp = begin->b_data; + (void) sighold(SIGUSR1); + if (host) { + if (!doingverify) + siz = rmtwrite(tp, writesize); + else if ((siz = rmtread(rbuf, writesize)) == + writesize && bcmp(rbuf, tp, writesize)) + siz = -1; + } else { + if (!doingverify) + siz = write(to, tp, writesize); + else if ((siz = read(to, rbuf, writesize)) == + writesize && bcmp(rbuf, tp, writesize)) + siz = -1; + if (siz < 0 && diskette && errno == ENOSPC) + siz = 0; /* really EOF */ + } + (void) sigrelse(SIGUSR1); + if (siz < 0 || + (pipeout && siz != writesize)) { + char buf[3000]; + + /* + * Isn't i18n wonderful? + */ + if (doingverify) { + if (diskette) + (void) snprintf(buf, sizeof (buf), + gettext( + "Verification error %ld blocks into diskette %d\n"), + asize * 2, tapeno); + else if (tapeout) + (void) snprintf(buf, sizeof (buf), + gettext( + "Verification error %ld feet into tape %d\n"), + (cartridge ? asize/tracks : + asize)/120L, + tapeno); + else + (void) snprintf(buf, sizeof (buf), + gettext( + "Verification error %ld blocks into volume %d\n"), + asize * 2, tapeno); + + } else { + if (diskette) + (void) snprintf(buf, sizeof (buf), + gettext( + "Write error %ld blocks into diskette %d\n"), + asize * 2, tapeno); + else if (tapeout) + (void) snprintf(buf, sizeof (buf), + gettext( + "Write error %ld feet into tape %d\n"), + (cartridge ? asize/tracks : + asize)/120L, tapeno); + else + (void) snprintf(buf, sizeof (buf), + gettext( + "Write error %ld blocks into volume %d\n"), + asize * 2, tapeno); + } + + msg(buf); + /* Restart from checkpoint */ +#ifdef TDEBUG + + /* XGETTEXT: #ifdef TDEBUG only */ + msg(gettext("sending SIGUSR2 to pid %ld\n"), master); +#endif + (void) kill(master, SIGUSR2); + Exit(X_REWRITE); + } + trecs = siz / tp_bsize; + if (diskette) + asize += trecs; /* asize == blocks written */ + else + asize += (siz/density + tenthsperirg); + if (trecs) + chkpt.sl_firstrec++; + for (bp = begin; bp < begin + trecs; bp++) { + if ((arch >= 0) && (bp->b_flags & BUF_ARCHIVE)) { + if ((unsigned)atomic((int(*)())write, arch, + (char *)&bp->b_flags, sizeof (bp->b_flags)) + != sizeof (bp->b_flags)) { + cmdwrterr(); + dumpabort(); + /*NOTREACHED*/ + } + if (atomic((int(*)())write, arch, bp->b_data, + tp_bsize) != tp_bsize) { + cmdwrterr(); + dumpabort(); + /*NOTREACHED*/ + } + } + if (bp->b_flags & BUF_SPCLREC) { + /*LINTED [bp->b_data is aligned]*/ + sp = (union u_spcl *)bp->b_data; + if (sp->s_spcl.c_type != TS_ADDR) { + lastnonaddr = sp->s_spcl.c_type; + lastnonaddrm = + sp->s_spcl.c_dinode.di_mode; + if (sp->s_spcl.c_type != TS_TAPE) + chkpt.sl_offset = 0; + } + chkpt.sl_count = sp->s_spcl.c_count; + bcopy((char *)sp, + (char *)&spcl, sizeof (spcl)); + mp = recmap; + endmp = &recmap[spcl.c_count]; + count = 0; + } else { + chkpt.sl_offset++; + chkpt.sl_count--; + count++; + mp++; + } + /* + * Adjust for contiguous hole + */ + for (; mp < endmp; mp++) { + if (*mp) + break; + chkpt.sl_offset++; + chkpt.sl_count--; + } + } + /* + * Check for end of tape + */ + if (trecs < ntrec || + (!pipeout && tsize > 0 && asize > tsize)) { + if (tapeout) + msg(gettext("End-of-tape detected\n")); + else + msg(gettext("End-of-file detected\n")); + (void) sighold(SIGUSR1); + caught = 0; + (void) kill(master, SIGUSR1); /* signal EOT */ + checkpoint(--bp, cmd); /* send checkpoint data */ + (void) sigpause(SIGUSR1); + break; + } + for (bp = begin; bp <= end; bp++) + bp->b_flags = BUF_EMPTY; + if (end + ntrec > last) { + bp = begin = bufp; + timeest(0, spcl.c_tapea); + } else + bp = begin = end+1; + end = begin + (ntrec-1); + } + + if (rbuf != NULL) + free(rbuf); +} + +/* + * Send checkpoint info back to master. This information + * consists of the current inode number, number of logical + * blocks written for that inode (or bitmap), the last logical + * block number written, the number of logical blocks written + * to this volume, the current dump state, and the current + * special record map. + */ +static void +checkpoint(bp, cmd) + struct bdesc *bp; + int cmd; +{ + int state, type; + ino_t ino; + + if (++bp >= &bufp[NBUF*ntrec]) + bp = bufp; + + /* + * If we are dumping files and the record following + * the last written to tape is a special record, use + * it to get an accurate indication of current state. + */ + if ((bp->b_flags & BUF_SPCLREC) && (bp->b_flags & BUF_FULL) && + lastnonaddr == TS_INODE) { + /*LINTED [bp->b_data is aligned]*/ + union u_spcl *nextspcl = (union u_spcl *)bp->b_data; + + if (nextspcl->s_spcl.c_type == TS_INODE) { + chkpt.sl_offset = 0; + chkpt.sl_count = 0; + } else if (nextspcl->s_spcl.c_type == TS_END) { + chkpt.sl_offset = 0; + chkpt.sl_count = 1; /* EOT indicator */ + } + ino = nextspcl->s_spcl.c_inumber; + type = nextspcl->s_spcl.c_type; + } else { + /* + * If not, use what we have. + */ + ino = spcl.c_inumber; + type = spcl.c_type; + } + + switch (type) { /* set output state */ + case TS_ADDR: + switch (lastnonaddr) { + case TS_INODE: + case TS_TAPE: + if ((lastnonaddrm & IFMT) == IFDIR || + (lastnonaddrm & IFMT) == IFATTRDIR) + state = DS_DIRS; + else + state = DS_FILES; + break; + case TS_CLRI: + state = DS_CLRI; + break; + case TS_BITS: + state = DS_BITS; + break; + } + break; + case TS_INODE: + if ((spcl.c_dinode.di_mode & IFMT) == IFDIR || + (spcl.c_dinode.di_mode & IFMT) == IFATTRDIR) + state = DS_DIRS; + else + state = DS_FILES; + break; + case 0: /* EOT on 1st record */ + case TS_TAPE: + state = DS_START; + ino = UFSROOTINO; + break; + case TS_CLRI: + state = DS_CLRI; + break; + case TS_BITS: + state = DS_BITS; + break; + case TS_END: + if (spcl.c_type == TS_END) + state = DS_DONE; + else + state = DS_END; + break; + } + + /* + * Checkpoint info to be processed by rollforward(): + * The inode with which the next volume should begin + * The last inode number on this volume + * The last logical block number on this volume + * The current output state + * The offset within the current inode (already in sl_offset) + * The number of records left from last spclrec (in sl_count) + * The physical block the next vol begins with (in sl_firstrec) + */ + chkpt.sl_inos = ino; + chkpt.sl_tapea = spcl.c_tapea + count; + chkpt.sl_state = state; + + if ((unsigned)atomic((int(*)())write, cmd, (char *)&chkpt, + sizeof (chkpt)) != sizeof (chkpt)) { + cmdwrterr(); + dumpabort(); + /*NOTREACHED*/ + } + if ((unsigned)atomic((int(*)())write, cmd, (char *)&spcl, + sizeof (spcl)) != sizeof (spcl)) { + cmdwrterr(); + dumpabort(); + /*NOTREACHED*/ + } +#ifdef DEBUG + if (xflag) { + /* XGETTEXT: #ifdef DEBUG only */ + msg(gettext("sent chkpt to master:\n")); + msg(" ino %u\n", chkpt.sl_inos); + msg(" 1strec %u\n", chkpt.sl_firstrec); + msg(" lastrec %u\n", chkpt.sl_tapea); + msg(" written %u\n", chkpt.sl_offset); + msg(" left %u\n", chkpt.sl_count); + msg(" state %d\n", chkpt.sl_state); + } +#endif +} + +/* + * Since a read from a pipe may not return all we asked for, + * or a write may not write all we ask if we get a signal, + * loop until the count is satisfied (or error). + */ +static ssize_t +atomic(func, fd, buf, count) + int (*func)(), fd, count; + char *buf; +{ + ssize_t got = 0, need = count; + + /* don't inherit random value if immediately get zero back from func */ + errno = 0; + while (need > 0) { + got = (*func)(fd, buf, MIN(need, 4096)); + if (got < 0 && errno == EINTR) + continue; + if (got <= 0) + break; + buf += got; + need -= got; + } + /* if we got what was asked for, return count, else failure (got) */ + return ((need != 0) ? got : count); +} + +void +#ifdef __STDC__ +positiontape(char *msgbuf) +#else +positiontape(msgbuf) + char *msgbuf; +#endif +{ + /* Static as never change, no need to waste stack space */ + static struct mtget mt; + static struct mtop rew = { MTREW, 1 }; + static struct mtop fsf = { MTFSF, 1 }; + char *info = strdup(gettext("Positioning `%s' to file %ld\n")); + char *fail = strdup(gettext("Cannot position tape to file %d\n")); + int m; + + /* gettext()'s return value is volatile, hence the strdup()s */ + + m = (access(tape, F_OK) == 0) ? 0 : O_CREAT; + + /* + * To avoid writing tape marks at inappropriate places, we open the + * device read-only, position it, close it, and reopen it for writing. + */ + while ((to = host ? rmtopen(tape, O_RDONLY) : + safe_device_open(tape, O_RDONLY|m, 0600)) < 0) { + if (autoload) { + if (!query_once(msgbuf, 1)) { + dumpabort(); + /*NOTREACHED*/ + } + } else { + if (!query(msgbuf)) { + dumpabort(); + /*NOTREACHED*/ + } + } + } + + if (host) { + if (rmtstatus(&mt) >= 0 && + rmtioctl(MTREW, 1) >= 0 && + filenum > 1) { + msg(info, dumpdev, filenum); + if (rmtioctl(MTFSF, filenum-1) < 0) { + msg(fail, filenum); + dumpabort(); + /*NOTREACHED*/ + } + } + rmtclose(); + } else { + if (ioctl(to, MTIOCGET, &mt) >= 0 && + ioctl(to, MTIOCTOP, &rew) >= 0 && + filenum > 1) { + msg(info, dumpdev, filenum); + fsf.mt_count = filenum - 1; + if (ioctl(to, MTIOCTOP, &fsf) < 0) { + msg(fail, filenum); + dumpabort(); + /*NOTREACHED*/ + } + } + (void) close(to); + to = -1; + } + + free(info); + free(fail); +} + +static void +#ifdef __STDC__ +cmdwrterr(void) +#else +cmdwrterr() +#endif +{ + int saverr = errno; + msg(gettext("Error writing command pipe: %s\n"), strerror(saverr)); +} + +static void +#ifdef __STDC__ +cmdrderr(void) +#else +cmdrderr() +#endif +{ + int saverr = errno; + msg(gettext("Error reading command pipe: %s\n"), strerror(saverr)); +} |