[mod.sources] backup - front end for BSD dump

sources-request@panda.UUCP (05/19/86)

Mod.sources:  Volume 5, Issue 5
Submitted by: Wombat <pur-ee!pucc-j.Purdue.EDU!rsk>

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	README
#	backup.8l
#	backup.c
#	backup.list
#	tape.label
# This archive created: Thu May 15 13:49:26 1986
# By:	Wombat (Purdue University)
cat << \SHAR_EOF > README
Backup is a front-end for 4.[23]bsd dump, allowing non-programmers to
successfully run disk-to-tape backups.  It incorporates what seems to
be a sufficient amount of paranoid thinking to prevent data-destroying
blunders.  I'm sending this out partially because I feel it could be of
some use, and partially because I'm interested in seeing what
[constructive] comments we'll receive; we're considering some changes
in the way we do backups.

Note that this program is NOT a standalone backup program; it has never
been tested with anything other than 4.2BSD and 4.3BSD dump.

-- 
Rich Kulawiec, pucc-j!rsk, rsk@asc.purdue.edu

Contents:

README
backup.8l			Man page for the "backup" command
	(nroff -man backup.8l)
backup.c			The backup program itself
	(cc -O -DPUCC -DBSD4_2  -DALLINONE -DVERIFYTAPE -o backup backup.c)
backup.list			A sample "config" file for backup
tape.label			A lore document explaining how tape labels work
	(nroff -ms tape.label)
SHAR_EOF
cat << \SHAR_EOF > backup.8l
.TH BACKUP 8L PUCC
.SH NAME
backup \- run periodic tape dumps
.SH SYNOPSIS
.B /etc/backup
.br
.SH DESCRIPTION
.I Backup
reads
.I /etc/backup.list
and attempts the necessary invocations of 
.IR dump (8)
to back up (to tape) all filesystems listed therein.
It asks the invoker to mount the appropriate
tape, and checks the tape label to make sure that
it is correct with respect to machine, filesystem,
and level of backup being done.
It then calls
.IR dump (8)
with appropriate arguments and waits
for it to finish.  When dump is done,
.I backup
will go on to the next filesystem.
.PP
The backup schedule in the file
.I /etc/backup.list
consists of lines of the form:
.nf

\ \ \ \ \ pucc-h:x0x9x9x:/usr

.fi
which are interpreted as follows:
.nf

\ \ \ \ \ machine-name:schedule:filesystem

.fi
where ``machine-name'' must be exactly the same as the string returned by
.IR hostname (2),
and ``filesystem'' the same as given in /etc/fstab.
The ``schedule'' is a seven character string, with one character for
each day of the week, with the first character representing Sunday.
The character corresponding to a given day of the week represents the level
of dump for that day.  For example, the sample line indicates that for pucc-h,
filesystem /usr, an epoch (level 0) dump should be done on Monday,
with incremental (level 9) dumps on Wednesday and Friday.
No dumps will be done on Sunday, Tuesday, Thursday, or Saturday.
Note that any numeric character will cause a dump of that level
to be done on that day; any non-numeric character
will prevent dumps from being done on that day.
Colons (``:'') are not allowed, since they are used as
field separators.
.PP
Additionally,
.I backup
runs ``/etc/dump #W'' (with # being the level of the dump)
and decides from its output whether a filesystem has already been
backed up on the same day.
If it finds that any particular filesystem
has already been dumped, it will ask the
operator to verify that it should be dumped again.
.PP
Any question that
.I backup
asks must be answered with 
.B go
or 
.BR stop ,
exactly.
Any other answer will cause an error message to be printed,
and the question will be asked again.
.SH FILES
.nf
/etc/backup.list
/etc/fstab
/etc/dumpdates
.fi
.SH SEE ALSO
dump(8)
.SH BUGS
.PP
There's no way to cleanly restart backup (if interrupted) without
repeating some work.
.PP
Since
.I backup
tries to fit all partial dumps for a machine on one tape, selecting
too low a level or waiting too long between backups
may cause it to run out of tape.
.PP
Mixing dump levels on one partial tape is not recommended.
.PP
Dump always claims to be rewinding the tape, even if it's not.
.PP
The tape labelling scheme is not fail safe.
SHAR_EOF
cat << \SHAR_EOF > backup.c
/*
*	backup.c		Rich Kulawiec, PUCC, 1985
*
*	Run dumps according to a fixed schedule encoded in file CONTROL.
*
*	File contains:
*
*	main()		-- main program, of course
*	check_sched()	-- examine a schedule line for information
*	already_done()	-- check to see if dumps already done today
*	ask_for_tape()	-- ask the operator to mount the appropriate tape
*	verify()	-- prompt operator to confirm tape load
*	verifytape()	-- check tape label file for correctness
*	write_label()	-- write a tape label file
*	rundump()	-- build a dump command and run it
*	query()		-- ask a question and get an answer
*	tapeskip()	-- skip one file on the tapedrive
*	offline()	-- take tapedrive offline
*	message()	-- print a message on the terminal
*
*	Defining DEBUG changes backup's idea of the tape drive, and
*	causes it to echo "dump" and "mt" commands instead of running them.
*	It will still run dump W as needed, though.
*	Depending on what's left in here, it may also print some
*	additional information as  it runs.
*
*	Defining ALLINONE causes all partial dumps to be placed on
*	the same tape.  It would probably be a bad idea to mix partial
*	dump levels on that tape.
*
*	Defining VERIFYTAPE causes backup to check the tape for a header
*	indicating whether or not it's the right tape.  Note that leaving
*	this undefined will still let backup query the operator before
*	firing up dump to scribble on the tape.
*/

