diff options
author | rgrimes <rgrimes@FreeBSD.org> | 1994-05-26 06:35:07 +0000 |
---|---|---|
committer | rgrimes <rgrimes@FreeBSD.org> | 1994-05-26 06:35:07 +0000 |
commit | d038e02fd667ab6c02875840105798aaa7029504 (patch) | |
tree | b555a2b228429d62f4946ae1a7e68c33012e1b03 /sbin/dump | |
parent | e3cfc8ce61f788739c66445d903f8beacb40c93d (diff) | |
download | FreeBSD-src-d038e02fd667ab6c02875840105798aaa7029504.zip FreeBSD-src-d038e02fd667ab6c02875840105798aaa7029504.tar.gz |
BSD 4.4 Lite sbin Sources
Note: XNSrouted and routed NOT imported here, they shall be imported with
usr.sbin.
Diffstat (limited to 'sbin/dump')
-rw-r--r-- | sbin/dump/Makefile | 25 | ||||
-rw-r--r-- | sbin/dump/dump.8 | 335 | ||||
-rw-r--r-- | sbin/dump/dump.h | 212 | ||||
-rw-r--r-- | sbin/dump/dumprmt.c | 380 | ||||
-rw-r--r-- | sbin/dump/itime.c | 271 | ||||
-rw-r--r-- | sbin/dump/main.c | 560 | ||||
-rw-r--r-- | sbin/dump/optr.c | 538 | ||||
-rw-r--r-- | sbin/dump/pathnames.h | 42 | ||||
-rw-r--r-- | sbin/dump/tape.c | 859 | ||||
-rw-r--r-- | sbin/dump/traverse.c | 613 | ||||
-rw-r--r-- | sbin/dump/unctime.c | 157 |
11 files changed, 3992 insertions, 0 deletions
diff --git a/sbin/dump/Makefile b/sbin/dump/Makefile new file mode 100644 index 0000000..78ab5d8 --- /dev/null +++ b/sbin/dump/Makefile @@ -0,0 +1,25 @@ +# @(#)Makefile 8.1 (Berkeley) 6/5/93 + +# dump.h header file +# itime.c reads /etc/dumpdates +# main.c driver +# optr.c operator interface +# dumprmt.c handles remote tape via rmt(8) +# tape.c handles the mag tape and opening/closing +# traverse.c traverses the file system +# unctime.c undo ctime +# +# DEBUG use local directory to find ddate and dumpdates +# TDEBUG trace out the process forking + +PROG= dump +LINKS= ${BINDIR}/dump ${BINDIR}/rdump +CFLAGS+=-DRDUMP +SRCS= itime.c main.c optr.c dumprmt.c tape.c traverse.c unctime.c +BINOWN= root +BINGRP= tty +BINMODE=6555 +MAN8= dump.0 +MLINKS+=dump.8 rdump.8 + +.include <bsd.prog.mk> diff --git a/sbin/dump/dump.8 b/sbin/dump/dump.8 new file mode 100644 index 0000000..2cd9335 --- /dev/null +++ b/sbin/dump/dump.8 @@ -0,0 +1,335 @@ +.\" Copyright (c) 1980, 1991, 1993 +.\" 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. +.\" +.\" @(#)dump.8 8.1 (Berkeley) 6/16/93 +.\" +.Dd June 16, 1993 +.Dt DUMP 8 +.Os BSD 4 +.Sh NAME +.Nm dump +.Nd filesystem backup +.Sh SYNOPSIS +.Nm dump +.Op Cm 0123456789BbhfusTdWn Op Ar argument ... +.Op Ar filesystem +.Sh DESCRIPTION +.Nm Dump +examines files +on a filesystem +and determines which files +need to be backed up. These files +are copied to the given disk, tape or other +storage medium for safe keeping (see the +.Cm f +option below for doing remote backups). +A dump that is larger than the output medium is broken into +multiple volumes. +On most media the size is determined by writing until an +end-of-media indication is returned. +On media that cannot reliably return an end-of-media indication +(such as some cartridge tape drives) +each volume is of a fixed size; +the actual size is determined by the tape size and density and/or +block count options below. +By default, the same output file name is used for each volume +after prompting the operator to change media. +.Pp +The following options are supported by +.Nm dump: +.Bl -tag -width 4n +.It Cm 0\-9 +Dump levels. +A level 0, full backup, +guarantees the entire file system is copied +(but see also the +.Cm h +option below). +A level number above 0, +incremental backup, +tells dump to +copy all files new or modified since the +last dump of the same or lower level. The default +level is 9. +.It Cm B Ar records +The number of dump records per volume. +This option overrides the calculation of tape size +based on length and density. +.It Cm b Ar blocksize +The number of kilobytes per dump record. +.It Cm h Ar level +Honor the user +.Dq nodump +flag +.Dp Dv UF_NODUMP +only for dumps at or above the given +.Ar level . +The default honor level is 1, +so that incremental backups omit such files +but full backups retain them. +.It Cm f Ar file +Write the backup to +.Ar file ; +.Ar file +may be a special device file +like +.Pa /dev/rmt12 +(a tape drive), +.Pa /dev/rsd1c +(a disk drive), +an ordinary file, +or +.Ql Fl +(the standard output). +Multiple file names may be given as a single argument separated by commas. +Each file will be used for one dump volume in the order listed; +if the dump requires more volumes than the number of names given, +the last file name will used for all remaining volumes after prompting +for media changes. +If the name of the file is of the form +.Dq host:file , +or +.Dq user@host:file , +.Nm dump +writes to the named file on the remote host using +.Xr rmt 8 . +.It Cm d Ar density +Set tape density to +.Ar density . +The default is 1600BPI. +.It Cm n +Whenever +.Nm dump +requires operator attention, +notify all operators in the group +.Dq operator +by means similar to a +.Xr wall 1 . +.It Cm s Ar feet +Attempt to calculate the amount of tape needed +at a particular density. +If this amount is exceeded, +.Nm dump +prompts for a new tape. +It is recommended to be a bit conservative on this option. +The default tape length is 2300 feet. +.It Cm u +Update the file +.Pa /etc/dumpdates +after a successful dump. +The format of +.Pa /etc/dumpdates +is readable by people, consisting of one +free format record per line: +filesystem name, +increment level +and +.Xr ctime 3 +format dump date. +There may be only one entry per filesystem at each level. +The file +.Pa /etc/dumpdates +may be edited to change any of the fields, +if necessary. +.It Cm T Ar date +Use the specified date as the starting time for the dump +instead of the time determined from looking in +.Pa /etc/dumpdates . +The format of date is the same as that of +.Xr ctime 3 . +This option is useful for automated dump scripts that wish to +dump over a specific period of time. +The +.Cm T +option is mutually exclusive from the +.Cm u +option. +.It Cm W +.Nm Dump +tells the operator what file systems need to be dumped. +This information is gleaned from the files +.Pa /etc/dumpdates +and +.Pa /etc/fstab . +The +.Cm W +option causes +.Nm dump +to print out, for each file system in +.Pa /etc/dumpdates +the most recent dump date and level, +and highlights those file systems that should be dumped. +If the +.Cm W +option is set, all other options are ignored, and +.Nm dump +exits immediately. +.It Cm w +Is like W, but prints only those filesystems which need to be dumped. +.El +.Pp +.Nm Dump +requires operator intervention on these conditions: +end of tape, +end of dump, +tape write error, +tape open error or +disk read error (if there are more than a threshold of 32). +In addition to alerting all operators implied by the +.Cm n +key, +.Nm dump +interacts with the operator on +.Em dump's +control terminal at times when +.Nm dump +can no longer proceed, +or if something is grossly wrong. +All questions +.Nm dump +poses +.Em must +be answered by typing +.Dq yes +or +.Dq no , +appropriately. +.Pp +Since making a dump involves a lot of time and effort for full dumps, +.Nm dump +checkpoints itself at the start of each tape volume. +If writing that volume fails for some reason, +.Nm dump +will, +with operator permission, +restart itself from the checkpoint +after the old tape has been rewound and removed, +and a new tape has been mounted. +.Pp +.Nm Dump +tells the operator what is going on at periodic intervals, +including usually low estimates of the number of blocks to write, +the number of tapes it will take, the time to completion, and +the time to the tape change. +The output is verbose, +so that others know that the terminal +controlling +.Nm dump +is busy, +and will be for some time. +.Pp +In the event of a catastrophic disk event, the time required +to restore all the necessary backup tapes or files to disk +can be kept to a minimum by staggering the incremental dumps. +An efficient method of staggering incremental dumps +to minimize the number of tapes follows: +.Bl -bullet -offset indent +.It +Always start with a level 0 backup, for example: +.Bd -literal -offset indent +/etc/dump 0uf /dev/nrst1 /usr/src +.Ed +.Pp +This should be done at set intervals, say once a month or once every two months, +and on a set of fresh tapes that is saved forever. +.It +After a level 0, dumps of active file +systems are taken on a daily basis, +using a modified Tower of Hanoi algorithm, +with this sequence of dump levels: +.Bd -literal -offset indent +3 2 5 4 7 6 9 8 9 9 ... +.Ed +.Pp +For the daily dumps, it should be possible to use a fixed number of tapes +for each day, used on a weekly basis. +Each week, a level 1 dump is taken, and +the daily Hanoi sequence repeats beginning with 3. +For weekly dumps, another fixed set of tapes per dumped file system is +used, also on a cyclical basis. +.El +.Pp +After several months or so, the daily and weekly tapes should get +rotated out of the dump cycle and fresh tapes brought in. +.Sh FILES +.Bl -tag -width /etc/dumpdates -compact +.It Pa /dev/rmt8 +default tape unit to dump to +.It Pa /etc/dumpdates +dump date records +.It Pa /etc/fstab +dump table: file systems and frequency +.It Pa /etc/group +to find group +.Em operator +.El +.Sh SEE ALSO +.Xr restore 8 , +.Xr rmt 8 , +.Xr dump 5 , +.Xr fstab 5 +.Sh DIAGNOSTICS +Many, and verbose. +.Pp +Dump exits with zero status on success. +Startup errors are indicated with an exit code of 1; +abnormal termination is indicated with an exit code of 3. +.Sh BUGS +.Pp +Fewer than 32 read errors on the filesystem are ignored. +Each reel requires a new process, so parent processes for +reels already written just hang around until the entire tape +is written. +.Pp +.Nm Dump +with the +.Cm W +or +.Cm w +options does not report filesystems that have never been recorded +in +.Pa /etc/dumpdates , +even if listed in +.Pa /etc/fstab . +.Pp +It would be nice if +.Nm dump +knew about the dump sequence, +kept track of the tapes scribbled on, +told the operator which tape to mount when, +and provided more assistance +for the operator running +.Xr restore . +.Sh HISTORY +A +.Nm dump +command appeared in Version 6 AT&T UNIX. diff --git a/sbin/dump/dump.h b/sbin/dump/dump.h new file mode 100644 index 0000000..9060b00 --- /dev/null +++ b/sbin/dump/dump.h @@ -0,0 +1,212 @@ +/*- + * Copyright (c) 1980, 1993 + * 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. + * + * @(#)dump.h 8.1 (Berkeley) 6/5/93 + */ + +#define MAXINOPB (MAXBSIZE / sizeof(struct dinode)) +#define MAXNINDIR (MAXBSIZE / sizeof(daddr_t)) + +/* + * Dump maps used to describe what is to be dumped. + */ +int mapsize; /* size of the state maps */ +char *usedinomap; /* map of allocated inodes */ +char *dumpdirmap; /* map of directories to be dumped */ +char *dumpinomap; /* map of files to be dumped */ +/* + * Map manipulation macros. + */ +#define SETINO(ino, map) \ + map[(u_int)((ino) - 1) / NBBY] |= 1 << ((u_int)((ino) - 1) % NBBY) +#define CLRINO(ino, map) \ + map[(u_int)((ino) - 1) / NBBY] &= ~(1 << ((u_int)((ino) - 1) % NBBY)) +#define TSTINO(ino, map) \ + (map[(u_int)((ino) - 1) / NBBY] & (1 << ((u_int)((ino) - 1) % NBBY))) + +/* + * All calculations done in 0.1" units! + */ +char *disk; /* name of the disk file */ +char *tape; /* name of the tape file */ +char *dumpdates; /* name of the file containing dump date information*/ +char *temp; /* name of the file for doing rewrite of dumpdates */ +char lastlevel; /* dump level of previous dump */ +char level; /* dump level of this dump */ +int uflag; /* update flag */ +int diskfd; /* disk file descriptor */ +int tapefd; /* tape file descriptor */ +int pipeout; /* true => output to standard output */ +ino_t curino; /* current inumber; used globally */ +int newtape; /* new tape flag */ +int density; /* density in 0.1" units */ +long tapesize; /* estimated tape size, blocks */ +long tsize; /* tape size in 0.1" units */ +long asize; /* number of 0.1" units written on current tape */ +int etapes; /* estimated number of tapes */ +int nonodump; /* if set, do not honor UF_NODUMP user flags */ + +int notify; /* notify operator flag */ +int blockswritten; /* number of blocks written on current tape */ +int tapeno; /* current tape number */ +time_t tstart_writing; /* when started writing the first tape block */ +struct fs *sblock; /* the file system super block */ +char sblock_buf[MAXBSIZE]; +long dev_bsize; /* block size of underlying disk device */ +int dev_bshift; /* log2(dev_bsize) */ +int tp_bshift; /* log2(TP_BSIZE) */ + +#ifndef __P +#include <sys/cdefs.h> +#endif + +/* operator interface functions */ +void broadcast __P((char *message)); +void lastdump __P((int arg)); /* int should be char */ +void msg __P((const char *fmt, ...)); +void msgtail __P((const char *fmt, ...)); +int query __P((char *question)); +void quit __P((const char *fmt, ...)); +void set_operators __P((void)); +void timeest __P((void)); +time_t unctime __P((char *str)); + +/* mapping rouintes */ +struct dinode; +long blockest __P((struct dinode *dp)); +int mapfiles __P((ino_t maxino, long *tapesize)); +int mapdirs __P((ino_t maxino, long *tapesize)); + +/* file dumping routines */ +void blksout __P((daddr_t *blkp, int frags, ino_t ino)); +void bread __P((daddr_t blkno, char *buf, int size)); +void dumpino __P((struct dinode *dp, ino_t ino)); +void dumpmap __P((char *map, int type, ino_t ino)); +void writeheader __P((ino_t ino)); + +/* tape writing routines */ +int alloctape __P((void)); +void close_rewind __P((void)); +void dumpblock __P((daddr_t blkno, int size)); +void startnewtape __P((int top)); +void trewind __P((void)); +void writerec __P((char *dp, int isspcl)); + +__dead void Exit __P((int status)); +void dumpabort __P((int signo)); +void getfstab __P((void)); + +char *rawname __P((char *cp)); +struct dinode *getino __P((ino_t inum)); + +/* rdump routines */ +#ifdef RDUMP +void rmtclose __P((void)); +int rmthost __P((char *host)); +int rmtopen __P((char *tape, int mode)); +int rmtwrite __P((char *buf, int count)); +#endif /* RDUMP */ + +void interrupt __P((int signo)); /* in case operator bangs on console */ + +/* + * Exit status codes + */ +#define X_FINOK 0 /* normal exit */ +#define X_REWRITE 2 /* restart writing from the check point */ +#define X_ABORT 3 /* abort dump; don't attempt checkpointing */ + +#define OPGRENT "operator" /* group entry to notify */ +#define DIALUP "ttyd" /* prefix for dialups */ + +struct fstab *fstabsearch __P((char *key)); /* search fs_file and fs_spec */ + +#ifndef NAME_MAX +#define NAME_MAX 255 +#endif + +/* + * The contents of the file _PATH_DUMPDATES is maintained both on + * a linked list, and then (eventually) arrayified. + */ +struct dumpdates { + char dd_name[NAME_MAX+3]; + char dd_level; + time_t dd_ddate; +}; +struct dumptime { + struct dumpdates dt_value; + struct dumptime *dt_next; +}; +struct dumptime *dthead; /* head of the list version */ +int nddates; /* number of records (might be zero) */ +int ddates_in; /* we have read the increment file */ +struct dumpdates **ddatev; /* the arrayfied version */ +void initdumptimes __P((void)); +void getdumptime __P((void)); +void putdumptime __P((void)); +#define ITITERATE(i, ddp) \ + for (ddp = ddatev[i = 0]; i < nddates; ddp = ddatev[++i]) + +void sig __P((int signo)); + +/* + * Compatibility with old systems. + */ +#ifdef COMPAT +#include <sys/file.h> +extern char *index(), *rindex(), *strdup(); +extern char *ctime(); +extern int read(), write(); +extern int errno; +#endif + +#ifndef _PATH_UTMP +#define _PATH_UTMP "/etc/utmp" +#endif +#ifndef _PATH_FSTAB +#define _PATH_FSTAB "/etc/fstab" +#endif + +#ifdef sunos +extern char *calloc(); +extern char *malloc(); +extern long atol(); +extern char *strcpy(); +extern char *strncpy(); +extern char *strcat(); +extern time_t time(); +extern void endgrent(); +extern __dead void exit(); +extern off_t lseek(); +extern const char *strerror(); +#endif diff --git a/sbin/dump/dumprmt.c b/sbin/dump/dumprmt.c new file mode 100644 index 0000000..22acbfd --- /dev/null +++ b/sbin/dump/dumprmt.c @@ -0,0 +1,380 @@ +/*- + * Copyright (c) 1980, 1993 + * 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 char sccsid[] = "@(#)dumprmt.c 8.1 (Berkeley) 6/5/93"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/mtio.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/time.h> +#ifdef sunos +#include <sys/vnode.h> + +#include <ufs/inode.h> +#else +#include <ufs/ufs/dinode.h> +#endif + +#include <netinet/in.h> +#include <netinet/tcp.h> + +#include <protocols/dumprestore.h> + +#include <ctype.h> +#include <netdb.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#ifdef __STDC__ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#endif + +#include "pathnames.h" +#include "dump.h" + +#define TS_CLOSED 0 +#define TS_OPEN 1 + +static int rmtstate = TS_CLOSED; +static int rmtape; +static char *rmtpeer; + +static int okname __P((char *)); +static int rmtcall __P((char *, char *)); +static void rmtconnaborted __P((/* int, int */)); +static int rmtgetb __P((void)); +static void rmtgetconn __P((void)); +static void rmtgets __P((char *, int)); +static int rmtreply __P((char *)); + +extern int ntrec; /* blocking factor on tape */ + +int +rmthost(host) + char *host; +{ + + rmtpeer = malloc(strlen(host) + 1); + if (rmtpeer) + strcpy(rmtpeer, host); + else + rmtpeer = host; + signal(SIGPIPE, rmtconnaborted); + rmtgetconn(); + if (rmtape < 0) + return (0); + return (1); +} + +static void +rmtconnaborted() +{ + + (void) fprintf(stderr, "rdump: Lost connection to remote host.\n"); + exit(1); +} + +void +rmtgetconn() +{ + register char *cp; + static struct servent *sp = NULL; + static struct passwd *pwd = NULL; +#ifdef notdef + static int on = 1; +#endif + char *tuser; + int size; + int maxseg; + + if (sp == NULL) { + sp = getservbyname("shell", "tcp"); + if (sp == NULL) { + (void) fprintf(stderr, + "rdump: shell/tcp: unknown service\n"); + exit(1); + } + pwd = getpwuid(getuid()); + if (pwd == NULL) { + (void) fprintf(stderr, "rdump: who are you?\n"); + exit(1); + } + } + if ((cp = index(rmtpeer, '@')) != NULL) { + tuser = rmtpeer; + *cp = '\0'; + if (!okname(tuser)) + exit(1); + rmtpeer = ++cp; + } else + tuser = pwd->pw_name; + rmtape = rcmd(&rmtpeer, (u_short)sp->s_port, pwd->pw_name, tuser, + _PATH_RMT, (int *)0); + size = ntrec * TP_BSIZE; + if (size > 60 * 1024) /* XXX */ + size = 60 * 1024; + /* Leave some space for rmt request/response protocol */ + size += 2 * 1024; + while (size > TP_BSIZE && + setsockopt(rmtape, SOL_SOCKET, SO_SNDBUF, &size, sizeof (size)) < 0) + size -= TP_BSIZE; + (void)setsockopt(rmtape, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size)); + maxseg = 1024; + if (setsockopt(rmtape, IPPROTO_TCP, TCP_MAXSEG, + &maxseg, sizeof (maxseg)) < 0) + perror("TCP_MAXSEG setsockopt"); + +#ifdef notdef + if (setsockopt(rmtape, IPPROTO_TCP, TCP_NODELAY, &on, sizeof (on)) < 0) + perror("TCP_NODELAY setsockopt"); +#endif +} + +static int +okname(cp0) + char *cp0; +{ + register char *cp; + register int c; + + for (cp = cp0; *cp; cp++) { + c = *cp; + if (!isascii(c) || !(isalnum(c) || c == '_' || c == '-')) { + (void) fprintf(stderr, "rdump: invalid user name %s\n", + cp0); + return (0); + } + } + return (1); +} + +int +rmtopen(tape, mode) + char *tape; + int mode; +{ + char buf[256]; + + (void)sprintf(buf, "O%s\n%d\n", tape, mode); + rmtstate = TS_OPEN; + return (rmtcall(tape, buf)); +} + +void +rmtclose() +{ + + if (rmtstate != TS_OPEN) + return; + rmtcall("close", "C\n"); + rmtstate = TS_CLOSED; +} + +int +rmtread(buf, count) + char *buf; + int count; +{ + char line[30]; + int n, i, cc; + extern errno; + + (void)sprintf(line, "R%d\n", count); + n = rmtcall("read", line); + if (n < 0) { + errno = n; + return (-1); + } + for (i = 0; i < n; i += cc) { + cc = read(rmtape, buf+i, n - i); + if (cc <= 0) { + rmtconnaborted(); + } + } + return (n); +} + +int +rmtwrite(buf, count) + char *buf; + int count; +{ + char line[30]; + + (void)sprintf(line, "W%d\n", count); + write(rmtape, line, strlen(line)); + write(rmtape, buf, count); + return (rmtreply("write")); +} + +void +rmtwrite0(count) + int count; +{ + char line[30]; + + (void)sprintf(line, "W%d\n", count); + write(rmtape, line, strlen(line)); +} + +void +rmtwrite1(buf, count) + char *buf; + int count; +{ + + write(rmtape, buf, count); +} + +int +rmtwrite2() +{ + + return (rmtreply("write")); +} + +int +rmtseek(offset, pos) + int offset, pos; +{ + char line[80]; + + (void)sprintf(line, "L%d\n%d\n", offset, pos); + return (rmtcall("seek", line)); +} + +struct mtget mts; + +struct mtget * +rmtstatus() +{ + register int i; + register char *cp; + + if (rmtstate != TS_OPEN) + return (NULL); + rmtcall("status", "S\n"); + for (i = 0, cp = (char *)&mts; i < sizeof(mts); i++) + *cp++ = rmtgetb(); + return (&mts); +} + +int +rmtioctl(cmd, count) + int cmd, count; +{ + char buf[256]; + + if (count < 0) + return (-1); + (void)sprintf(buf, "I%d\n%d\n", cmd, count); + return (rmtcall("ioctl", buf)); +} + +static int +rmtcall(cmd, buf) + char *cmd, *buf; +{ + + if (write(rmtape, buf, strlen(buf)) != strlen(buf)) + rmtconnaborted(); + return (rmtreply(cmd)); +} + +static int +rmtreply(cmd) + char *cmd; +{ + register char *cp; + char code[30], emsg[BUFSIZ]; + + rmtgets(code, sizeof (code)); + if (*code == 'E' || *code == 'F') { + rmtgets(emsg, sizeof (emsg)); + msg("%s: %s", cmd, emsg); + if (*code == 'F') { + rmtstate = TS_CLOSED; + return (-1); + } + return (-1); + } + if (*code != 'A') { + /* Kill trailing newline */ + cp = code + strlen(code); + if (cp > code && *--cp == '\n') + *cp = '\0'; + + msg("Protocol to remote tape server botched (code \"%s\").\n", + code); + rmtconnaborted(); + } + return (atoi(code + 1)); +} + +int +rmtgetb() +{ + char c; + + if (read(rmtape, &c, 1) != 1) + rmtconnaborted(); + return (c); +} + +/* Get a line (guaranteed to have a trailing newline). */ +void +rmtgets(line, len) + char *line; + int len; +{ + register char *cp = line; + + while (len > 1) { + *cp = rmtgetb(); + if (*cp == '\n') { + cp[1] = '\0'; + return; + } + cp++; + len--; + } + *cp = '\0'; + msg("Protocol to remote tape server botched.\n"); + msg("(rmtgets got \"%s\").\n", line); + rmtconnaborted(); +} diff --git a/sbin/dump/itime.c b/sbin/dump/itime.c new file mode 100644 index 0000000..94656ac --- /dev/null +++ b/sbin/dump/itime.c @@ -0,0 +1,271 @@ +/*- + * Copyright (c) 1980, 1993 + * 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 char sccsid[] = "@(#)itime.c 8.1 (Berkeley) 6/5/93"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#ifdef sunos +#include <sys/vnode.h> + +#include <ufs/fsdir.h> +#include <ufs/inode.h> +#include <ufs/fs.h> +#else +#include <ufs/ufs/dinode.h> +#endif + +#include <protocols/dumprestore.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#ifdef __STDC__ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#endif + +#include "dump.h" + +struct dumpdates **ddatev = 0; +int nddates = 0; +int ddates_in = 0; +struct dumptime *dthead = 0; + +static void dumprecout __P((FILE *, struct dumpdates *)); +static int getrecord __P((FILE *, struct dumpdates *)); +static int makedumpdate __P((struct dumpdates *, char *)); +static void readdumptimes __P((FILE *)); + +void +initdumptimes() +{ + FILE *df; + + if ((df = fopen(dumpdates, "r")) == NULL) { + if (errno != ENOENT) { + quit("cannot read %s: %s\n", dumpdates, + strerror(errno)); + /* NOTREACHED */ + } + /* + * Dumpdates does not exist, make an empty one. + */ + msg("WARNING: no file `%s', making an empty one\n", dumpdates); + if ((df = fopen(dumpdates, "w")) == NULL) { + quit("cannot create %s: %s\n", dumpdates, + strerror(errno)); + /* NOTREACHED */ + } + (void) fclose(df); + if ((df = fopen(dumpdates, "r")) == NULL) { + quit("cannot read %s even after creating it: %s\n", + dumpdates, strerror(errno)); + /* NOTREACHED */ + } + } + (void) flock(fileno(df), LOCK_SH); + readdumptimes(df); + (void) fclose(df); +} + +static void +readdumptimes(df) + FILE *df; +{ + register int i; + register struct dumptime *dtwalk; + + for (;;) { + dtwalk = (struct dumptime *)calloc(1, sizeof (struct dumptime)); + if (getrecord(df, &(dtwalk->dt_value)) < 0) + break; + nddates++; + dtwalk->dt_next = dthead; + dthead = dtwalk; + } + + ddates_in = 1; + /* + * arrayify the list, leaving enough room for the additional + * record that we may have to add to the ddate structure + */ + ddatev = (struct dumpdates **) + calloc((unsigned) (nddates + 1), sizeof (struct dumpdates *)); + dtwalk = dthead; + for (i = nddates - 1; i >= 0; i--, dtwalk = dtwalk->dt_next) + ddatev[i] = &dtwalk->dt_value; +} + +void +getdumptime() +{ + register struct dumpdates *ddp; + register int i; + char *fname; + + fname = disk; +#ifdef FDEBUG + msg("Looking for name %s in dumpdates = %s for level = %c\n", + fname, dumpdates, level); +#endif + spcl.c_ddate = 0; + lastlevel = '0'; + + initdumptimes(); + /* + * Go find the entry with the same name for a lower increment + * and older date + */ + ITITERATE(i, ddp) { + if (strncmp(fname, ddp->dd_name, sizeof (ddp->dd_name)) != 0) + continue; + if (ddp->dd_level >= level) + continue; + if (ddp->dd_ddate <= spcl.c_ddate) + continue; + spcl.c_ddate = ddp->dd_ddate; + lastlevel = ddp->dd_level; + } +} + +void +putdumptime() +{ + FILE *df; + register struct dumpdates *dtwalk; + register int i; + int fd; + char *fname; + + if(uflag == 0) + return; + if ((df = fopen(dumpdates, "r+")) == NULL) + quit("cannot rewrite %s: %s\n", dumpdates, strerror(errno)); + fd = fileno(df); + (void) flock(fd, LOCK_EX); + fname = disk; + free((char *)ddatev); + ddatev = 0; + nddates = 0; + dthead = 0; + ddates_in = 0; + readdumptimes(df); + if (fseek(df, 0L, 0) < 0) + quit("fseek: %s\n", strerror(errno)); + spcl.c_ddate = 0; + ITITERATE(i, dtwalk) { + if (strncmp(fname, dtwalk->dd_name, + sizeof (dtwalk->dd_name)) != 0) + continue; + if (dtwalk->dd_level != level) + continue; + goto found; + } + /* + * construct the new upper bound; + * Enough room has been allocated. + */ + dtwalk = ddatev[nddates] = + (struct dumpdates *)calloc(1, sizeof (struct dumpdates)); + nddates += 1; + found: + (void) strncpy(dtwalk->dd_name, fname, sizeof (dtwalk->dd_name)); + dtwalk->dd_level = level; + dtwalk->dd_ddate = spcl.c_date; + + ITITERATE(i, dtwalk) { + dumprecout(df, dtwalk); + } + if (fflush(df)) + quit("%s: %s\n", dumpdates, strerror(errno)); + if (ftruncate(fd, ftell(df))) + quit("ftruncate (%s): %s\n", dumpdates, strerror(errno)); + (void) fclose(df); + msg("level %c dump on %s", level, + spcl.c_date == 0 ? "the epoch\n" : ctime(&spcl.c_date)); +} + +static void +dumprecout(file, what) + FILE *file; + struct dumpdates *what; +{ + + if (fprintf(file, DUMPOUTFMT, + what->dd_name, + what->dd_level, + ctime(&what->dd_ddate)) < 0) + quit("%s: %s\n", dumpdates, strerror(errno)); +} + +int recno; + +static int +getrecord(df, ddatep) + FILE *df; + struct dumpdates *ddatep; +{ + char tbuf[BUFSIZ]; + + recno = 0; + if ( (fgets(tbuf, sizeof (tbuf), df)) != tbuf) + return(-1); + recno++; + if (makedumpdate(ddatep, tbuf) < 0) + msg("Unknown intermediate format in %s, line %d\n", + dumpdates, recno); + +#ifdef FDEBUG + msg("getrecord: %s %c %s", ddatep->dd_name, ddatep->dd_level, + ddatep->dd_ddate == 0 ? "the epoch\n" : ctime(&ddatep->dd_ddate)); +#endif + return(0); +} + +static int +makedumpdate(ddp, tbuf) + struct dumpdates *ddp; + char *tbuf; +{ + char un_buf[128]; + + (void) sscanf(tbuf, DUMPINFMT, ddp->dd_name, &ddp->dd_level, un_buf); + ddp->dd_ddate = unctime(un_buf); + if (ddp->dd_ddate < 0) + return(-1); + return(0); +} diff --git a/sbin/dump/main.c b/sbin/dump/main.c new file mode 100644 index 0000000..40d668e --- /dev/null +++ b/sbin/dump/main.c @@ -0,0 +1,560 @@ +/*- + * 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 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 +static char sccsid[] = "@(#)main.c 8.4 (Berkeley) 4/15/94"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#ifdef sunos +#include <sys/vnode.h> + +#include <ufs/inode.h> +#include <ufs/fs.h> +#else +#include <ufs/ffs/fs.h> +#include <ufs/ufs/dinode.h> +#endif + +#include <protocols/dumprestore.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <fstab.h> +#include <signal.h> +#include <stdio.h> +#ifdef __STDC__ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#endif + +#include "dump.h" +#include "pathnames.h" + +#ifndef SBOFF +#define SBOFF (SBLOCK * DEV_BSIZE) +#endif + +int notify = 0; /* notify operator flag */ +int blockswritten = 0; /* number of blocks written on current tape */ +int tapeno = 0; /* current tape number */ +int density = 0; /* density in bytes/0.1" */ +int ntrec = NTREC; /* # tape blocks in each tape record */ +int cartridge = 0; /* Assume non-cartridge tape */ +long dev_bsize = 1; /* recalculated below */ +long blocksperfile; /* output blocks per file */ +char *host = NULL; /* remote host (if any) */ + +static long numarg __P((int, char *, long, long, int *, char ***)); +static __dead void missingarg __P((int, char *)); + +int +main(argc, argv) + int argc; + char **argv; +{ + register ino_t ino; + register int dirty; + register struct dinode *dp; + register struct fstab *dt; + register char *map; + register char *cp; + int i, anydirskipped, bflag = 0, Tflag = 0, honorlevel = 1; + ino_t maxino; + + spcl.c_date = 0; + (void)time((time_t *)&spcl.c_date); + + tsize = 0; /* Default later, based on 'c' option for cart tapes */ + 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 == 1) { + (void) fprintf(stderr, "Must specify a key.\n"); + Exit(X_ABORT); + } + argv++; + argc -= 2; + for (cp = *argv++; cp != NULL && *cp != '\0'; cp++) { + switch (*cp) { + case '-': + break; + + case 'w': + lastdump('w'); /* tell us only what has to be done */ + exit(0); + + case 'W': /* what to do */ + lastdump('W'); /* tell us state of what is done */ + exit(0); /* do nothing else */ + + case 'f': /* output file */ + if (argc < 1) + missingarg('f', "output file"); + tape = *argv++; + argc--; + break; + + case 'd': /* density, in bits per inch */ + density = numarg('d', "density", + 10L, 327670L, &argc, &argv) / 10; + if (density >= 625 && !bflag) + ntrec = HIGHDENSITYTREC; + break; + + case 's': /* tape size, feet */ + tsize = numarg('s', "size", + 1L, 0L, &argc, &argv) * 12 * 10; + break; + + case 'T': /* time of last dump */ + if (argc < 1) + missingarg('T', "time of last dump"); + spcl.c_ddate = unctime(*argv); + if (spcl.c_ddate < 0) { + (void)fprintf(stderr, "bad time \"%s\"\n", + *argv); + exit(X_ABORT); + } + Tflag = 1; + lastlevel = '?'; + argc--; + argv++; + break; + + case 'b': /* blocks per tape write */ + ntrec = numarg('b', "number of blocks per write", + 1L, 1000L, &argc, &argv); + break; + + case 'B': /* blocks per output file */ + blocksperfile = numarg('B', "number of blocks per file", + 1L, 0L, &argc, &argv); + break; + + case 'c': /* Tape is cart. not 9-track */ + cartridge = 1; + break; + + /* dump level */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + level = *cp; + break; + + case 'u': /* update /etc/dumpdates */ + uflag = 1; + break; + + case 'n': /* notify operators */ + notify = 1; + break; + + case 'h': + honorlevel = numarg('h', "honor level", + 0L, 10L, &argc, &argv); + break; + + default: + (void)fprintf(stderr, "bad key '%c'\n", *cp); + exit(X_ABORT); + } + } + if (argc < 1) { + (void)fprintf(stderr, "Must specify disk or filesystem\n"); + exit(X_ABORT); + } + 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_ABORT); + } + if (Tflag && uflag) { + (void)fprintf(stderr, + "You cannot use the T and u flags together.\n"); + exit(X_ABORT); + } + if (strcmp(tape, "-") == 0) { + pipeout++; + tape = "standard output"; + } + + if (blocksperfile) + blocksperfile = blocksperfile / ntrec * ntrec; /* round down */ + else { + /* + * 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) + */ + if (density == 0) + density = cartridge ? 100 : 160; + if (tsize == 0) + tsize = cartridge ? 1700L*120L : 2300L*120L; + } + + if (index(tape, ':')) { + host = tape; + tape = index(host, ':'); + *tape++ = '\0'; +#ifdef RDUMP + if (rmthost(host) == 0) + exit(X_ABORT); +#else + (void)fprintf(stderr, "remote dump not enabled\n"); + exit(X_ABORT); +#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); + + set_operators(); /* /etc/group snarfed */ + 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); + } + (void)strcpy(spcl.c_label, "none"); + (void)gethostname(spcl.c_host, NAMELEN); + spcl.c_level = level - '0'; + spcl.c_type = TS_TAPE; + if (!Tflag) + getdumptime(); /* /etc/dumpdates snarfed */ + + msg("Date of this level %c dump: %s", level, + spcl.c_date == 0 ? "the epoch\n" : ctime(&spcl.c_date)); + msg("Date of last level %c dump: %s", lastlevel, + spcl.c_ddate == 0 ? "the epoch\n" : ctime(&spcl.c_ddate)); + msg("Dumping %s ", 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); + + if ((diskfd = open(disk, O_RDONLY)) < 0) { + msg("Cannot open %s\n", disk); + exit(X_ABORT); + } + sync(); + sblock = (struct fs *)sblock_buf; + bread(SBOFF, (char *) sblock, SBSIZE); + if (sblock->fs_magic != FS_MAGIC) + quit("bad sblock magic number\n"); + dev_bsize = sblock->fs_fsize / fsbtodb(sblock, 1); + dev_bshift = ffs(dev_bsize) - 1; + if (dev_bsize != (1 << dev_bshift)) + quit("dev_bsize (%d) 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); +#ifdef FS_44INODEFMT + if (sblock->fs_inodefmt >= FS_44INODEFMT) + spcl.c_flags |= DR_NEWINODEFMT; +#endif + maxino = sblock->fs_ipg * sblock->fs_ncg; + mapsize = roundup(howmany(maxino, NBBY), 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; + + msg("mapping (Pass I) [regular files]\n"); + anydirskipped = mapfiles(maxino, &tapesize); + + msg("mapping (Pass II) [directories]\n"); + while (anydirskipped) { + anydirskipped = mapdirs(maxino, &tapesize); + } + + if (pipeout) { + 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 = + ( tapesize /* blocks */ + * TP_BSIZE /* bytes/block */ + * (1.0/density) /* 0.1" / byte */ + + + 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 = + ( tapesize /* blocks */ + * TP_BSIZE /* bytes / block */ + * (1.0/density) /* 0.1" / byte */ + + + 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); + } + + /* + * 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); + + 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) % NBBY) == 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); + if ((dp->di_mode & IFMT) != IFDIR) + continue; + (void)dumpino(dp, ino); + } + + msg("dumping (Pass IV) [regular files]\n"); + for (map = dumpinomap, ino = 1; ino < maxino; ino++) { + int mode; + + if (((ino - 1) % NBBY) == 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 = dp->di_mode & IFMT; + if (mode == IFDIR) + continue; + (void)dumpino(dp, ino); + } + + spcl.c_type = TS_END; + for (i = 0; i < ntrec; i++) + writeheader(maxino - 1); + if (pipeout) + msg("DUMP: %ld tape blocks\n",spcl.c_tapea); + else + msg("DUMP: %ld tape blocks on %d volumes(s)\n", + spcl.c_tapea, spcl.c_volume); + putdumptime(); + trewind(); + broadcast("DUMP IS DONE!\7\7\n"); + msg("DUMP IS DONE\n"); + Exit(X_FINOK); + /* NOTREACHED */ +} + +/* + * 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(letter, meaning, vmin, vmax, pargc, pargv) + int letter; + char *meaning; + long vmin, vmax; + int *pargc; + char ***pargv; +{ + register char *p; + long val; + char *str; + + if (--*pargc < 0) + missingarg(letter, meaning); + str = *(*pargv)++; + for (p = str; *p; p++) + if (!isdigit(*p)) + goto bad; + val = atol(str); + if (val < vmin || (vmax && val > vmax)) + goto bad; + return (val); + +bad: + (void)fprintf(stderr, "bad '%c' (%s) value \"%s\"\n", + letter, meaning, str); + exit(X_ABORT); +} + +static __dead void +missingarg(letter, meaning) + int letter; + char *meaning; +{ + + (void)fprintf(stderr, "The '%c' flag (%s) requires an argument\n", + letter, meaning); + exit(X_ABORT); +} + +void +sig(signo) + 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(cp) + char *cp; +{ + static char rawbuf[MAXPATHLEN]; + char *dp = rindex(cp, '/'); + + if (dp == NULL) + return (NULL); + *dp = '\0'; + (void)strcpy(rawbuf, cp); + *dp = '/'; + (void)strcat(rawbuf, "/r"); + (void)strcat(rawbuf, dp + 1); + return (rawbuf); +} + +#ifdef sunos +const char * +strerror(errnum) + int errnum; +{ + extern int sys_nerr; + extern const char *const sys_errlist[]; + + if (errnum < sys_nerr) + return (sys_errlist[errnum]); + else + return ("bogus errno in strerror"); +} +#endif diff --git a/sbin/dump/optr.c b/sbin/dump/optr.c new file mode 100644 index 0000000..6db0908 --- /dev/null +++ b/sbin/dump/optr.c @@ -0,0 +1,538 @@ +/*- + * Copyright (c) 1980, 1988, 1993 + * 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 char sccsid[] = "@(#)optr.c 8.2 (Berkeley) 1/6/94"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/wait.h> +#include <sys/time.h> + +#include <errno.h> +#include <fstab.h> +#include <grp.h> +#include <signal.h> +#include <stdio.h> +#ifdef __STDC__ +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#endif +#include <tzfile.h> +#ifdef __STDC__ +#include <unistd.h> +#endif +#include <utmp.h> +#ifndef __STDC__ +#include <varargs.h> +#endif + +#include "dump.h" +#include "pathnames.h" + +void alarmcatch __P((/* int, int */)); +int datesort __P((const void *, const void *)); +static void sendmes __P((char *, char *)); + +/* + * Query the operator; This previously-fascist piece of code + * no longer requires an exact response. + * It is intended to protect dump aborting by inquisitive + * people banging on the console terminal to see what is + * happening which might cause dump to croak, destroying + * a large number of hours of work. + * + * Every 2 minutes we reprint the message, alerting others + * that dump needs attention. + */ +static int timeout; +static char *attnmessage; /* attention message */ + +int +query(question) + char *question; +{ + char replybuffer[64]; + int back, errcount; + FILE *mytty; + + if ((mytty = fopen(_PATH_TTY, "r")) == NULL) + quit("fopen on %s fails: %s\n", _PATH_TTY, strerror(errno)); + attnmessage = question; + timeout = 0; + alarmcatch(); + back = -1; + errcount = 0; + do { + if (fgets(replybuffer, 63, mytty) == NULL) { + clearerr(mytty); + if (++errcount > 30) /* XXX ugly */ + quit("excessive operator query failures\n"); + } else if (replybuffer[0] == 'y' || replybuffer[0] == 'Y') { + back = 1; + } else if (replybuffer[0] == 'n' || replybuffer[0] == 'N') { + back = 0; + } else { + (void) fprintf(stderr, + " DUMP: \"Yes\" or \"No\"?\n"); + (void) fprintf(stderr, + " DUMP: %s: (\"yes\" or \"no\") ", question); + } + } while (back < 0); + + /* + * Turn off the alarm, and reset the signal to trap out.. + */ + (void) alarm(0); + if (signal(SIGALRM, sig) == SIG_IGN) + signal(SIGALRM, SIG_IGN); + (void) fclose(mytty); + return(back); +} + +char lastmsg[100]; + +/* + * Alert the console operator, and enable the alarm clock to + * sleep for 2 minutes in case nobody comes to satisfy dump + */ +void +alarmcatch() +{ + if (notify == 0) { + if (timeout == 0) + (void) fprintf(stderr, + " DUMP: %s: (\"yes\" or \"no\") ", + attnmessage); + else + msgtail("\7\7"); + } else { + if (timeout) { + msgtail("\n"); + broadcast(""); /* just print last msg */ + } + (void) fprintf(stderr," DUMP: %s: (\"yes\" or \"no\") ", + attnmessage); + } + signal(SIGALRM, alarmcatch); + (void) alarm(120); + timeout = 1; +} + +/* + * Here if an inquisitive operator interrupts the dump program + */ +void +interrupt(signo) + int signo; +{ + msg("Interrupt received.\n"); + if (query("Do you want to abort dump?")) + dumpabort(0); +} + +/* + * The following variables and routines manage alerting + * operators to the status of dump. + * This works much like wall(1) does. + */ +struct group *gp; + +/* + * Get the names from the group entry "operator" to notify. + */ +void +set_operators() +{ + if (!notify) /*not going to notify*/ + return; + gp = getgrnam(OPGRENT); + (void) endgrent(); + if (gp == NULL) { + msg("No group entry for %s.\n", OPGRENT); + notify = 0; + return; + } +} + +struct tm *localclock; + +/* + * We fork a child to do the actual broadcasting, so + * that the process control groups are not messed up + */ +void +broadcast(message) + char *message; +{ + time_t clock; + FILE *f_utmp; + struct utmp utmp; + char **np; + int pid, s; + + if (!notify || gp == NULL) + return; + + switch (pid = fork()) { + case -1: + return; + case 0: + break; + default: + while (wait(&s) != pid) + continue; + return; + } + + clock = time((time_t *)0); + localclock = localtime(&clock); + + if ((f_utmp = fopen(_PATH_UTMP, "r")) == NULL) { + msg("Cannot open %s: %s\n", _PATH_UTMP, strerror(errno)); + return; + } + + while (!feof(f_utmp)) { + if (fread((char *) &utmp, sizeof (struct utmp), 1, f_utmp) != 1) + break; + if (utmp.ut_name[0] == 0) + continue; + for (np = gp->gr_mem; *np; np++) { + if (strncmp(*np, utmp.ut_name, sizeof(utmp.ut_name)) != 0) + continue; + /* + * Do not send messages to operators on dialups + */ + if (strncmp(utmp.ut_line, DIALUP, strlen(DIALUP)) == 0) + continue; +#ifdef DEBUG + msg("Message to %s at %s\n", *np, utmp.ut_line); +#endif + sendmes(utmp.ut_line, message); + } + } + (void) fclose(f_utmp); + Exit(0); /* the wait in this same routine will catch this */ + /* NOTREACHED */ +} + +static void +sendmes(tty, message) + char *tty, *message; +{ + char t[50], buf[BUFSIZ]; + register char *cp; + int lmsg = 1; + FILE *f_tty; + + (void) strcpy(t, _PATH_DEV); + (void) strcat(t, tty); + + if ((f_tty = fopen(t, "w")) != NULL) { + setbuf(f_tty, buf); + (void) fprintf(f_tty, + "\n\ +\7\7\7Message from the dump program to all operators at %d:%02d ...\r\n\n\ +DUMP: NEEDS ATTENTION: ", + localclock->tm_hour, localclock->tm_min); + for (cp = lastmsg; ; cp++) { + if (*cp == '\0') { + if (lmsg) { + cp = message; + if (*cp == '\0') + break; + lmsg = 0; + } else + break; + } + if (*cp == '\n') + (void) putc('\r', f_tty); + (void) putc(*cp, f_tty); + } + (void) fclose(f_tty); + } +} + +/* + * print out an estimate of the amount of time left to do the dump + */ + +time_t tschedule = 0; + +void +timeest() +{ + time_t tnow, deltat; + + (void) time((time_t *) &tnow); + if (tnow >= tschedule) { + tschedule = tnow + 300; + if (blockswritten < 500) + return; + deltat = tstart_writing - tnow + + (1.0 * (tnow - tstart_writing)) + / blockswritten * tapesize; + msg("%3.2f%% done, finished in %d:%02d\n", + (blockswritten * 100.0) / tapesize, + deltat / 3600, (deltat % 3600) / 60); + } +} + +void +#if __STDC__ +msg(const char *fmt, ...) +#else +msg(fmt, va_alist) + char *fmt; + va_dcl +#endif +{ + va_list ap; + + (void) fprintf(stderr," DUMP: "); +#ifdef TDEBUG + (void) fprintf(stderr, "pid=%d ", getpid()); +#endif +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + (void) vfprintf(stderr, fmt, ap); + (void) fflush(stdout); + (void) fflush(stderr); + (void) vsprintf(lastmsg, fmt, ap); + va_end(ap); +} + +void +#if __STDC__ +msgtail(const char *fmt, ...) +#else +msgtail(fmt, va_alist) + char *fmt; + va_dcl +#endif +{ + va_list ap; +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + (void) vfprintf(stderr, fmt, ap); + va_end(ap); +} + +void +#if __STDC__ +quit(const char *fmt, ...) +#else +quit(fmt, va_alist) + char *fmt; + va_dcl +#endif +{ + va_list ap; + + (void) fprintf(stderr," DUMP: "); +#ifdef TDEBUG + (void) fprintf(stderr, "pid=%d ", getpid()); +#endif +#if __STDC__ + va_start(ap, fmt); +#else + va_start(ap); +#endif + (void) vfprintf(stderr, fmt, ap); + va_end(ap); + (void) fflush(stdout); + (void) fflush(stderr); + dumpabort(0); +} + +/* + * Tell the operator what has to be done; + * we don't actually do it + */ + +struct fstab * +allocfsent(fs) + register struct fstab *fs; +{ + register struct fstab *new; + + new = (struct fstab *)malloc(sizeof (*fs)); + if (new == NULL || + (new->fs_file = strdup(fs->fs_file)) == NULL || + (new->fs_type = strdup(fs->fs_type)) == NULL || + (new->fs_spec = strdup(fs->fs_spec)) == NULL) + quit("%s\n", strerror(errno)); + new->fs_passno = fs->fs_passno; + new->fs_freq = fs->fs_freq; + return (new); +} + +struct pfstab { + struct pfstab *pf_next; + struct fstab *pf_fstab; +}; + +static struct pfstab *table; + +void +getfstab() +{ + register struct fstab *fs; + register struct pfstab *pf; + + if (setfsent() == 0) { + msg("Can't open %s for dump table information: %s\n", + _PATH_FSTAB, strerror(errno)); + return; + } + while ((fs = getfsent()) != NULL) { + if (strcmp(fs->fs_type, FSTAB_RW) && + strcmp(fs->fs_type, FSTAB_RO) && + strcmp(fs->fs_type, FSTAB_RQ)) + continue; + fs = allocfsent(fs); + if ((pf = (struct pfstab *)malloc(sizeof (*pf))) == NULL) + quit("%s\n", strerror(errno)); + pf->pf_fstab = fs; + pf->pf_next = table; + table = pf; + } + (void) endfsent(); +} + +/* + * Search in the fstab for a file name. + * This file name can be either the special or the path file name. + * + * The entries in the fstab are the BLOCK special names, not the + * character special names. + * The caller of fstabsearch assures that the character device + * is dumped (that is much faster) + * + * The file name can omit the leading '/'. + */ +struct fstab * +fstabsearch(key) + char *key; +{ + register struct pfstab *pf; + register struct fstab *fs; + char *rn; + + for (pf = table; pf != NULL; pf = pf->pf_next) { + fs = pf->pf_fstab; + if (strcmp(fs->fs_file, key) == 0 || + strcmp(fs->fs_spec, key) == 0) + return (fs); + rn = rawname(fs->fs_spec); + if (rn != NULL && strcmp(rn, key) == 0) + return (fs); + if (key[0] != '/') { + if (*fs->fs_spec == '/' && + strcmp(fs->fs_spec + 1, key) == 0) + return (fs); + if (*fs->fs_file == '/' && + strcmp(fs->fs_file + 1, key) == 0) + return (fs); + } + } + return (NULL); +} + +/* + * Tell the operator what to do + */ +void +lastdump(arg) + char arg; /* w ==> just what to do; W ==> most recent dumps */ +{ + register int i; + register struct fstab *dt; + register struct dumpdates *dtwalk; + char *lastname, *date; + int dumpme; + time_t tnow; + + (void) time(&tnow); + getfstab(); /* /etc/fstab input */ + initdumptimes(); /* /etc/dumpdates input */ + qsort((char *) ddatev, nddates, sizeof(struct dumpdates *), datesort); + + if (arg == 'w') + (void) printf("Dump these file systems:\n"); + else + (void) printf("Last dump(s) done (Dump '>' file systems):\n"); + lastname = "??"; + ITITERATE(i, dtwalk) { + if (strncmp(lastname, dtwalk->dd_name, + sizeof(dtwalk->dd_name)) == 0) + continue; + date = (char *)ctime(&dtwalk->dd_ddate); + date[16] = '\0'; /* blast away seconds and year */ + lastname = dtwalk->dd_name; + dt = fstabsearch(dtwalk->dd_name); + dumpme = (dt != NULL && + dt->fs_freq != 0 && + dtwalk->dd_ddate < tnow - (dt->fs_freq * SECSPERDAY)); + if (arg != 'w' || dumpme) + (void) printf( + "%c %8s\t(%6s) Last dump: Level %c, Date %s\n", + dumpme && (arg != 'w') ? '>' : ' ', + dtwalk->dd_name, + dt ? dt->fs_file : "", + dtwalk->dd_level, + date); + } +} + +int +datesort(a1, a2) + const void *a1, *a2; +{ + struct dumpdates *d1 = *(struct dumpdates **)a1; + struct dumpdates *d2 = *(struct dumpdates **)a2; + int diff; + + diff = strncmp(d1->dd_name, d2->dd_name, sizeof(d1->dd_name)); + if (diff == 0) + return (d2->dd_ddate - d1->dd_ddate); + return (diff); +} diff --git a/sbin/dump/pathnames.h b/sbin/dump/pathnames.h new file mode 100644 index 0000000..e4dff53 --- /dev/null +++ b/sbin/dump/pathnames.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 1989, 1993 + * 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. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/5/93 + */ + +#include <paths.h> + +#define _PATH_DEFTAPE "/dev/rmt8" +#define _PATH_DTMP "/etc/dtmp" +#define _PATH_DUMPDATES "/etc/dumpdates" +#define _PATH_LOCK "/tmp/dumplockXXXXXX" +#define _PATH_RMT "rmt" diff --git a/sbin/dump/tape.c b/sbin/dump/tape.c new file mode 100644 index 0000000..f6a4e5b --- /dev/null +++ b/sbin/dump/tape.c @@ -0,0 +1,859 @@ +/*- + * Copyright (c) 1980, 1991, 1993 + * 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 char sccsid[] = "@(#)tape.c 8.2 (Berkeley) 3/17/94"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/wait.h> +#ifdef sunos +#include <sys/vnode.h> + +#include <ufs/fs.h> +#include <ufs/inode.h> +#else +#include <ufs/ffs/fs.h> +#include <ufs/ufs/dinode.h> +#endif + +#include <protocols/dumprestore.h> + +#include <errno.h> +#include <fcntl.h> +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#ifdef __STDC__ +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#else +int write(), read(); +#endif + +#include "dump.h" +#include "pathnames.h" + +int writesize; /* size of malloc()ed buffer for tape */ +long lastspclrec = -1; /* tape block number of last written header */ +int trecno = 0; /* next record to write in current block */ +extern long blocksperfile; /* number of blocks per output file */ +long blocksthisvol; /* number of blocks on current output file */ +extern int ntrec; /* blocking factor on tape */ +extern int cartridge; +extern char *host; +char *nexttape; + +static int atomic __P((int (*)(), int, char *, int)); +static void doslave __P((int, int)); +static void enslave __P((void)); +static void flushtape __P((void)); +static void killall __P((void)); +static void rollforward __P((void)); + +/* + * Concurrent dump mods (Caltech) - disk block reading and tape writing + * are exported to several slave processes. While one slave writes the + * tape, the others read disk blocks; they pass control of the tape in + * a ring via signals. The parent process traverses the filesystem and + * sends writeheader()'s and lists of daddr's to the slaves via pipes. + * The following structure defines the instruction packets sent to slaves. + */ +struct req { + daddr_t dblk; + int count; +}; +int reqsiz; + +#define SLAVES 3 /* 1 slave writing, 1 reading, 1 for slack */ +struct slave { + int tapea; /* header number at start of this chunk */ + int count; /* count to next header (used for TS_TAPE */ + /* after EOT) */ + int inode; /* inode that we are currently dealing with */ + int fd; /* FD for this slave */ + int pid; /* PID for this slave */ + int sent; /* 1 == we've sent this slave requests */ + int firstrec; /* record number of this block */ + char (*tblock)[TP_BSIZE]; /* buffer for data blocks */ + struct req *req; /* buffer for requests */ +} slaves[SLAVES+1]; +struct slave *slp; + +char (*nextblock)[TP_BSIZE]; + +int master; /* pid of master, for sending error signals */ +int tenths; /* length of tape used per block written */ +static int caught; /* have we caught the signal to proceed? */ +static int ready; /* have we reached the lock point without having */ + /* received the SIGUSR2 signal from the prev slave? */ +static jmp_buf jmpbuf; /* where to jump to if we are ready when the */ + /* SIGUSR2 arrives from the previous slave */ + +int +alloctape() +{ + int pgoff = getpagesize() - 1; + char *buf; + int i; + + writesize = ntrec * TP_BSIZE; + reqsiz = (ntrec + 1) * sizeof(struct req); + /* + * CDC 92181's and 92185's make 0.8" gaps in 1600-bpi start/stop mode + * (see DEC TU80 User's Guide). The shorter gaps of 6250-bpi require + * repositioning after stopping, i.e, streaming mode, where the gap is + * variable, 0.30" to 0.45". The gap is maximal when the tape stops. + */ + if (blocksperfile == 0) + tenths = writesize / density + + (cartridge ? 16 : density == 625 ? 5 : 8); + /* + * Allocate tape buffer contiguous with the array of instruction + * packets, so flushtape() can write them together with one write(). + * Align tape buffer on page boundary to speed up tape write(). + */ + for (i = 0; i <= SLAVES; i++) { + buf = (char *) + malloc((unsigned)(reqsiz + writesize + pgoff + TP_BSIZE)); + if (buf == NULL) + return(0); + slaves[i].tblock = (char (*)[TP_BSIZE]) + (((long)&buf[ntrec + 1] + pgoff) &~ pgoff); + slaves[i].req = (struct req *)slaves[i].tblock - ntrec - 1; + } + slp = &slaves[0]; + slp->count = 1; + slp->tapea = 0; + slp->firstrec = 0; + nextblock = slp->tblock; + return(1); +} + +void +writerec(dp, isspcl) + char *dp; + int isspcl; +{ + + slp->req[trecno].dblk = (daddr_t)0; + slp->req[trecno].count = 1; + *(union u_spcl *)(*(nextblock)++) = *(union u_spcl *)dp; + if (isspcl) + lastspclrec = spcl.c_tapea; + trecno++; + spcl.c_tapea++; + if (trecno >= ntrec) + flushtape(); +} + +void +dumpblock(blkno, size) + daddr_t blkno; + int size; +{ + int avail, tpblks, dblkno; + + dblkno = fsbtodb(sblock, blkno); + tpblks = size >> tp_bshift; + while ((avail = MIN(tpblks, ntrec - trecno)) > 0) { + slp->req[trecno].dblk = dblkno; + slp->req[trecno].count = avail; + trecno += avail; + spcl.c_tapea += avail; + if (trecno >= ntrec) + flushtape(); + dblkno += avail << (tp_bshift - dev_bshift); + tpblks -= avail; + } +} + +int nogripe = 0; + +void +tperror(signo) + int signo; +{ + + if (pipeout) { + msg("write error on %s\n", tape); + quit("Cannot recover\n"); + /* NOTREACHED */ + } + msg("write error %d blocks into volume %d\n", blocksthisvol, tapeno); + broadcast("DUMP WRITE ERROR!\n"); + if (!query("Do you want to restart?")) + dumpabort(0); + msg("Closing this volume. Prepare to restart with new media;\n"); + msg("this dump volume will be rewritten.\n"); + killall(); + nogripe = 1; + close_rewind(); + Exit(X_REWRITE); +} + +void +sigpipe(signo) + int signo; +{ + + quit("Broken pipe\n"); +} + +static void +flushtape() +{ + int i, blks, got; + long lastfirstrec; + + int siz = (char *)nextblock - (char *)slp->req; + + slp->req[trecno].count = 0; /* Sentinel */ + + if (atomic(write, slp->fd, (char *)slp->req, siz) != siz) + quit("error writing command pipe: %s\n", strerror(errno)); + slp->sent = 1; /* we sent a request, read the response later */ + + lastfirstrec = slp->firstrec; + + if (++slp >= &slaves[SLAVES]) + slp = &slaves[0]; + + /* Read results back from next slave */ + if (slp->sent) { + if (atomic(read, slp->fd, (char *)&got, sizeof got) + != sizeof got) { + perror(" DUMP: error reading command pipe in master"); + dumpabort(0); + } + slp->sent = 0; + + /* Check for end of tape */ + if (got < writesize) { + msg("End of tape detected\n"); + + /* + * Drain the results, don't care what the values were. + * If we read them here then trewind won't... + */ + for (i = 0; i < SLAVES; i++) { + if (slaves[i].sent) { + if (atomic(read, slaves[i].fd, + (char *)&got, sizeof got) + != sizeof got) { + perror(" DUMP: error reading command pipe in master"); + dumpabort(0); + } + slaves[i].sent = 0; + } + } + + close_rewind(); + rollforward(); + return; + } + } + + blks = 0; + if (spcl.c_type != TS_END) { + for (i = 0; i < spcl.c_count; i++) + if (spcl.c_addr[i] != 0) + blks++; + } + slp->count = lastspclrec + blks + 1 - spcl.c_tapea; + slp->tapea = spcl.c_tapea; + slp->firstrec = lastfirstrec + ntrec; + slp->inode = curino; + nextblock = slp->tblock; + trecno = 0; + asize += tenths; + blockswritten += ntrec; + blocksthisvol += ntrec; + if (!pipeout && (blocksperfile ? + (blocksthisvol >= blocksperfile) : (asize > tsize))) { + close_rewind(); + startnewtape(0); + } + timeest(); +} + +void +trewind() +{ + int f; + int got; + + for (f = 0; f < SLAVES; f++) { + /* + * Drain the results, but unlike EOT we DO (or should) care + * what the return values were, since if we detect EOT after + * we think we've written the last blocks to the tape anyway, + * we have to replay those blocks with rollforward. + * + * fixme: punt for now. + */ + if (slaves[f].sent) { + if (atomic(read, slaves[f].fd, (char *)&got, sizeof got) + != sizeof got) { + perror(" DUMP: error reading command pipe in master"); + dumpabort(0); + } + slaves[f].sent = 0; + if (got != writesize) { + msg("EOT detected in last 2 tape records!\n"); + msg("Use a longer tape, decrease the size estimate\n"); + quit("or use no size estimate at all.\n"); + } + } + (void) close(slaves[f].fd); + } + while (wait((int *)NULL) >= 0) /* wait for any signals from slaves */ + /* void */; + + if (pipeout) + return; + + msg("Closing %s\n", tape); + +#ifdef RDUMP + if (host) { + rmtclose(); + while (rmtopen(tape, 0) < 0) + sleep(10); + rmtclose(); + return; + } +#endif + (void) close(tapefd); + while ((f = open(tape, 0)) < 0) + sleep (10); + (void) close(f); +} + +void +close_rewind() +{ + trewind(); + if (nexttape) + return; + if (!nogripe) { + msg("Change Volumes: Mount volume #%d\n", tapeno+1); + broadcast("CHANGE DUMP VOLUMES!\7\7\n"); + } + while (!query("Is the new volume mounted and ready to go?")) + if (query("Do you want to abort?")) { + dumpabort(0); + /*NOTREACHED*/ + } +} + +void +rollforward() +{ + register struct req *p, *q, *prev; + register struct slave *tslp; + int i, size, savedtapea, got; + union u_spcl *ntb, *otb; + tslp = &slaves[SLAVES]; + ntb = (union u_spcl *)tslp->tblock[1]; + + /* + * Each of the N slaves should have requests that need to + * be replayed on the next tape. Use the extra slave buffers + * (slaves[SLAVES]) to construct request lists to be sent to + * each slave in turn. + */ + for (i = 0; i < SLAVES; i++) { + q = &tslp->req[1]; + otb = (union u_spcl *)slp->tblock; + + /* + * For each request in the current slave, copy it to tslp. + */ + + prev = NULL; + for (p = slp->req; p->count > 0; p += p->count) { + *q = *p; + if (p->dblk == 0) + *ntb++ = *otb++; /* copy the datablock also */ + prev = q; + q += q->count; + } + if (prev == NULL) + quit("rollforward: protocol botch"); + if (prev->dblk != 0) + prev->count -= 1; + else + ntb--; + q -= 1; + q->count = 0; + q = &tslp->req[0]; + if (i == 0) { + q->dblk = 0; + q->count = 1; + trecno = 0; + nextblock = tslp->tblock; + savedtapea = spcl.c_tapea; + spcl.c_tapea = slp->tapea; + startnewtape(0); + spcl.c_tapea = savedtapea; + lastspclrec = savedtapea - 1; + } + size = (char *)ntb - (char *)q; + if (atomic(write, slp->fd, (char *)q, size) != size) { + perror(" DUMP: error writing command pipe"); + dumpabort(0); + } + slp->sent = 1; + if (++slp >= &slaves[SLAVES]) + slp = &slaves[0]; + + q->count = 1; + + if (prev->dblk != 0) { + /* + * If the last one was a disk block, make the + * first of this one be the last bit of that disk + * block... + */ + q->dblk = prev->dblk + + prev->count * (TP_BSIZE / DEV_BSIZE); + ntb = (union u_spcl *)tslp->tblock; + } else { + /* + * It wasn't a disk block. Copy the data to its + * new location in the buffer. + */ + q->dblk = 0; + *((union u_spcl *)tslp->tblock) = *ntb; + ntb = (union u_spcl *)tslp->tblock[1]; + } + } + slp->req[0] = *q; + nextblock = slp->tblock; + if (q->dblk == 0) + nextblock++; + trecno = 1; + + /* + * Clear the first slaves' response. One hopes that it + * worked ok, otherwise the tape is much too short! + */ + if (slp->sent) { + if (atomic(read, slp->fd, (char *)&got, sizeof got) + != sizeof got) { + perror(" DUMP: error reading command pipe in master"); + dumpabort(0); + } + slp->sent = 0; + + if (got != writesize) { + quit("EOT detected at start of the tape!\n"); + } + } +} + +/* + * 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 +startnewtape(top) + int top; +{ + int parentpid; + int childpid; + int status; + int waitpid; + char *p; +#ifdef sunos + void (*interrupt_save)(); +#else + sig_t interrupt_save; +#endif + + interrupt_save = signal(SIGINT, SIG_IGN); + parentpid = getpid(); + +restore_check_point: + (void)signal(SIGINT, interrupt_save); + /* + * All signals are inherited... + */ + childpid = fork(); + if (childpid < 0) { + msg("Context save fork fails in parent %d\n", parentpid); + Exit(X_ABORT); + } + if (childpid != 0) { + /* + * PARENT: + * save the context by waiting + * until the child doing all of the work returns. + * don't catch the interrupt + */ + signal(SIGINT, SIG_IGN); +#ifdef TDEBUG + msg("Tape: %d; parent process: %d child process %d\n", + tapeno+1, parentpid, childpid); +#endif /* TDEBUG */ + while ((waitpid = wait(&status)) != childpid) + msg("Parent %d waiting for child %d has another child %d return\n", + parentpid, childpid, waitpid); + if (status & 0xFF) { + msg("Child %d returns LOB status %o\n", + childpid, status&0xFF); + } + status = (status >> 8) & 0xFF; +#ifdef TDEBUG + switch(status) { + case X_FINOK: + msg("Child %d finishes X_FINOK\n", childpid); + break; + case X_ABORT: + msg("Child %d finishes X_ABORT\n", childpid); + break; + case X_REWRITE: + msg("Child %d finishes X_REWRITE\n", childpid); + break; + default: + msg("Child %d finishes unknown %d\n", + childpid, status); + break; + } +#endif /* TDEBUG */ + switch(status) { + case X_FINOK: + Exit(X_FINOK); + case X_ABORT: + Exit(X_ABORT); + case X_REWRITE: + goto restore_check_point; + default: + msg("Bad return code from dump: %d\n", status); + Exit(X_ABORT); + } + /*NOTREACHED*/ + } else { /* we are the child; just continue */ +#ifdef TDEBUG + sleep(4); /* allow time for parent's message to get out */ + msg("Child on Tape %d has parent %d, my pid = %d\n", + tapeno+1, parentpid, getpid()); +#endif /* TDEBUG */ + /* + * If we have a name like "/dev/rmt0,/dev/rmt1", + * use the name before the comma first, and save + * the remaining names for subsequent volumes. + */ + tapeno++; /* current tape sequence */ + if (nexttape || index(tape, ',')) { + if (nexttape && *nexttape) + tape = nexttape; + if ((p = index(tape, ',')) != NULL) { + *p = '\0'; + nexttape = p + 1; + } else + nexttape = NULL; + msg("Dumping volume %d on %s\n", tapeno, tape); + } +#ifdef RDUMP + while ((tapefd = (host ? rmtopen(tape, 2) : + pipeout ? 1 : open(tape, O_WRONLY|O_CREAT, 0666))) < 0) +#else + while ((tapefd = (pipeout ? 1 : + open(tape, O_WRONLY|O_CREAT, 0666))) < 0) +#endif + { + msg("Cannot open output \"%s\".\n", tape); + if (!query("Do you want to retry the open?")) + dumpabort(0); + } + + enslave(); /* Share open tape file descriptor with slaves */ + + asize = 0; + blocksthisvol = 0; + if (top) + newtape++; /* new tape signal */ + spcl.c_count = slp->count; + /* + * measure firstrec in TP_BSIZE units since restore doesn't + * know the correct ntrec value... + */ + spcl.c_firstrec = slp->firstrec; + spcl.c_volume++; + spcl.c_type = TS_TAPE; + spcl.c_flags |= DR_NEWHEADER; + writeheader((ino_t)slp->inode); + spcl.c_flags &=~ DR_NEWHEADER; + if (tapeno > 1) + msg("Volume %d begins with blocks from inode %d\n", + tapeno, slp->inode); + } +} + +void +dumpabort(signo) + int signo; +{ + + if (master != 0 && master != getpid()) + /* Signals master to call dumpabort */ + (void) kill(master, SIGTERM); + else { + killall(); + msg("The ENTIRE dump is aborted.\n"); + } +#ifdef RDUMP + rmtclose(); +#endif + Exit(X_ABORT); +} + +__dead void +Exit(status) + int status; +{ + +#ifdef TDEBUG + msg("pid = %d exits with status %d\n", getpid(), status); +#endif /* TDEBUG */ + exit(status); +} + +/* + * proceed - handler for SIGUSR2, used to synchronize IO between the slaves. + */ +void +proceed(signo) + int signo; +{ + + if (ready) + longjmp(jmpbuf, 1); + caught++; +} + +void +enslave() +{ + int cmd[2]; + register int i, j; + + master = getpid(); + + signal(SIGTERM, dumpabort); /* Slave sends SIGTERM on dumpabort() */ + signal(SIGPIPE, sigpipe); + signal(SIGUSR1, tperror); /* Slave sends SIGUSR1 on tape errors */ + signal(SIGUSR2, proceed); /* Slave sends SIGUSR2 to next slave */ + + for (i = 0; i < SLAVES; i++) { + if (i == slp - &slaves[0]) { + caught = 1; + } else { + caught = 0; + } + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, cmd) < 0 || + (slaves[i].pid = fork()) < 0) + quit("too many slaves, %d (recompile smaller): %s\n", + i, strerror(errno)); + + slaves[i].fd = cmd[1]; + slaves[i].sent = 0; + if (slaves[i].pid == 0) { /* Slave starts up here */ + for (j = 0; j <= i; j++) + (void) close(slaves[j].fd); + signal(SIGINT, SIG_IGN); /* Master handles this */ + doslave(cmd[0], i); + Exit(X_FINOK); + } + } + + for (i = 0; i < SLAVES; i++) + (void) atomic(write, slaves[i].fd, + (char *) &slaves[(i + 1) % SLAVES].pid, + sizeof slaves[0].pid); + + master = 0; +} + +void +killall() +{ + register int i; + + for (i = 0; i < SLAVES; i++) + if (slaves[i].pid > 0) + (void) kill(slaves[i].pid, SIGKILL); +} + +/* + * Synchronization - each process has a lockfile, and shares file + * descriptors to the following process's lockfile. When our write + * completes, we release our lock on the following process's lock- + * file, allowing the following process to lock it and proceed. We + * get the lock back for the next cycle by swapping descriptors. + */ +static void +doslave(cmd, slave_number) + register int cmd; + int slave_number; +{ + register int nread; + int nextslave, size, wrote, eot_count; + + /* + * Need our own seek pointer. + */ + (void) close(diskfd); + if ((diskfd = open(disk, O_RDONLY)) < 0) + quit("slave couldn't reopen disk: %s\n", strerror(errno)); + + /* + * Need the pid of the next slave in the loop... + */ + if ((nread = atomic(read, cmd, (char *)&nextslave, sizeof nextslave)) + != sizeof nextslave) { + quit("master/slave protocol botched - didn't get pid of next slave.\n"); + } + + /* + * Get list of blocks to dump, read the blocks into tape buffer + */ + while ((nread = atomic(read, cmd, (char *)slp->req, reqsiz)) == reqsiz) { + register struct req *p = slp->req; + + for (trecno = 0; trecno < ntrec; + trecno += p->count, p += p->count) { + if (p->dblk) { + bread(p->dblk, slp->tblock[trecno], + p->count * TP_BSIZE); + } else { + if (p->count != 1 || atomic(read, cmd, + (char *)slp->tblock[trecno], + TP_BSIZE) != TP_BSIZE) + quit("master/slave protocol botched.\n"); + } + } + if (setjmp(jmpbuf) == 0) { + ready = 1; + if (!caught) + (void) pause(); + } + ready = 0; + caught = 0; + + /* Try to write the data... */ + eot_count = 0; + size = 0; + + while (eot_count < 10 && size < writesize) { +#ifdef RDUMP + if (host) + wrote = rmtwrite(slp->tblock[0]+size, + writesize-size); + else +#endif + wrote = write(tapefd, slp->tblock[0]+size, + writesize-size); +#ifdef WRITEDEBUG + printf("slave %d wrote %d\n", slave_number, wrote); +#endif + if (wrote < 0) + break; + if (wrote == 0) + eot_count++; + size += wrote; + } + +#ifdef WRITEDEBUG + if (size != writesize) + printf("slave %d only wrote %d out of %d bytes and gave up.\n", + slave_number, size, writesize); +#endif + + if (eot_count > 0) + size = 0; + + /* + * fixme: Pyramids running OSx return ENOSPC + * at EOT on 1/2 inch drives. + */ + if (size < 0) { + (void) kill(master, SIGUSR1); + for (;;) + (void) sigpause(0); + } else { + /* + * pass size of write back to master + * (for EOT handling) + */ + (void) atomic(write, cmd, (char *)&size, sizeof size); + } + + /* + * If partial write, don't want next slave to go. + * Also jolts him awake. + */ + (void) kill(nextslave, SIGUSR2); + } + if (nread != 0) + quit("error reading command pipe: %s\n", strerror(errno)); +} + +/* + * 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 int +atomic(func, fd, buf, count) + int (*func)(), fd; + char *buf; + int count; +{ + int got, need = count; + + while ((got = (*func)(fd, buf, need)) > 0 && (need -= got) > 0) + buf += got; + return (got < 0 ? got : count - need); +} diff --git a/sbin/dump/traverse.c b/sbin/dump/traverse.c new file mode 100644 index 0000000..ce70050 --- /dev/null +++ b/sbin/dump/traverse.c @@ -0,0 +1,613 @@ +/*- + * Copyright (c) 1980, 1988, 1991, 1993 + * 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 char sccsid[] = "@(#)traverse.c 8.2 (Berkeley) 9/23/93"; +#endif /* not lint */ + +#include <sys/param.h> +#include <sys/time.h> +#include <sys/stat.h> +#ifdef sunos +#include <sys/vnode.h> + +#include <ufs/fs.h> +#include <ufs/fsdir.h> +#include <ufs/inode.h> +#else +#include <ufs/ffs/fs.h> +#include <ufs/ufs/dir.h> +#include <ufs/ufs/dinode.h> +#endif + +#include <protocols/dumprestore.h> + +#include <ctype.h> +#include <stdio.h> +#ifdef __STDC__ +#include <string.h> +#include <unistd.h> +#endif + +#include "dump.h" + +#define HASDUMPEDFILE 0x1 +#define HASSUBDIRS 0x2 + +#ifdef FS_44INODEFMT +typedef quad_t fsizeT; +#else +typedef long fsizeT; +#endif + +static int dirindir __P((ino_t ino, daddr_t blkno, int level, long *size)); +static void dmpindir __P((ino_t ino, daddr_t blk, int level, fsizeT *size)); +static int searchdir __P((ino_t ino, daddr_t blkno, long size, long filesize)); + +/* + * This is an estimation of the number of TP_BSIZE blocks in the file. + * It estimates the number of blocks in files with holes by assuming + * that all of the blocks accounted for by di_blocks are data blocks + * (when some of the blocks are usually used for indirect pointers); + * hence the estimate may be high. + */ +long +blockest(dp) + register struct dinode *dp; +{ + long blkest, sizeest; + + /* + * dp->di_size is the size of the file in bytes. + * dp->di_blocks stores the number of sectors actually in the file. + * If there are more sectors than the size would indicate, this just + * means that there are indirect blocks in the file or unused + * sectors in the last file block; we can safely ignore these + * (blkest = sizeest below). + * If the file is bigger than the number of sectors would indicate, + * then the file has holes in it. In this case we must use the + * block count to estimate the number of data blocks used, but + * we use the actual size for estimating the number of indirect + * dump blocks (sizeest vs. blkest in the indirect block + * calculation). + */ + blkest = howmany(dbtob(dp->di_blocks), TP_BSIZE); + sizeest = howmany(dp->di_size, TP_BSIZE); + if (blkest > sizeest) + blkest = sizeest; + if (dp->di_size > sblock->fs_bsize * NDADDR) { + /* calculate the number of indirect blocks on the dump tape */ + blkest += + howmany(sizeest - NDADDR * sblock->fs_bsize / TP_BSIZE, + TP_NINDIR); + } + return (blkest + 1); +} + +/* Auxiliary macro to pick up files changed since previous dump. */ +#ifdef FS_44INODEFMT +#define CHANGEDSINCE(dp, t) \ + ((dp)->di_mtime.ts_sec >= (t) || (dp)->di_ctime.ts_sec >= (t)) +#else +#define CHANGEDSINCE(dp, t) \ + ((dp)->di_mtime >= (t) || (dp)->di_ctime >= (t)) +#endif + +/* The WANTTODUMP macro decides whether a file should be dumped. */ +#ifdef UF_NODUMP +#define WANTTODUMP(dp) \ + (CHANGEDSINCE(dp, spcl.c_ddate) && \ + (nonodump || ((dp)->di_flags & UF_NODUMP) != UF_NODUMP)) +#else +#define WANTTODUMP(dp) CHANGEDSINCE(dp, spcl.c_ddate) +#endif + +/* + * Dump pass 1. + * + * Walk the inode list for a filesystem to find all allocated inodes + * that have been modified since the previous dump time. Also, find all + * the directories in the filesystem. + */ +int +mapfiles(maxino, tapesize) + ino_t maxino; + long *tapesize; +{ + register int mode; + register ino_t ino; + register struct dinode *dp; + int anydirskipped = 0; + + for (ino = ROOTINO; ino < maxino; ino++) { + dp = getino(ino); + if ((mode = (dp->di_mode & IFMT)) == 0) + continue; + SETINO(ino, usedinomap); + if (mode == IFDIR) + SETINO(ino, dumpdirmap); + if (WANTTODUMP(dp)) { + SETINO(ino, dumpinomap); + if (mode != IFREG && mode != IFDIR && mode != IFLNK) + *tapesize += 1; + else + *tapesize += blockest(dp); + continue; + } + if (mode == IFDIR) + anydirskipped = 1; + } + /* + * Restore gets very upset if the root is not dumped, + * so ensure that it always is dumped. + */ + SETINO(ROOTINO, dumpinomap); + return (anydirskipped); +} + +/* + * Dump pass 2. + * + * Scan each directory on the filesystem to see if it has any modified + * files in it. If it does, and has not already been added to the dump + * list (because it was itself modified), then add it. If a directory + * has not been modified itself, contains no modified files and has no + * subdirectories, then it can be deleted from the dump list and from + * the list of directories. By deleting it from the list of directories, + * its parent may now qualify for the same treatment on this or a later + * pass using this algorithm. + */ +int +mapdirs(maxino, tapesize) + ino_t maxino; + long *tapesize; +{ + register struct dinode *dp; + register int i, isdir; + register char *map; + register ino_t ino; + long filesize; + int ret, change = 0; + + isdir = 0; /* XXX just to get gcc to shut up */ + for (map = dumpdirmap, ino = 1; ino < maxino; ino++) { + if (((ino - 1) % NBBY) == 0) /* map is offset by 1 */ + isdir = *map++; + else + isdir >>= 1; + if ((isdir & 1) == 0 || TSTINO(ino, dumpinomap)) + continue; + dp = getino(ino); + filesize = dp->di_size; + for (ret = 0, i = 0; filesize > 0 && i < NDADDR; i++) { + if (dp->di_db[i] != 0) + ret |= searchdir(ino, dp->di_db[i], + (long)dblksize(sblock, dp, i), + filesize); + if (ret & HASDUMPEDFILE) + filesize = 0; + else + filesize -= sblock->fs_bsize; + } + for (i = 0; filesize > 0 && i < NIADDR; i++) { + if (dp->di_ib[i] == 0) + continue; + ret |= dirindir(ino, dp->di_ib[i], i, &filesize); + } + if (ret & HASDUMPEDFILE) { + SETINO(ino, dumpinomap); + *tapesize += blockest(dp); + change = 1; + continue; + } + if ((ret & HASSUBDIRS) == 0) { + if (!TSTINO(ino, dumpinomap)) { + CLRINO(ino, dumpdirmap); + change = 1; + } + } + } + return (change); +} + +/* + * Read indirect blocks, and pass the data blocks to be searched + * as directories. Quit as soon as any entry is found that will + * require the directory to be dumped. + */ +static int +dirindir(ino, blkno, ind_level, filesize) + ino_t ino; + daddr_t blkno; + int ind_level; + long *filesize; +{ + int ret = 0; + register int i; + daddr_t idblk[MAXNINDIR]; + + bread(fsbtodb(sblock, blkno), (char *)idblk, (int)sblock->fs_bsize); + if (ind_level <= 0) { + for (i = 0; *filesize > 0 && i < NINDIR(sblock); i++) { + blkno = idblk[i]; + if (blkno != 0) + ret |= searchdir(ino, blkno, sblock->fs_bsize, + *filesize); + if (ret & HASDUMPEDFILE) + *filesize = 0; + else + *filesize -= sblock->fs_bsize; + } + return (ret); + } + ind_level--; + for (i = 0; *filesize > 0 && i < NINDIR(sblock); i++) { + blkno = idblk[i]; + if (blkno != 0) + ret |= dirindir(ino, blkno, ind_level, filesize); + } + return (ret); +} + +/* + * Scan a disk block containing directory information looking to see if + * any of the entries are on the dump list and to see if the directory + * contains any subdirectories. + */ +static int +searchdir(ino, blkno, size, filesize) + ino_t ino; + daddr_t blkno; + register long size; + long filesize; +{ + register struct direct *dp; + register long loc, ret = 0; + char dblk[MAXBSIZE]; + + bread(fsbtodb(sblock, blkno), dblk, (int)size); + if (filesize < size) + size = filesize; + for (loc = 0; loc < size; ) { + dp = (struct direct *)(dblk + loc); + if (dp->d_reclen == 0) { + msg("corrupted directory, inumber %d\n", ino); + break; + } + loc += dp->d_reclen; + if (dp->d_ino == 0) + continue; + if (dp->d_name[0] == '.') { + if (dp->d_name[1] == '\0') + continue; + if (dp->d_name[1] == '.' && dp->d_name[2] == '\0') + continue; + } + if (TSTINO(dp->d_ino, dumpinomap)) { + ret |= HASDUMPEDFILE; + if (ret & HASSUBDIRS) + break; + } + if (TSTINO(dp->d_ino, dumpdirmap)) { + ret |= HASSUBDIRS; + if (ret & HASDUMPEDFILE) + break; + } + } + return (ret); +} + +/* + * Dump passes 3 and 4. + * + * Dump the contents of an inode to tape. + */ +void +dumpino(dp, ino) + register struct dinode *dp; + ino_t ino; +{ + int ind_level, cnt; + fsizeT size; + char buf[TP_BSIZE]; + + if (newtape) { + newtape = 0; + dumpmap(dumpinomap, TS_BITS, ino); + } + CLRINO(ino, dumpinomap); + spcl.c_dinode = *dp; + spcl.c_type = TS_INODE; + spcl.c_count = 0; + switch (dp->di_mode & S_IFMT) { + + case 0: + /* + * Freed inode. + */ + return; + + case S_IFLNK: + /* + * Check for short symbolic link. + */ +#ifdef FS_44INODEFMT + if (dp->di_size > 0 && + dp->di_size < sblock->fs_maxsymlinklen) { + spcl.c_addr[0] = 1; + spcl.c_count = 1; + writeheader(ino); + bcopy((caddr_t)dp->di_shortlink, buf, + (u_long)dp->di_size); + buf[dp->di_size] = '\0'; + writerec(buf, 0); + return; + } +#endif + /* fall through */ + + case S_IFDIR: + case S_IFREG: + if (dp->di_size > 0) + break; + /* fall through */ + + case S_IFIFO: + case S_IFSOCK: + case S_IFCHR: + case S_IFBLK: + writeheader(ino); + return; + + default: + msg("Warning: undefined file type 0%o\n", dp->di_mode & IFMT); + return; + } + if (dp->di_size > NDADDR * sblock->fs_bsize) + cnt = NDADDR * sblock->fs_frag; + else + cnt = howmany(dp->di_size, sblock->fs_fsize); + blksout(&dp->di_db[0], cnt, ino); + if ((size = dp->di_size - NDADDR * sblock->fs_bsize) <= 0) + return; + for (ind_level = 0; ind_level < NIADDR; ind_level++) { + dmpindir(ino, dp->di_ib[ind_level], ind_level, &size); + if (size <= 0) + return; + } +} + +/* + * Read indirect blocks, and pass the data blocks to be dumped. + */ +static void +dmpindir(ino, blk, ind_level, size) + ino_t ino; + daddr_t blk; + int ind_level; + fsizeT *size; +{ + int i, cnt; + daddr_t idblk[MAXNINDIR]; + + if (blk != 0) + bread(fsbtodb(sblock, blk), (char *)idblk, (int) sblock->fs_bsize); + else + bzero((char *)idblk, (int)sblock->fs_bsize); + if (ind_level <= 0) { + if (*size < NINDIR(sblock) * sblock->fs_bsize) + cnt = howmany(*size, sblock->fs_fsize); + else + cnt = NINDIR(sblock) * sblock->fs_frag; + *size -= NINDIR(sblock) * sblock->fs_bsize; + blksout(&idblk[0], cnt, ino); + return; + } + ind_level--; + for (i = 0; i < NINDIR(sblock); i++) { + dmpindir(ino, idblk[i], ind_level, size); + if (*size <= 0) + return; + } +} + +/* + * Collect up the data into tape record sized buffers and output them. + */ +void +blksout(blkp, frags, ino) + daddr_t *blkp; + int frags; + ino_t ino; +{ + register daddr_t *bp; + int i, j, count, blks, tbperdb; + + blks = howmany(frags * sblock->fs_fsize, TP_BSIZE); + tbperdb = sblock->fs_bsize >> tp_bshift; + for (i = 0; i < blks; i += TP_NINDIR) { + if (i + TP_NINDIR > blks) + count = blks; + else + count = i + TP_NINDIR; + for (j = i; j < count; j++) + if (blkp[j / tbperdb] != 0) + spcl.c_addr[j - i] = 1; + else + spcl.c_addr[j - i] = 0; + spcl.c_count = count - i; + writeheader(ino); + bp = &blkp[i / tbperdb]; + for (j = i; j < count; j += tbperdb, bp++) + if (*bp != 0) + if (j + tbperdb <= count) + dumpblock(*bp, (int)sblock->fs_bsize); + else + dumpblock(*bp, (count - j) * TP_BSIZE); + spcl.c_type = TS_ADDR; + } +} + +/* + * Dump a map to the tape. + */ +void +dumpmap(map, type, ino) + char *map; + int type; + ino_t ino; +{ + register int i; + char *cp; + + spcl.c_type = type; + spcl.c_count = howmany(mapsize * sizeof(char), TP_BSIZE); + writeheader(ino); + for (i = 0, cp = map; i < spcl.c_count; i++, cp += TP_BSIZE) + writerec(cp, 0); +} + +/* + * Write a header record to the dump tape. + */ +void +writeheader(ino) + ino_t ino; +{ + register long sum, cnt, *lp; + + spcl.c_inumber = ino; + spcl.c_magic = NFS_MAGIC; + spcl.c_checksum = 0; + lp = (long *)&spcl; + sum = 0; + cnt = sizeof(union u_spcl) / (4 * sizeof(long)); + while (--cnt >= 0) { + sum += *lp++; + sum += *lp++; + sum += *lp++; + sum += *lp++; + } + spcl.c_checksum = CHECKSUM - sum; + writerec((char *)&spcl, 1); +} + +struct dinode * +getino(inum) + ino_t inum; +{ + static daddr_t minino, maxino; + static struct dinode inoblock[MAXINOPB]; + + curino = inum; + if (inum >= minino && inum < maxino) + return (&inoblock[inum - minino]); + bread(fsbtodb(sblock, ino_to_fsba(sblock, inum)), (char *)inoblock, + (int)sblock->fs_bsize); + minino = inum - (inum % INOPB(sblock)); + maxino = minino + INOPB(sblock); + return (&inoblock[inum - minino]); +} + +/* + * Read a chunk of data from the disk. + * Try to recover from hard errors by reading in sector sized pieces. + * Error recovery is attempted at most BREADEMAX times before seeking + * consent from the operator to continue. + */ +int breaderrors = 0; +#define BREADEMAX 32 + +void +bread(blkno, buf, size) + daddr_t blkno; + char *buf; + int size; +{ + int cnt, i; + extern int errno; + +loop: + if ((int)lseek(diskfd, ((off_t)blkno << dev_bshift), 0) < 0) + msg("bread: lseek fails\n"); + if ((cnt = read(diskfd, buf, size)) == size) + return; + if (blkno + (size / dev_bsize) > fsbtodb(sblock, sblock->fs_size)) { + /* + * Trying to read the final fragment. + * + * NB - dump only works in TP_BSIZE blocks, hence + * rounds `dev_bsize' fragments up to TP_BSIZE pieces. + * It should be smarter about not actually trying to + * read more than it can get, but for the time being + * we punt and scale back the read only when it gets + * us into trouble. (mkm 9/25/83) + */ + size -= dev_bsize; + goto loop; + } + if (cnt == -1) + msg("read error from %s: %s: [block %d]: count=%d\n", + disk, strerror(errno), blkno, size); + else + msg("short read error from %s: [block %d]: count=%d, got=%d\n", + disk, blkno, size, cnt); + if (++breaderrors > BREADEMAX) { + msg("More than %d block read errors from %d\n", + BREADEMAX, disk); + broadcast("DUMP IS AILING!\n"); + msg("This is an unrecoverable error.\n"); + if (!query("Do you want to attempt to continue?")){ + dumpabort(0); + /*NOTREACHED*/ + } else + breaderrors = 0; + } + /* + * Zero buffer, then try to read each sector of buffer separately. + */ + bzero(buf, size); + for (i = 0; i < size; i += dev_bsize, buf += dev_bsize, blkno++) { + if ((int)lseek(diskfd, ((off_t)blkno << dev_bshift), 0) < 0) + msg("bread: lseek2 fails!\n"); + if ((cnt = read(diskfd, buf, (int)dev_bsize)) == dev_bsize) + continue; + if (cnt == -1) { + msg("read error from %s: %s: [sector %d]: count=%d\n", + disk, strerror(errno), blkno, dev_bsize); + continue; + } + msg("short read error from %s: [sector %d]: count=%d, got=%d\n", + disk, blkno, dev_bsize, cnt); + } +} diff --git a/sbin/dump/unctime.c b/sbin/dump/unctime.c new file mode 100644 index 0000000..7b3fd4e --- /dev/null +++ b/sbin/dump/unctime.c @@ -0,0 +1,157 @@ +/*- + * Copyright (c) 1980, 1993 + * 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 char sccsid[] = "@(#)unctime.c 8.1 (Berkeley) 6/5/93"; +#endif /* not lint */ + +#include <sys/types.h> + +#include <stdio.h> +#include <time.h> +#ifdef __STDC__ +#include <stdlib.h> +#include <string.h> +#endif + +#ifndef __P +#include <sys/cdefs.h> +#endif + +/* + * Convert a ctime(3) format string into a system format date. + * Return the date thus calculated. + * + * Return -1 if the string is not in ctime format. + */ + +/* + * Offsets into the ctime string to various parts. + */ + +#define E_MONTH 4 +#define E_DAY 8 +#define E_HOUR 11 +#define E_MINUTE 14 +#define E_SECOND 17 +#define E_YEAR 20 + +static int dcmp __P((struct tm *, struct tm *)); +static time_t emitl __P((struct tm *)); +static int lookup __P((char *)); + + +time_t +unctime(str) + char *str; +{ + struct tm then; + char dbuf[26]; + + (void) strncpy(dbuf, str, sizeof(dbuf) - 1); + dbuf[sizeof(dbuf) - 1] = '\0'; + dbuf[E_MONTH+3] = '\0'; + if ((then.tm_mon = lookup(&dbuf[E_MONTH])) < 0) + return (-1); + then.tm_mday = atoi(&dbuf[E_DAY]); + then.tm_hour = atoi(&dbuf[E_HOUR]); + then.tm_min = atoi(&dbuf[E_MINUTE]); + then.tm_sec = atoi(&dbuf[E_SECOND]); + then.tm_year = atoi(&dbuf[E_YEAR]) - 1900; + return(emitl(&then)); +} + +static char months[] = + "JanFebMarAprMayJunJulAugSepOctNovDec"; + +static int +lookup(str) + char *str; +{ + register char *cp, *cp2; + + for (cp = months, cp2 = str; *cp != '\0'; cp += 3) + if (strncmp(cp, cp2, 3) == 0) + return((cp-months) / 3); + return(-1); +} +/* + * Routine to convert a localtime(3) format date back into + * a system format date. + * + * Use a binary search. + */ + +static time_t +emitl(dp) + struct tm *dp; +{ + time_t conv; + register int i, bit; + struct tm dcopy; + + dcopy = *dp; + dp = &dcopy; + conv = 0; + for (i = 30; i >= 0; i--) { + bit = 1 << i; + conv |= bit; + if (dcmp(localtime(&conv), dp) > 0) + conv &= ~bit; + } + return(conv); +} + +/* + * Compare two localtime dates, return result. + */ + +#define DECIDE(a) \ + if (dp->a > dp2->a) \ + return(1); \ + if (dp->a < dp2->a) \ + return(-1) + +static int +dcmp(dp, dp2) + register struct tm *dp, *dp2; +{ + + DECIDE(tm_year); + DECIDE(tm_mon); + DECIDE(tm_mday); + DECIDE(tm_hour); + DECIDE(tm_min); + DECIDE(tm_sec); + return(0); +} |