#ifndef lint
static char *rcsid = "$Header: /usr/src/etc/RCS/backup.c,v 1.13 85/08/19 19:49:42 rsk Exp $";
#endif lint
/*
 * $Log:	backup.c,v $
 * Revision 1.13  85/08/19  19:49:42  rsk
 * Changed the handling of offline() to fix a buglet; if backuup
 * ran epoch dumps using a schedule file whose last line did not
 * have a '0' in the same position as the '0' for the epoch dumps
 * that were run (confusing isn't it?) then what happened was that
 * Level was set to, say, 'x', and the test at the end of main
 * succeeded...and backup tried to take the tapedrive offline after
 * already doing so.
 * Therefore, the following was done:
 * rundump() now calls offline() (1) all the time if ALLINONE is
 * not defined (2) for all epoch dumps if ALLINONE is defined.
 * To take care of multiple partial dumps, a variable "Rewind"
 * was created; if it is set, then main() will call offline()
 * just prior to exiting.  The only place that sets Rewind is
 * inside verifytape() -- and it sets it iff it's looking at
 * a partial tape and is running with ALLINONE defined.
 * 
 * Revision 1.12  85/08/18  20:34:51  rsk
 * Added several (void)'s in front of calls to offline()
 * to satisfy lint.
 * 
 * Revision 1.11  85/08/18  16:17:35  rsk
 * Several changes.
 * If DEBUG is defined, then MT_OFFLINE is fixed so that
 * it just rewinds the tape rather than taking it offline.
 * The series of if statements in verifytape() are now
 * chained with else clauses.
 * When write_label() is called in verifytape(), verifytape()
 * calls itself to check the results.
 * Added several calls to offline() in verifytape() to
 * make sure bad tape gets noticed.
 * Took out the level sanity check in write_label() since it
 * really didn't do much.
 * Fixed a small bug in write_label(); if ALLINONE is defined
 * and this is a partial tape, then should write PARTIAL_LABEL
 * rather than the name of the filesystem.  This only shows up
 * if a SPARE tape is used for a partial and then the order
 * of partial dumps gets re-arranged later, but it's fixed
 * anyhow.
 * 
 * Revision 1.10  85/08/17  13:14:58  rsk
 * Updated list of functions at top to reflect reality.
 * 
 * Revision 1.9  85/08/16  13:01:13  rsk
 * Rearranged functions to reflect logical order.
 * 
 * Revision 1.8  85/08/16  11:29:22  rsk
 * Added a little debugging code to already_done(); fixed up the
 * function headers to more accurately reflect what goes on in
 * each function.
 * 
 * Revision 1.7  85/08/16  10:58:42  rsk
 * Ripped out pseudo-exit routine from last night.  Simplified
 * exit codes.  Switched calls to rundump() and verify(), since
 * an accidental change last night had backup trying to verify
 * the tape after running the dump.  Made verify return a value
 * even though it's ignored, 'cause (a) might use it later,
 * and (b) couldn't figure out how to recursively return a void.
 * 
 * Revision 1.6  85/08/16  02:01:24  rsk
 * Added a new routine that's called instead of exit() with
 * a wealth (10, actually) of exit codes; might be handy if
 * this is run by a script someday.  Will probably trim the
 * list tomorrow, as well as fixing a known bug.
 * The program is definitely easier manage now that it's
 * broken up into chunks, although the growth in the number
 * of (void) functions and global variables isn't very aesthetic.
 * 
 * Revision 1.5  85/08/16  01:31:44  rsk
 * Many changes.  Several new functions, which have taken chunks
 * of the large loop that used to be in main() and broken them
 * up into little pieces.  Tapeok is now a global, among other
 * things, and is used when putting multiple partial dumps on
 * one tape to avoid attempting to reverify the tape after it's
 * been done once.   Most new functions are (void) since if they
 * fail we have to abort anyway; no sense testing for a return
 * value and then aborting.
 * 
 * Revision 1.4  85/08/16  00:00:42  rsk
 * Made Filesys, Tapedrive, and Schedline global
 * prepatory to breaking the large loop in main()
 * up into several function calls.
 * 
 * Revision 1.3  85/08/14  18:45:52  rsk
 * Parameterized the sizes of a number of characters arrays.
 * Used STRSIZE (255) for devices, filesystems, command lines,
 * hostnames, lines from the schedule file, and replies from
 * the operator.
 * Used DATESTRSIZE (30) for dates...since all dates are in
 * ctime() format, which takes 26 characters, this should
 * be sufficient.
 * Only non-parameterized array is "weekmask", which is
 * set to 7.  This will need to be changed if another
 * day is added to the week.
 * 
 * Revision 1.2  85/08/12  20:16:00  rsk
 * Added changes for "spare" tapes, including auto-labelling.
 * Slightly changed what happens when dump fails if DEBUG is defined.
 * Took #ifdef DEBUG off write_label() routine.
 * 
 * Revision 1.1  85/08/09  16:13:05  rsk
 * Initial revision
 * 
*/

#include <stdio.h>
#include <ctype.h>
#include <sys/time.h>

#define	OKAY		0
#define STRSIZE		255	/* Should be large enough for most everything */
#define DATESTRSIZE	30	/* Should only need to be 26 (for ctime) */
#define	FAIL		-1
#define	DUMPFLAGS	"ufd"
#define	DENSITY		"6250"
#define	DUMPINFOFLAG	"W"
#define	MT_SKIP		"fsf 1"
#define	MYNAME		"BACKUP"
#define	SPARE_LABEL	"SPARE"

#ifdef ALLINONE
#define PARTIAL_LABEL	"PARTIAL"
#endif ALLINONE

#ifdef DEBUG
#define	DUMP		"echo /etc/dump"
#define	TAPE		"dev.rmt8"
#define	NTAPE		"dev.nrmt8"
#define	CONTROL		"backup.list"
#define	MT		"/bin/echo mt -f"
#define	MT_OFFLINE	"rewind"
#else DEBUG
#define	DUMP		"/etc/dump"
#define	TAPE		"/dev/rmt8"
#define	NTAPE		"/dev/nrmt8"
#define	CONTROL		"/etc/backup.list"
#define	MT		"/bin/mt -f"
#define	MT_OFFLINE	"offline"
#endif DEBUG

#define	GO		"go"
#define STOP		"stop"

/*
* Different classes of errors; provides additional error message on exit.
*/

#define ST_OKAY		0
#define ST_NOWORK	1
#define ST_DUMP		2
#define ST_ABORT	3
#define ST_CTL		4
#define ST_TAPE		5
#define ST_SYSCALL	6
#define ST_INTERNAL	7

char	Command[STRSIZE];	/* constructed shell commands */
char	Date[DATESTRSIZE];	/* current date/time in ctime() format */
char	Hostname[STRSIZE];	/* the name of this host */
char	Filesys[STRSIZE];	/* name of filesystem(s) to dump */
char	Tapedrive[STRSIZE];	/* name of tapedrive to use */
char	Schedline[STRSIZE];	/* a line read out of schedule file */
char	Weekmask[7];		/* one slot per day of week */
char	Level;			/* level of this dump */
#ifdef ALLINONE
int	Tapeok = FAIL;		/* Has this tape been verified? */
int	Rewind = FAIL;		/* Does tape need to be rewound at end? */
#endif ALLINONE

struct tm	*localtime();
FILE	*popen();
void	message();
void	check_sched();
void	ask_for_tape();
void	rundump();
char	*strcpy();
char	*strncpy();
char	*index();
char	*rindex();
char	*gets();
long	time();

main()
{
	FILE	*fp;
	long	timebuf;		/* seconds since epoch */
	struct	tm *ldate;		/* date broken down into fields */

	if(gethostname(Hostname,sizeof(Hostname)) == FAIL) {
		message("gethostname() failed");
		exit(ST_SYSCALL);
	}

	(void) time(&timebuf);
	(void) strcpy(Date,ctime(&timebuf));
	ldate = localtime(&timebuf);

	if( (fp = fopen(CONTROL,"r")) == NULL) {
		message("can't open control file");
		exit(ST_SYSCALL);
	}

	/*
	* Read the schedule file; make rudimentary checks for integrity.
	*/

	while( fscanf(fp,"%s",Schedline) != FAIL) {

	/*
	* Break out the fields of this line of the schedule.
	*/

		check_sched(ldate->tm_wday);

	/*
	* If this line of the schedule file pertains to this machine,
	* and if there's a digit in the dump schedule for today,
	* then we probably have some work to do.
	*/

		if( (strncmp(Schedline,Hostname,strlen(Hostname)) == OKAY)
			&& isascii(Level) && isdigit(Level) ) {

	/*
	* Check to see if we've already dumped this filesystem today.
	*/

			if( already_done() == OKAY ){
				(void) printf("%s: filesystem %s has already been dumped today\n",MYNAME,Filesys);
				if(query ("Do you want to dump it again?") != OKAY)
					continue;
			}
	/*
	* Request the proper tape from the operator.
	*/
			ask_for_tape();

	/*
	* If ALLINONE is defined, then first pass through here will
	* set Tapeok to OKAY, assuming it's the right tape,
	* when we're putting multiple partial dumps on one tape.
	* Successive passes won't bother to verify the tape.
	*/

#ifdef ALLINONE
			if(Level == '0' )
				(void) verify();
			else 
				if(Tapeok == FAIL)
					(void) verify();
#else ALLINONE
			(void) verify();
#endif ALLINONE

	/*
	* Build the call the dump, and run it.  This does the real work.
	* Note that rundump() also has the responsibility for rewinding
	* the tape and taking the drive offline if needed.
	*/
			rundump();
		}
	}
	message("finished");
	(void) fclose(fp);

	/*
	* If in ALLINONE mode, then take tape drive offline at the
	* end of series of partials.  If these were epoch dumps,
	* the drive will already be offline...see above.
	* Note: this is one of the places where assumptions about
	* NOT mixing epoch and partial dumps are important.
	*/

#ifdef ALLINONE
	if (Rewind == OKAY)
		(void) offline();
#endif ALLINONE

	exit(ST_OKAY);
}
/*
* check_sched() -- examine line of schedule file for work to do
*
* Parameters: day_of_week -- part of ldate structure
*
* Global Variables:	Schedline, Filesys, Weekmask, Level
*
* Returns: nothing
*
* Side Effects: sets Filesys, Weekmask, and Level according to schedule line
*
*/
void check_sched(day_of_week)
int day_of_week;
{
	char	*charptr;		/* scratch character pointer */


	if( (charptr = rindex(Schedline,':')) == NULL) {
		message("Garbled control file");
		exit(ST_CTL);
	}
	(void) strcpy(Filesys,++charptr);	/* got filesystem */

	if( (charptr = index(Schedline,':')) == NULL) {
		message("Garbled control file");
		exit(ST_CTL);
	}
	(void) strncpy(Weekmask,++charptr,7);	/* got dump levels */

	/*
	* Get today's dump level.
	*/

	Level = Weekmask[day_of_week];
}
/*
* already_done() -- see if this filesystem was dumped today
*
* Parameters:	none
*
* Global Variables: Command, Date, Filesys, Level
*
* Returns:	OKAY -- if already dumped Filesystem at level Level today.
*		FAIL -- otherwise
*
* Side Effects: runs "dump W" to gather information
*
*/
already_done()
{
	char	last_device[STRSIZE];	/* device filesystem is mounted on */
	char	last_filesys[STRSIZE];	/* name of filesystem */
	char	last_level;		/* level of last dump */
	char	last_date[DATESTRSIZE];	/* ctime format date of last dump */
	char	replybuf[STRSIZE];	/* holds a line of reply from dump W */
	FILE	*sp;			/* stream pointer */
	int	returnval;		/* temporary place for return value */

	returnval = FAIL;

	/*
	* Build a command line for dump that will cause it to print
	* its idea of which filesystems need to be dumped.
	* Do this rather than reading /etc/dumpdates and /etc/fstab
	* and deciphering everything ourself.
	*/

	(void) sprintf(Command,"%s %c%s",DUMP,Level,DUMPINFOFLAG);
	(void) fflush(stdout);

	/*
	* Run the command, and read its output.
	*/

	if( (sp = popen(Command,"r")) == NULL) {
		message("popen() failed");
		exit(ST_SYSCALL);
	}

	/*
	* Eat first line of dump's verbose output
	*/
	
	if(fscanf(sp,"%[^\n]\n",replybuf) == FAIL) {
		message("can't fscanf() reply from dump");
		exit(ST_SYSCALL);
	}

	/*
	* Grab all necessary fields out of dump's output.
	* Slight trickiness; must eat everything up to the first '('
	* in order to get filesystem name; thus, last_device
	* might contain some garbage characters.  We don't care, since
	* we don't really use it.  Then we eat everything to ')'
	* in order to get filesystem name.
	*/

	while (fscanf(sp,"%[^(]( %[^)]) Last dump: Level %c, Date %[^\n]\n",last_device,last_filesys,&last_level,last_date) != FAIL) {

	/*
	* If this line of "dump W" output pertains to the filesystem in
	* question, and if the dump level matches the current one,
	* AND if the date of the last dump appears to be today,
	* then it would seem that this filesystem has been dumped today.
	* Note that fact, and just keep going; must read all of dump's
	* output to avoid causing SIGPIPE on early pclose().
	*/
		if(strncmp(last_filesys,Filesys,strlen(Filesys)) == OKAY 
			&& last_level == Level
			&& strncmp(last_date,Date,10) == OKAY)
			returnval = OKAY;
	}

	if( pclose(sp) == FAIL) {
		message("pclose() failed");
		exit(ST_SYSCALL);
	}

#ifdef DEBUG
	(void) printf("already_done() returning %s\n",returnval == OKAY ? "OKAY" : "FAIL");
	return(query("Type go for OKAY(yes), stop for FAIL(no)."));
#else DEBUG
	return(returnval);
#endif DEBUG
}
/*
* ask_for_tape() -- request the operator to mount the proper tape
*
* Parameters: none
*
* Global Variables: Tapedrive, Hostname, Filesys, Date
*
* Returns: nothing
*
* Side Effects: none
*
*/
void ask_for_tape()
{

	/*
	* Ask for the proper tape; use rewind device for epochs,
	* no-rewind device for incrementals.
	* Note that if this tape has already been verified, i.e.
	* we compiled with ALLINONE defined and Tapeok has been
	* set to OKAY by a previous call to verify(), then we do nothing.
	*/

	if(Level == '0') {
		(void) strcpy(Tapedrive,TAPE);
		(void) printf("%s: get the FULL backup tape for machine %s, filesystem %s for %.3s\n",MYNAME,Hostname,Filesys,Date);
	}
	else {
		(void) strcpy(Tapedrive,NTAPE);
#ifdef ALLINONE
		if (Tapeok == FAIL)
			(void) printf("%s: get the PARTIAL backup tape for machine %s for %.3s\n",MYNAME,Hostname,Date);
		
#else ALLINONE
		(void) printf("%s: get the PARTIAL backup tape for machine %s, filesystem %s for %.3s\n",MYNAME,Hostname,Filesys,Date);
#endif ALLINONE

	}
}
/*
* verify() -- prompt operator to mount tape
*
* Parameters:	none
*
* Global Variables: none
*
* Returns:	OKAY if tape verified.
*
* Side Effects: exits if operator wants to quit
*
*/
verify()
{
	if( query("Is the correct tape mounted?") == OKAY) {
		if( verifytape() == OKAY) {
			message("tape verified");
			return(OKAY);
		}
		else {
			message("\007\007\007INCORRECT TAPE MOUNTED!!");
			message("Get the correct tape!!");
			return(verify());
		}
	}
	else {
		if( query ("Do you want to continue the backup?") == OKAY) {
			return(verify());
		}
		else {
			message("aborting on operator command");
			exit(ST_ABORT);
		}
	}
	/*
	* We should never get here; abort if it happens.
	*/

	message("Something very bad has happened in verify()");
	exit(ST_INTERNAL);
	
}
/*
* verifytape() -- check tape label vs. current filesystem
*		  Is smart enough to recognize a spare tape; also
*		  recognizes partial tape labels if in ALLINONE mode.
*
* Parameters:	none
*
* Global Variables: Hostname, Date, Filesys, Level
*
* Returns:	OKAY -- if this is the correct w.r.t. filesys & level
*		FAIL -- otherwise
*
* Side Effects: leaves tape positioned at end of label file if correct tape.
*		writes a new label if this is a spare tape
*		set the Rewind flag if ALLINONE is defined and this is
*			a multiple partial dump tape
*
*/
verifytape()
{
#ifdef VERIFYTAPE
	char	last_hostname[STRSIZE];	/* hostname as written on tape */
	char	last_date[DATESTRSIZE];	/* date as written on tape */
	char	last_filesys[STRSIZE];	/* filesystem as written on tape */
	char	last_level;		/* dump level as written on tape */
	FILE	*fp;

	if( (fp = fopen(TAPE,"r")) == NULL) {
		message("can't open tapedrive");
		exit(ST_SYSCALL);
	}

#ifdef DEBUG
	(void) printf("%s: searching for label: %s %.3s %s %c\n",MYNAME,Hostname,Date,Filesys,Level);
#endif DEBUG

	if(fscanf(fp,"%s %s %s %c",last_hostname,last_date,last_filesys,&last_level) == FAIL) {
		message("can't read tape label");
		exit(ST_SYSCALL);
	}

#ifdef DEBUG
	(void) printf("%s:    found this label: %s %.3s %s %c\n",MYNAME,last_hostname,last_date,last_filesys,last_level);
#endif DEBUG

	(void) fclose(fp);

	/*
	* If the label appears to be okay, i.e. all four fields match
	* our idea of what should be there, then skip over the label
	* file and get ready to run dump.
	*/

	if( strcmp(Hostname,last_hostname) == OKAY
		&& strncmp(Date,last_date,3) == OKAY
		&& strcmp(Filesys,last_filesys) == OKAY
		&& Level == last_level) {
		(void) tapeskip();
		return(OKAY);
	}

#ifdef ALLINONE

	/*
	* If we're dealing with multiple partial dumps on one tape,
	* then we need to check for the special label marking this
	* tape; therefore, a slightly modified test is made.
	*
	* Must also set Rewind so that tape will be taken care of
	* when all dumps are done, just before we exit...see the
	* end of main().
	*/

	else if( strcmp(Hostname,last_hostname) == OKAY
		&& strncmp(Date,last_date,3) == OKAY
		&& strcmp(PARTIAL_LABEL,last_filesys) == OKAY
		&& Level != '0'
		&& Level == last_level) {
		Tapeok = OKAY;
		Rewind = OKAY;
		(void) tapeskip();
		return(OKAY);
	}

#endif ALLINONE

	/*
	* This might be a spare tape; if so, ignore the date and dumplevel
	* fields; just check the host, and look for SPARE_LABEL in place
	* of the filesystem name.
	*/

	else if (strcmp(Hostname,last_hostname) == OKAY
		&& strncmp(last_filesys,SPARE_LABEL,strlen(SPARE_LABEL)) == OKAY) {
		message("This looks like a spare tape.");
		if( query("Go ahead and use it?") == OKAY) {
			if( write_label() == OKAY) {
				return(verifytape());
			}
			else {
				(void) offline();
				return(FAIL);
			}
		}
		else {
			(void) offline();
			return(FAIL);
		}
	}
	else {
		(void) offline();
		return(FAIL);
	}

#else VERIFYTAPE
	return(OKAY);
#endif VERIFYTAPE
}
/*
* write_label() -- write a label on a spare tape
*
* Parameters:	none
*
* Global Variables:	Hostname, Date, Filesys, Level
*
* Returns:	OKAY if label successfully written
*		FAIL otherwise
*
* Side Effects: writes a tiny label file on the beginning of the tape
*
*/
write_label()
{
	FILE	*fp;

	if( (fp = fopen(TAPE,"w")) == NULL) {
		message("can't open tapedrive");
		exit(ST_SYSCALL);
	}

#ifdef ALLINONE
	if( Level == '0')
		fprintf(fp,"%s %.3s %s %c\n",Hostname,Date,Filesys,Level);
	else
		fprintf(fp,"%s %.3s %s %c\n",Hostname,Date,PARTIAL_LABEL,Level);
#else ALLINONE
	fprintf(fp,"%s %.3s %s %c\n",Hostname,Date,Filesys,Level);
#endif ALLINONE

	(void) fclose(fp);

	return(OKAY);
}
/*
* rundump() -- call the dump program to do the real work
*
* Parameters: none
*
* Global Variables: Level, Tapedrive, Filesys, Command
*
* Returns: nothing
*
* Side Effects: runs the actual dump
* 		queries the operator for advice if dump fails
*
*/
void rundump()
{
	int	status;			/* return status of dump command */

	/*
	* Build the proper command line to call dump.
	*/

	(void) sprintf(Command,"%s %c%s %s %s %s",DUMP,Level,DUMPFLAGS,Tapedrive,DENSITY,Filesys);

	(void) printf("%s: running %s\n",MYNAME,Command);
	(void) fflush(stdout);

	/*
	* Call dump; check the return status to make sure it's alright.
	*/

	if( (status = system(Command)) != OKAY) {
		(void) printf("%s: dump failed, status=%d\n",MYNAME,(status>>8));
#ifdef DEBUG
		if( query("Want to go on?") == FAIL) {
			(void) offline();
			exit(ST_ABORT);
		}
#else DEBUG
		(void) offline();
		exit(ST_DUMP);
#endif DEBUG
	}

#ifdef ALLINONE
	if(Level == '0')
		(void) offline();
#else ALLINONE
	(void) offline();
#endif ALLINONE
}
/*
* query(question) -- prints "question" on the tty, then waits for answer
*
* Parameters:	question -- a null-terminated string
*
* Global Variables: none
*
* Returns:	OKAY -- if question eventually answered with GO
*		FAIL -- if question eventually answered with STOP
*
* Side Effects: recursive routine
*
*/
query(question)
char	*question;
{
	char	reply[STRSIZE];		/* Whatever the user types in reply */

	(void) printf("%s: %s <Answer %s or %s only>: ",MYNAME,question,GO,STOP);
	/*
	* Print the question and wait for an answer.
	* If we don't understand the answer, retry (recursively).
	*/

	if( gets(reply) == NULL) {
		message("can't talk to console");
		exit(ST_SYSCALL);
	}
	else if( strcmp(reply,GO) == 0 ) {
		return(OKAY);
	}
	else if( strcmp(reply,STOP) == 0) {
		return(FAIL);
	}
	else {
		message("incorrect response, try again");
		return(query(question));
	}

	/*
	* We should never get here; abort if it happens.
	*/

	message("Something very bad has happened in query()");
	exit(ST_INTERNAL);
}
/*
* takeskip() -- skip forward one file on tape
*
* Parameters: none
*
* Global Variables:	Command
*
* Returns:	OKAY
*
* Side Effects: skips forward one file on the tape via "mt"
*
*/
tapeskip()
{
	int	status;

	(void) sprintf(Command,"%s %s %s",MT,NTAPE,MT_SKIP);

	if( (status = system(Command)) != OKAY) {
		(void) printf("%s: mt failed, status=%d\n",MYNAME,(status>>8));
		exit(ST_TAPE);
	}

	return(OKAY);
}
/*
* offline() -- takes tapedrive offline
*
* Parameters:	none
*
* Global Variables:	Command, Tapedrive
*
* Returns:	OKAY
*
* Side Effects:	calls "mt" to do the work; leaves tape drive off line.
*
*/
offline()
{
	int	status;

	(void) sprintf(Command,"%s %s %s",MT,Tapedrive,MT_OFFLINE);

	if( (status = system(Command)) != OKAY) {
		(void) printf("%s: mt failed, status=%d\n",MYNAME,(status>>8));
		exit(ST_TAPE);
	}

	return(OKAY);
}
/*
* message(msg) -- print a message labelled with name of this program on tty
*
* Parameters:	msg -- the string to print
*
* Global Variables: none
*
* Returns: nothing
*
* Side Effects: none
*
*/
void message(msg)
char	*msg;
{
	(void) printf("%s: %s\n",MYNAME,msg);
}
SHAR_EOF
cat << \SHAR_EOF > backup.list
pucc-h:x0x9x9x:/
pucc-h:x0x9x9x:/sys
pucc-h:x0x9x9x:/usr
pucc-h:x0x9x9x:/usr/spool
pucc-i:xx0x9x9:/
pucc-i:xx0x9x9:/sys
pucc-i:xx0x9x9:/usr
pucc-i:xx0x9x9:/usr/pub
pucc-i:xx0x9x9:/usr/spool
SHAR_EOF
cat << \SHAR_EOF > tape.label
.LP
.nr LT 6.5i
.lt 6.5i
.nr LL 6.5i
.ll 6.5i
.TL
How backup tape labels work.
.AU
Rich Kulawiec
.NH
Overview 
.PP
Our backup tapes are \*Qlabelled\*U on the autoload ring (in writing)
and on the tape itself (in a small file).  This document explains how
that system works and how to label tapes.
.NH
What's in a label
.PP
A sample tape label file looks like this:
.DS
pucc-j Mon /usera 0
.DE
This means that the tape in question is supposed to be used to do
a level 0 dump of /usera on pucc-j on Monday.  If someone tries
to use it for any other dump, \fIbackup(1l)\fR will stop them.
\fBThere is no way to force backup to override this.\fR
The only way out is manual intervention.
.PP
The whitespace-separated fields in the label file have meaning as follows:
.DS
.nf
hostname:    eg. \*Qpucc-j\*U, as returned by gethostname()
day-of-week: eg. \*QMon\*U, as returned in the first 3 characters of ctime()
filesystem:  eg. \*Q/usera\*U, as shown in /etc/fstab
dump level:  eg. \*Q0\*U, a single digit between 0 and 9
.fi
.DE
.NH
How backup uses labels
.PP
Before \fIbackup\fR will scribble on a tape, it reads the label file and
compares it against its idea of what it's about to try to do.
If everything looks okay, it will skip past that file and write
the dump on tape.
However,
if any of the four fields fail to match
(with certain exceptions, see below),
it complains vigorously and demands a different tape.
.NH
The exceptions
.PP
Since we put all of the partial backups for an entire machine on
one tape (at least for the moment) that tape is labelled with
the string \*QPARTIAL\*U where the filesystem should be.
Incidentally, partial backup tapes only have one label at beginning
of the tape\(emthere's nothing between the actual dumps.
.PP
The other case we have to discuss concerns the pool of spare tapes.
This pool is actually several small pools of several tapes per machine,
each of which has been labelled as follows:
.DS
hostname sss SPARE c
.DE
where \*Qhostname\*U is as above; \*Qsss\*U is just a scratch character string;
\*QSPARE\*U is the string that tells backup that this is a spare tape; and
\*Qc\*U is just a scratch character.
.PP
When a spare tape is presented to \fIbackup\fR, it verifies that it is indeed
a spare, and that it's for the correct machine, and then writes a \fIreal\fR
label file on it.  In other words, the tape is no longer a spare.
.NH
Creating a tape label file
.PP
Simple; you can just \*Qecho\*U the information onto that tape; or type it
into a file and \*Qcat\*U it on, or whatever.  Make sure that you write it
on the tape at 6250 bpi using the RAW device, since \fIbackup\fR
runs at that density.
It's also important to point out that writing a label file on a tape
which already contains an old label file and a dump image will make the dump
inacessible, since a double EOF will be written after the label file.
Make \fIcertain\fR that the dump isn't needed before rewriting the label file.
.PP
It's also easy to check a label file, just by using cat or dd to read the
first file on the tape.   Again, make sure you use the 6250 bpi RAW device.
Here's an example:
.DS
% echo \*Qpucc-j Mon /usera 0\*U > /dev/rmt6250

% cat /dev/rmt6250
pucc-j Mon /usera 0
.DE
.NH
Faking it
.PP
Occasionally, due to a failed backup or a massive change in a filesystem
between epoch dumps, there is a need to do a manual dump.  This is a
slightly dangerous and tricky business, and should not be attempted unless
you understand \fBexactly\fR what you are doing.  Caveat emptor.
.PP
Generally speaking, these "special-case" dumps are epoch dumps and are done
on the same tapes normally used by \fIbackup\fR.  Mount the tape and verify
that it is indeed the correct one by checking the label on it; one way
to do this is
.DS
% cat /dev/nrmt8
.DE
which will show you the label--it also leaves the tape positioned after the
end of the label and ready for dumping, since the no-rewind tape device
is used.
.PP
The correct invocation for dump is of the form
.DS
% /etc/dump 0ufd /dev/rmt8 6250 \fI/filesystem\fR
.DE
where \fIfilesystem\fR is the name (e.g. /usr) of the filesystem
you wish to dump.
The effects of the "u" flag are important to note; if present, it causes
the file /etc/dumpdates to keep a record of the date of this dump, and
that record is used to decide which files will be included on subsequent
incremental dumps.  If absent, no such record will be kept, and incremental
dumps will be based on whatever date is already in /etc/dumpdates.
For our purpose, which is usually to "catch up" after making many
changes to a filesystem, the "u" flag should be used.
.NH
Summary
.PP
Most of the tape labelling functions are automatic; human intervention
should only be required if (a) we run out of spare tapes or (b) we want
to reuse some of the (formerly spare) tapes, or (c) we have to set up
the backup scheme on a new machine, or on new filesystems.
SHAR_EOF
#	End of shell archive
exit 0