[net.sources] backup

cfv@packet.UUCP (07/23/83)

I am reposting my tar frontend program called 'backup'. I recently found a
VERY serious bug in the old version that needed some repairs. If you tried
to back up a file like 'foo;init;bar' then when the system call was made
it executed 'init'. Needless to say, this causes some interesting problems.
The new version takes care of this by putting quotes around all of the
filenames to be backed up since there is no way to tell shell not to
interpret things *sigh*

======= backup.dist ========
# !/bin/csh
# Run this file as shell script
echo extracting README
cat > README <<'EOF'
The files in this directory create a simple backup system for a Unix  site.
It  is  written  for  4.1BSD,  but  should be easily adaptable for any Unix
system.  Please let me know of any problems, changes, or improvements.

This is a list of the files in the system. A summary of their operations
follow:

                     +-------------------------------+
                     | total 77                      |
		     | -rw-r-----   3385  README     |
                     | -rw-r-----   4019  backup.1   |
                     | -rw-r-----  29156  backup.c   |
                     | -rw-r-----   2560  backup.csh |
                     | -rw-------      2  daily      |
                     | -rw-------      2  monthly    |
                     | -rw-------      2  quarterly  |
                     | -rw-------      2  root       |
                     | -rw-------      2  weekly     |
                     +-------------------------------+

README is this file, and contains comments on the backup  system.  Backup.1
is  the  man  page for the backup program.  Backup.c is the source code for
the tar front end.  Backup.csh is a short c-shell front end that I  use  to
control  my  system  backups.  It uses the files daily, monthly, quarterly,
root, and weekly to store the set number of the type of backup I  use,  and
it  keeps  track  of the set I need to use.  I use 8 sets of daily, weekly,
and quarterly, and  I  use  6  sets  of  monthly  backups.  This  gives  me
reasonable  data  retention  for  up to 2 years without having to use large
numbers of tapes, and still keeps things relatively easy  to  use.  I  have
two root dump tapes, which I generate weekly so if one is bad I have a week
older one I can use.  Since my tar backups include  root,  these  aremainly
for  recovring  from catastrophic disk crashes where you can't use tar, and
to keep an image of the special files which cause tar to barf.

                 Caveats and other things you should know.

This program was written for a Vax running 4.1BSD.  I have tried to keep it
as  easy  as  possible  to get running on other machines, but since I don't
have access to them it is hard to say how  well  I  succeed.  If  you  have
flaky  problems one thing to look for is long values in ints (especially on
PDP11's).  This is a bad habit of mine and I have tried to avoid it, but  I
doubt I was completely successful.

This software is in the public domain.  I make no restriction on the use of
it  beyond  asking  that  credit  is  given where due.  I do not promise to
support it, enhance it, or fix any bugs that might (will)  show  up.  There
is  no  guarantee of usability of this software.  Use at your own risk, and
don't rely on it until you have gone over it thoroughly.  I do not  promise
to  support this software in any way, shape or form.  It was written by me,
and the company I work for has no responsibility for its contents.

Now that I have covered my tail (I hope) I want to let you know that I will
try  to  pass  along  any  enhancments  or fixes I do or get in the future,
although that is dependent upon my free time  and  my  companies  continued
belief in keeping this in the public domain.  If you do improve or fix this
program, please let me know.

    Chuck Von Rospach
    PacketCable, Inc.
    10411 Bubb Road
    Cupertino, Ca. 95014
    (408) 257-9963
'EOF'
echo extracting backup.1
cat > backup.1 <<'EOF'
.TH BACKUP 1
.SH NAME
backup \- Unix system backup program
.SH SYNOPSIS
.B backup
[
.B \-f
<filename>]
[
.B \-h
<header>]
[
.B \-i
]
[
.B \-n
]
[
.B \-r
]
[
.B \-s
]
[
.B \-v
]
[
.B \-V
]
[
B \-N
]
[
.B <file_name>
[
.B ...
]
]
.SH DESCRIPTION
.I Backup
is a front end for the
.B tar(1)
tape archive program. Its purpose is to map
the Unix directory structure of the given files ('.' is the default)
and send the files that need to be backed up to
.B tar
in a reasonable way.
.PP
This program gives
.B tar(1)
two new capabilities: it automatically does
multi-volume backups and it also does incremental backups. Multiple volume
support is rather crude because the size of the tape volume is hard coded
into the program. If the output device (default is
.B /dev/rmt8
and can be
overridden with \-f) is a special file then multiple volume handling is
done. If the output is to a standard file then no volume handling is done.
.PP
Incremental backups are done based on the creation date of the file
.I .Backup
in the current directory. If that file does not exist, a full backup is
done.
.PP
After each volume and at the end of the backup the tar file that was written
is read back and a formatted listing is sent to the standard output. This
can be disabled with the '\-s' option.
.PP
The flags to
.B backup
do the following:
.BS
.IP \-f
Takes the next parameter as the name of the output device. If the file exists
and it is a special device multiple volume checking is done. If it does not
exist it will be created. If the \-r option is not given an existing file
will be deleted before the first
.B tar
call.
.IP \-h
Takes the next parameter as the header to be put on the listing made of the
tar file. It should be in quotes, as in -h "This is a header".
.IP \-i
This will cause backup to write only those files that have been modified since
the creation date of the file
.B .Backup
in the current directory. If this file does not exist a full dump is done.
.IP \-n
This keeps backup from creating or updating the
.B .Backup
file. This can be used for full dumps when later incrementals will not be
done or if you don't want your incremental series to key off of this backup.
.IP \-r
This causes backup to make the first call to
.B tar
with
.B 'tar r'
instead of
.B 'tar c'
so that the files backed up are added to the tape instead of
overwriting the contents. If the file does not exist then the -r option
is disabled. This will fail horribly (and usually silently)
if the file is not a tar file.
.IP \-s
.B Backup
will not generate a listing of the tar file.
.IP \-v
.B Backup
will keep you informed of its process through the directory tree and
other pieces of choice debugging information.
.IP \-V
.B Backup
will babble wildly about everything except the weather. Useful only
for hopelessly confused debugging programmers.
.IP \-N
.B Backup
will notify the accounts defined in the group
.B 'operator'
in
.B /etc/group.
Every tty that is logged in under one of the operator accounts is notified
whenever backup needs a new tape or when backup finishes.
.SH FILES
.B .Backup
\- used to checkpoint incrementals
.br
.B .NewBackup
\- the file that is created while backup is in operation so that
.B .Backup
is not destroyed until the backup is successfully completed.
.br
.B /etc/group
\- for the group
.B 'operator'.
.br
.B /dev/rmt8
\- default tar output device.
.SH SEE ALSO
tar(1)
.SH DIAGNOSTICS
All diagnostics generated by tar are passed on by backup.
.PP
Output to a directory is frowned upon.
.PP
Backing up non-existent files is noted, and the backup continues
.SH BUGS
There should be some way of defining tape size on the command line.
.PP
If tar can be modified to accept filenames to be backed up from the
standard input then the whole thing can be made much simpler
.PP
Tar barfs horribly on special files, so backup has to check for them and
skip over them. This means that you cannot use backup on root and expect
to do a full system restore from it, so occasional dumps of root are still
neccessary.
'EOF'
echo extracting backup.c
cat > backup.c <<'EOF'
/*
 *  backup.c - source file  for the backup program. Backup is a front
 *      end written for tar(1) that will co-ordinate incremental backups
 *      and take care of multiple volume backups. Legal options for backup
 *      are:
 *
 *      -f <file_name> : file where tar image is to be created. The
 *          default is /dev/rmt8.
 *
 *      -h <header>    : This allows you to define a header that will be
 *          printed on the tar listing.
 *
 *      -i : Do an incremental backup, using the creation time of the
 *          '.Backup' file in the current directory as the cutoff time.
 *          if '.Backup' does not exist, a full backup is done.
 *
 *      -n : Do not update the '.Backup' file. This can be used when you
 *          take a full dump that is not part of the normal cycle and you
 *          do not want later incrementals to work from this tape.
 *
 *      -r : The first call to tar will be an 'r' instead of a 'c'. This
 *          allows you to add files to the back of an existing file or
 *          tape. Note that tar(1) barfs horribly if you attempt to
 *          do a 'tar r' on a tape or file where a 'tar c' has not been
 *          done already, and it does it silently.
 *
 *      -s : silent. Do not generate a listing of the tape contents.
 *
 *      -v : verbose. Clue in the user to everything going on.
 *
 *      -V : Very verbose. Clue in the user to even more.
 *
 *      -N : notify logged on users that are part of group 'operator' as
 *           defined in /etc/group. Whenever a tape change is required or
 *           when the program is done a message is sent to each tty logged
 *           on as one of the given accounts. If backup is running as root
 *           it will override 'mesg n'.
 *
 *      Parameters that do not start with '-' are taken to be files to be
 *      backed up. Once the first filename is read, parameter checking is
 *      ended so that a file such as '-file' can be backed up.
 *
 *      The program operates as follows:
 *
 *      Given parameters are parsed. If -f is given, and the filename
 *      is a special device volume handling is done as normal. If the
 *      file is not special and the '-r' option is not given the file
 *      is deleted before we write to it. If the file does not exist
 *      and the -r is given, -r is turned off.
 *
 *      If -i is given, stat .Backup to get the creation time to be used
 *      as the basis for the incremental. If .Backup is not there, the
 *      creation time is 0.
 *
 *      If this is a full backup and the -n is not given then create
 *      .NewBackup to store the creation time.
 *
 *      If a header is given, store it somewhere for later.
 *
 *      For each given filename (or '.' if no name is given) a recursive
 *      search is done. All files that have been changes since the given
 *      backup date are batched to the tar file.
 *
 *      Files are stored in a command string until the maximum size of that
 *      string is reached or the total bytes of the files would overflow the
 *      size of the tape as set by TAPELIMIT. The size of the command string
 *      is currently 10000 characters. This string is then sent to system().
 *
 *      Unless the -s option is given, when a call is made to save a file
 *      that would fill a tape the tape is read (by 'tar t') and the
 *      output sent through 'pr -h "given header"' and to the standard
 *      output.
 *
 *      Some special processing is done on full backups to minimize the
 *      number of calls that must be made to tar. Each directory is
 *      recursively searched and the number of bytes required to store that
 *      directory stored. When the total number of bytes to store a directory
 *      would overflow the tape then the routine returns and allows the search
 *      routine to recurse before trying the backup again. If the directory
 *      will fit on the tape then it is given sent to tar and we let tar's
 *      inherent recursive save back things up.
 *
 *      Please note that tar barfs horribly on special devices so there are
 *      patches in both the sizing routines and the directory searching
 *      routines to guarantee that we don't try to put a special file on
 *      the backup tape. If this doesn't make sense, try backing up /dev.
 *
 *      When the directory search is finished, a call is made to flush any
 *      pending output and a listing of the final tape volume is done.
 *
 *      Written by: chuck Von Rospach
 *                  PacketCable, Inc.
 *                  Cupertino, Ca.
 *
 *      Modification history:
 *
 *      When            Who             Why
 *      22 April 1983   cvr             implemented
 *      22 July  1983   cvr             fixed ugly bug when backing up a
 *                                      file with shell characters in it
 *                                      such as foo;init;bar, which causes
 *                                      backup to execute the 'init' command
 *                                      which makes life interesting when
 *                                      done by root.
 *  
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include <grp.h>
#include <time.h>
#include <ctype.h>
#include <stdio.h>

#define FALSE       0
#define TRUE        1
#define MAXLINE     255
#define BACKUP      ".Backup"         /* File which stores time of last run */
#define NEWBACKUP   ".NewBackup"      /* File created by backup during run */
#define TAPELIMIT   36000000          /* When to change tapes */
#define PMODE       0644              /* creation mode of files */
#define NCARGS      10240             /* # characters in exec arglist */
					/* This is stolen from <sys/param.h>
					 * and needs to be tailored for your
					 * system. (cfv)
					 */
#define CMDLIMIT    (NCARGS - 500)     /* size to execute tar command */
#define USAGE "Usage: backup [-f <file>] [-h \"header\"] [-i] [-n] [-r] [file [...]]"

char *strcpy(), *strcat(), *malloc(), *strsave(), *asctime(), *itoa(),
     *getenv();
FILE *fopen();
long size(), in_list();
struct tm *localtime();

/* size_list is used by the directory size mapping routines that help
 * reduce the number of calls we have to make to tar
 */
struct size_list {
    char* name;
    long size;
    struct size_list *next
};

/* dir_list is used by the backup routines to store the sub directories that
 * will have to be searched recursively when we finish with the current
 */
struct dir_list {
    char *dl_name;
    struct dir_list *dl_next;
};
int file_flg        = FALSE;     /* not the default tar device */
int incremental     = FALSE;     /* default is full backup */
int no_create       = FALSE;     /* default is to start tape with 'tar c' */
int notify_flg      = FALSE;     /* send comments to group 'operator' */
int one_volume      = FALSE;     /* do end of tape processing */
int silent          = FALSE;     /* default is to generate tape listings */
int tape_loaded     = FALSE;     /* no tape on the drive to start */
int tape_working    = FALSE;     /* there hasn't been a call to tar on this */
				 /* tape volume yet */
int verbose         = FALSE;     /* scream as you go */
int veryverbose     = FALSE;     /* babble wildly */
int  volume         = 0;         /* Which volume of tape are we on */

char output_device[MAXLINE];     /* alternate tar file location */
char header[MAXLINE]  = {'\0'};  /* header for tar listing */
char command[NCARGS] = {'\0'};   /* stores the command to execute tar */

long total_bytes = 0, tape_bytes = 0, batch_bytes = 0;

struct size_list *stop = NULL, *slast = NULL;

/* 
 */
main(argc,argv)
int argc;
char *argv[];
{
    int no_update       = FALSE;   /* default is to update .Backup */
    struct stat sbuf;              /* used for stat() */
    time_t backup_date  = 0;       /* date we do backups as of */
    struct tm *tms;                /* used to generate a time string */

    header[0] = '\0';
    while (--argc > 0 && (*++argv)[0] == '-') {   /* process arguments */
	switch(*(argv[0]+1)) {
	    case 'f':
		file_flg = TRUE;
		if (--argc > 0)
		    strcpy(output_device,(*++argv));
		else {
		    fprintf(stderr,"Backup: -f must have a filename\n");
		    goto usage;
		}
		break;
	    case 'h':
		if (--argc > 0)
		    strcpy(header,(*++argv));
		else {
		    fprintf(stderr,"Backup: -h must have a header\n");
		    goto usage;
		}
		break;
	    case 'i':
		incremental = TRUE;
		break;
	    case 'n':
		no_update = TRUE;
		break;
	    case 'r':
		no_create = TRUE;
		break;
	    case 's':
		silent = TRUE;
		break;
	    case 'V':
		veryverbose = TRUE;
	    case 'v':
		verbose = TRUE;
		break;
	    case 'N':
		notify_flg  = TRUE;
		break;
	    default:
		fprintf(stderr,"Backup: Illegal option '%s'.\n",*argv);
usage:
		fprintf(stderr,"%s\n",USAGE);
		exit(1);
		break;
	}
    }

/*
 * if they have given us the -f flag, see if the output file alread exists.
 * If it doesn't, turn off the -r flag and multi-volume checking since it
 * can't be a special device.
 *
 * If it is a directory, let them know and abort.
 * check to see if it is a special device. If it is, then volume checking
 * is done. If it isn't, and they haven't specified -r then delete the
 * old file before starting.
 */
    if (file_flg) {
	if (stat(output_device,&sbuf) == -1) {
	    no_create = FALSE;
	    one_volume = TRUE;
	}
	else {
	    if (is_dir(sbuf)) {
		fprintf(stderr,"Can't write output to a directory!\n");
		goto usage;
	    }

	    if (!is_dev(sbuf)) {
		one_volume = TRUE;
		if (!no_create)
		    unlink(output_device);
	    }
	}
    }

/* if -i was given, get the incremental date from ./.Backup. If it doesn't
 * exist, we default to 0 and get a full dump .
 *
 * If this is a full dump and the -n flag was not given, create ./.Newbackup
 * to set up the checkpoint time for incrementals off this full dump.
 *
 * Let them know what date tha backup is being made from.
 */
    if (incremental) {
	if (!stat(".Backup",&sbuf))
	    backup_date = sbuf.st_ctime;
    }
    else {
	unlink(NEWBACKUP);
	if (!no_update) {
	    register int f;

	    if ((f = creat(NEWBACKUP,PMODE)) == -1) {
		fprintf(stderr,"Backup: Can't creat %s. -n assumed.\n",
			NEWBACKUP);
		no_update = TRUE;
	    }
	    close(f);
	}
	backup_date = 0;
    }
    tms = localtime(&backup_date);
    fprintf(stderr,"Backup: backup date is %s",asctime(tms));

/* Load the first tape. If one_volume is set this will be a nop.
 *
 * If there are any arguments left on the command line then pass them to
 * the directory search routine one at a time if they are a directory, or
 * save them if they are files. If they don't exist, print a message and
 * skip them.
 *
 * If there are no files on the command line then back up the system starting
 * at '.'
 */
    load_tape(++volume);
    if (argc > 0) {
	while (argc) {
	    if (stat(*argv,&sbuf) == 0) {
		if (is_dir(sbuf))
		    read_dir(backup_date,*argv++);
		else
		    save_file(*argv++,(long)sbuf.st_size);
	    }
	    else
		fprintf(stderr,"Backup: File '%s' does not exist\n",*argv++);
	    argc--;
	}
    }
    else
	read_dir(backup_date,".");

/* The backup is complete so flush any unexecuted tar command.
 *
 * If -s is not set, generate the final part of the tape listing.
 *
 * if -i and -n are not set, we delete the old .Backup and rename .NewBackup
 * to .Backup so that future incrementals use the new checkpoint date.
 *
 * finally, print some statistics and notify the operators before exiting
 */
    write_tape();
    if (!silent)
	print_tape();

    if (!incremental && !no_update) {
	unlink(BACKUP);
	link(NEWBACKUP,BACKUP);
	unlink(NEWBACKUP);
    }
    printf("Total # of bytes written : %DK\n",total_bytes / 1000);
    fprintf(stderr,"Backup: Total # of bytes written : %DK\n",total_bytes / 1000);
    if (notify_flg)
	notify("Backup is finished!");

}
/* 
 *
 *  This function does a recursive search of the given directory and causes
 *  all files younger than (time_t date) to be saved to tape by tar. For
 *  full dumps date will be 0. If this is not an incremental dump then the
 *  routine tries to take safe shortcuts by checking the size of directories
 *  to see if they will fit on the tape whole. If so, then the directory is
 *  sent to tar, and we don't have to do as many tar calls (usually 1 per
 *  tape instead of 11 or 12 per tape without the searching).
 */
read_dir(date,dir_name)
time_t date;
char *dir_name;
{
    char filename[MAXLINE];
    int empty_dir = TRUE, is_root = FALSE;
    long dir_size;
    FILE *fp;
    struct stat sbuf;
    struct direct dir_ent;
    struct dir_list *top = NULL, *last = NULL, *ptr = NULL;

    if(verbose)
	fprintf(stderr,"Backup: checking directory %s.\n", dir_name);

    if (stat(dir_name,&sbuf) == -1) {
	fprintf(stderr,"Backup: Can't stat '%s'\n",dir_name);
	return;
    }

/* If the directory we are reading is root (/) then set is_root so that we
 * don't attempt to check its size. This is simply to reduce some of the
 * overhead involved in the size, and can be removed if your system will
 * fit on one tape
 */
    if (!strcmp(".",dir_name) && root_dir())
	is_root = TRUE;

    if (is_dev(sbuf)) {
	if (veryverbose)
	    fprintf(stderr,"Backup: Skipping device '%s'\n",dir_name);
	return;  /* skip devices */
    }

/* If this is a full dump and we are not at root level, look forward and see
 * if it is possible to use tar's recursive backup on this directory without
 * overflowing the tape. If so, save it and skip doing a file by file check.
 */
    if (!incremental && !is_root) {
	dir_size = size(dir_name);
	if ((tape_bytes + dir_size) < TAPELIMIT) {
	    save_file(dir_name,dir_size);
	    return;
	}
	else {
	    if (veryverbose)
		fprintf(stderr,"Backup: directory size for %s would overflow tape\n",
		    dir_name);
	}
    }

/* We have to do it the hard way, so open the file and prepare to read
 * the directory structure.
 */
    if ((fp = fopen(dir_name,"r")) == NULL) {
	fprintf(stderr,"Backup: cannot open directory '%d'. Skipped.\n",
	    dir_name);
	return;
    }

/* For each directory entry in this file, do the following:
 *
 * skip deleted files (inode = 0), also skip . and .. so we don't
 * loop forever.
 *
 *  Turn off empty_dir, which is used to make sure directories with nothing
 *  in them are still backed up to preserve the tree structure.
 *
 *  generate the pathname of this file, and get its stats. If stat fails
 *  (should never happen), then scream and ignore this file. Check to see
 *  if this is a device. If so, skip it.
 */
    while (fread((char *)&dir_ent,sizeof(dir_ent),1,fp)) {
	if (dir_ent.d_ino == 0
	  || strcmp(dir_ent.d_name,".") == 0
	  || strcmp(dir_ent.d_name,"..") == 0)
	    continue;
	empty_dir = FALSE;
	strcpy(filename,dir_name);
	strcat(filename,"/");
	strcat(filename,dir_ent.d_name);
	if (stat(filename,&sbuf) == -1) {
	    fprintf(stderr,"Backup: Can't stat '%s'\n",filename);
	    continue;
	}
	if (is_dev(sbuf)) {
	    if (veryverbose)
		fprintf(stderr,"Backup: Skipping device '%s'\n",filename);
	    continue;  /* skip devices */
	}

/* Check to see if this is a directory. If it is, store it in our directory
 * list so it can be checked recursively when we are done with this one.
 * It is done this way so we can close this directory file before opening the
 * next one because otherwise deeply nested directories will overflow the
 * number of available file pointers.
 *
 *  The directory list is a linked list of directories in this file, and the
 *  list addition routine is normal.
 */
	if (sbuf.st_mode & S_IFDIR) {
/* lint flags pointer alignment on malloc() */
	    if ((ptr = (struct dir_list *)malloc(sizeof(struct dir_list))) == NULL) {
		perror("backup");
		exit(1);
	    }
	    if (top == NULL)
		last = top = ptr;
	    else {
		last->dl_next = ptr;
		last = last->dl_next;
	    }
	    ptr->dl_name = strsave(filename);
	    ptr->dl_next = NULL;
	    continue;
	}

/* if we get this far, it is a normal file. Check the time it was last
 * changes against the backup date, and if this file is newer, save it.
 */
	if (sbuf.st_mtime > date || sbuf.st_ctime > date)
	    save_file(filename,(long) sbuf.st_size);
    }

/* We are done with the files in this directory, so close it. We then check to
 * see if there are any directories to be checked. If there is, then
 * recursivley call read_dir to check them out
 */
    fclose(fp);
    ptr = top;
    while (ptr != NULL) {
	read_dir(date,ptr->dl_name);
	ptr = ptr->dl_next;
    }
/* The final thing we do before exiting is check to see if something was
 * saved in this directory. If there wasn't, them empty_dir will be TRUE, and
 * we should save this directoy to make sure a restore will keep the same
 * tree structure. This is not donce by an incremental dump
 */
    if (empty_dir && !incremental)
	save_file(dir_name,0L);
}
/* 
 *
 *  This function will take the given filename and add it to the command
 *  string that will be sent to tar. If the string fills, we use a system
 *  call to execute it. End of tape handling is handled here when
 *  appropriate.
 */
save_file(filename,bytes)
char *filename;
long bytes;
{
    struct stat sbuf;

    if (veryverbose)
	fprintf(stderr,"Backup: saving file %s, size is %D\n",filename,bytes);

/* Take a stat of the file, and see if it is a directory. If it is, then
 *  delete it from the size list to keep that at a reasonable size.
 *  If the command string is null, then initialize it to 'tar c' or 'tar r'
 * depending on whether or not there has already been a tar executed on the
 * tape (we don't want to tar c twice, of course).
 *
 *  If the -f flag has been given, then pass it and the output file to the
 *  tar program.
 */
    stat(filename,&sbuf);
    if (is_dir(sbuf))
	delete_list(filename);

    if (!strlen(command)) {
	if (no_create || tape_working) {
	    strcpy(command,"nice tar r");
	}
	else {
	    strcpy(command,"nice tar c");
	    tape_working = TRUE;
	}

	if (file_flg) {
	    strcat(command,"f ");
	    strcat(command,output_device);
	}
    }

/* Put a space after the current command, and then append the filename after
 * it.
 */
    strcat(command," ");
/*
 *  22 July 1983 - bug fix. Because shell metacharacters can do horribly
 * strange things, put quotes around everything! (*sigh*) instead of just
 * around control characters and white space - cvr
 */
    strcat(command,"\"");
    strcat(command,filename);
    strcat(command,"\"");

/* Add the size of this file(directory) to the various counters.
 *
 * If the size of the command string is longer than it should be then
 * write this batch to the tape.
 */
    tape_bytes  += bytes;
    total_bytes += bytes;
    batch_bytes += bytes;
    if ((tape_bytes > TAPELIMIT) || (strlen(command) > CMDLIMIT))
	write_tape();
}
/*
 *  This function acutally does the system call, and checks to make sure that
 *  the tape is ready or that it needs to be changed.
 */
write_tape()
{
    fprintf(stderr,"Backup: writing tape - %DK bytes\n",batch_bytes/1000);
    batch_bytes = 0;

/* if command is null (possible on the clean up call in main()) then simply
 * return.
 */
    if (!strlen(command)) {
	if (veryverbose)
	    fprintf(stderr,"No command to system in write_tape. Returning\n");
	return;
    }

/* If the tape isn't loaded, load it. execute the system call on the command,
 * null the command string out since we are finished with it (this flags
 * save file to restart with 'tar r'. If one_volume is not set, we do
 * end of tape checking and print and change tapes if we have reached it.
 */
    if (!tape_loaded)
	load_tape(++volume);
    do_sys(command);
    command[0] = '\0';
    if (!one_volume)
	if (tape_bytes > TAPELIMIT) {
	    if (!silent)
		print_tape();
	    load_tape(++volume);
	}
}
/*
 *  This routine takes care of loading the next volume of tape on the
 *  drive. if -N has been given it screams at all operators through
 *  notify().
 */
load_tape(number)
int number;
{
    if (one_volume) {
	tape_loaded = TRUE;
	return;
    }

    if (notify_flg && (volume > 1))
	notify("Backup needs a new tape!");

    if (strlen(header))
	fprintf(stderr,"Backup: Please load tape labeled '%s',Volume %d\n",
		header,number);
    else
	fprintf(stderr,"Backup: Please load tape Volume %d\n",number);

    keypress();
    tape_loaded  = TRUE;
    tape_working = FALSE;
    tape_bytes = batch_bytes = 0;
}
/*
 *  This routine is called to generate a listing of the tar tape, which
 *  it does by executing the 'tar v' command and piping it through pr
 *  (with any defined header) and to the standard output.
 */
print_tape()
{
    char s[MAXLINE];

    fprintf(stderr,"Backup: Generating listing of tape ");
    if (!one_volume)
	fprintf(stderr,"volume %d",volume);
    fprintf(stderr,". %DK bytes\n",tape_bytes/1000);

    strcpy(s,"tar tv");
    if (file_flg) {
	strcat(s,"f ");
	strcat(s,output_device);
    }
    strcat(s," | cat -v | pr -f -h \"");
    strcat(s,header);
    if (!one_volume) {
	if(strlen(header))
	    strcat(s,",");
	strcat(s,"Volume #");
	strcat(s,itoa(volume));
    }
    strcat(s,"\"");
    do_sys(s);
}
/*
 *  This is a recursive function that is used by read_dir() to see if the
 *  entire directory can be stored on the tape at once rather than with
 *  multiple calls. If not, it returns TAPELIMIT to show read_dir that it
 *  goes over the limit.
 *
 *  This routine works with the routines in_list() and add_list(), which
 *  keep track of directory names and sizes of directories that have already
 *  been sized so we don't have to open the file and look it up a second time.
 *  save_file() will call delete_list() when it saves a directory so that
 *  this list can be kept to a reasonable size.
 *
 *  The structure of this function is very similar to read_dir(), and was
 *  actually kludged out of that routine.
 */
long size(dir)
char *dir;
{
    char filename[MAXLINE];
    struct stat sbuf;
    struct direct dir_ent;
    FILE *fp;
    long dir_size = 0;

    if(dir_size = in_list(dir)) {
	if (veryverbose)
	    fprintf(stderr,"Backup: returning size %d for %s\n",dir_size, dir);
	return(dir_size);
    }

    if (stat(dir,&sbuf) == -1) {
	fprintf(stderr,"Backup: Can't stat '%s'\n",dir);
	return(0);
    }

    if (is_dev(sbuf))
	return(TAPELIMIT);  /* tar barfs on devices, so do not let backup
			     * try a short-cut on this directory
			     */

    if ((fp = fopen(dir,"r")) == NULL) {
	fprintf(stderr,"Backup: cannot open directory '%d' in size. Skipped.\n",
	    dir);
	return(0);
    }

    while (fread((char *)&dir_ent,sizeof(dir_ent),1,fp)) {
	if (dir_ent.d_ino == 0
	  || strcmp(dir_ent.d_name,".") == 0
	  || strcmp(dir_ent.d_name,"..") == 0)
	    continue;
	strcpy(filename,dir);
	strcat(filename,"/");
	strcat(filename,dir_ent.d_name);
	if (stat(filename,&sbuf) == -1) {
	    fprintf(stderr,"Backup: Can't stat '%s'\n",filename);
	    continue;
	}

	if (is_dev(sbuf))
	    return(TAPELIMIT);  /* tar barfs on devices, so do not let backup
				 * try a short-cut on this directory
				 */

	if (sbuf.st_mode & S_IFDIR)
	    dir_size += size(filename);
	else
	    dir_size += sbuf.st_size;

	if (tape_bytes + dir_size > TAPELIMIT) {
	    if (veryverbose)
		fprintf(stderr,"Backup: limit exceeded in size. \n");
	    break;
	}
    }
    fclose(fp);
    add_list(dir,dir_size);
    return(dir_size);
}
/*
 *  This function returns the size of the given directory if it is already
 *  in the list, or 0 if it is not.
 */
long in_list(dir)
char *dir;
{
    struct size_list *ptr;

    ptr = stop;

    while (ptr != NULL) {
	if (!strcmp(dir,ptr->name))
	    return(ptr->size);
	ptr = ptr->next;
    }
    return(0);
}
/*
 *  This function adds the given directory to the size list with the given
 *  size.
 *  This is a reasonably standard linked list.
 */
add_list(dir,dir_size)
char *dir;
long dir_size;
{
    if (stop == NULL) {
/* lint flags pointer alignment on malloc() */
	if ((stop = (struct size_list *)malloc(sizeof(struct size_list))) == NULL){
	    fprintf(stderr,"Backup: Malloc failed in add_list.\n");
	    exit(1);
	}
	stop->name = strsave(dir);
	stop->size = dir_size;
	stop->next = NULL;
	slast = stop;
    }
    else {
/* lint flags pointer alignment on malloc() */
	if ((slast->next = (struct size_list *)malloc(sizeof(struct size_list))) == NULL){
	    fprintf(stderr,"Backup: Malloc failed in add_list.\n");
	    exit(1);
	}
	slast->next->name = strsave(dir);
	slast->next->size = dir_size;
	slast->next->next = NULL;
	slast = slast->next;
    }
    return;
}
/*
 *  This function is called to delete directories out of the size list
 *  list after they have been saved (since they aren't needed anymore) so
 *  they do not slow down the list search time any more.
 */
delete_list(dir)
char *dir;
{
    struct size_list *ptr;

loop:
    if (stop == NULL) {
	return;
    }

    if (!strncmp(dir,stop->name,strlen(dir))) { /* if we are deleting top of */
	ptr = stop;                            /* list - special case */
	stop = stop->next;
	cfree((char *)ptr);
	goto loop;
    }

    ptr = slast = stop;

    while (ptr != NULL) {
	if (!strncmp(dir,stop->name,strlen(dir))) {
	    slast->next = ptr->next;
	    cfree((char *)ptr);
	    ptr = slast->next;
	}
	else {
	    slast = ptr;
	    ptr = ptr->next;
	}
    }
}
/*
 *  This function is used for debugging only, and prints the size list
 *  starting at ptr.
 */
print_list(ptr)
struct size_list *ptr;
{
    if (ptr == NULL)
	return;
    fprintf(stderr,"Backup: list contains %s, %d\n",ptr->name,ptr->size);
    print_list(ptr->next);
}
/*
 *  This function does a system on the given command string and then looks
 *  at the returned status to make sure everything is all right.
 */
do_sys(com)
char *com;
{
    register int status, signal, rc;

    if (veryverbose)
	fprintf(stderr,"Backup: Executing %s\n",com);

    if (status=system(com)) {
	signal = status &0177;
	if (signal == 0) {
	    rc = (status>>8)&0377;
	}
	fprintf(stderr,"Backup: system() returned signal of %d, status of %d\n",
		signal,rc);
	exit(rc);
    }
}
/*
 */
char *itoa(n) /* integer to ascii conversion */
int n;
{
    char s[MAXLINE];
    register int i,sign;

    if ((sign = n) < 0)
	n = -n;
    i = 0;

    do {      s[i++] = n % 10 + '0';
    } while (( n /= 10) > 0);

    if (sign < 0 )
	s[i++] = '-';
    s[i] = '\0';
    reverse(s);
    return (strsave(s));
}
/*
 */
reverse(s) /* reverse string s in place */
char s[];
{
    int c,i,j;

    for (i = 0, j = strlen(s) - 1; i < j ; i++ , j--) {
	c = s[i];
	s[i] = s[j];
	s[j] = c;
    }
}
/*
 */
char *strsave(s) /* save string s somewhere */
char *s;
{
    char *p, *malloc(),*strcpy();

    if ((p = malloc((unsigned) strlen(s) +1)) != NULL)
	strcpy(p,s);
    return(p);
}
/*
 */
keypress()  /* wait for a newline from stdin */
{
    int c;

    fprintf(stderr,"Backup: Press <RETURN> when ready -");
    while ((c = getchar()) != '\n')
	if (c == EOF) {
	    fprintf(stderr,"Backup: EOF on input during tape load. Terminating.\n");
	    exit(-1);
	}
}
/*
 * has_white() is not currently used, so it is commented out.
 *  22 Jul 1983 - cvr
 */
#ifdef NOT_USED
has_white(s)  /* returns TRUE if the string includes ' ', \n, or \t */
char s[];
{
    register int i;

    for (i = 0; s[i] != '\0'; i++)
	if (isspace(s[i]))
	    return(TRUE);
    return(FALSE);
}
#endif NOT_USED
/*
 */
is_dev(buf)  /* returns TRUE if given stats show file is a special file */
struct stat buf;
{
    if ((buf.st_mode & S_IFCHR) == S_IFCHR
     || (buf.st_mode & S_IFBLK) == S_IFBLK)
	return(TRUE);
    return(FALSE);
}
/*
 */
is_dir(buf) /* returns TRUE if stats show given file is a directory */
struct stat buf;
{
    if ((buf.st_mode & S_IFDIR) == S_IFDIR)
	return(TRUE);
    return(FALSE);
}
/*
 */
root_dir() /* returns TRUE if the current directory is '/' */
{
    char cwd[MAXLINE];

    strcpy(cwd,getenv("cwd"));
    return(strcmp(cwd,"/"));
}
/*
 *  This function looks in /etc/group for the group 'operator', and then
 *  sends the given message to each terminal that is logged on to one
 *  of those accounts. If backup is run as root then it will override
 *  'mesg n', otherwise you can ignore it.
 *
 *  It figures out who is logged on and where by executing 'who' as a pipe
 *  and reading the output, and comparing the accounts to those in the
 *  operator list.
 */
notify(s)
char *s;
{
    char *opers[100];
    char **names;
    int no_opers = 0, i = 0;
    struct group *ptr, *getgrnam();
    FILE *pfp, *popen();
    char name[MAXLINE],tty[MAXLINE],junk[MAXLINE];
    char *fgets();

    name[0] = tty[0] = junk[0] = '\0';
    if ((ptr = getgrnam("operator")) == NULL)
	return;
    names = ptr->gr_mem;
    while (*names != NULL) {
	opers[no_opers++] = *(names++);
    }
    if ((pfp = popen("who","r")) == NULL)
	return;
    while (fscanf(pfp,"%s %s",name,tty) != EOF) {
	fgets(junk,MAXLINE,pfp);
	for (i = 0; i < no_opers; i++) {
	    if (!strcmp(name,opers[i]))
		send_notice(tty,s);
	}
    }
    pclose(pfp);
    return;
}
/*
 *  This function actually sends the notice for notify()
 */
send_notice(tty,notice)
char *tty, *notice;
{
    char device[MAXLINE];
    FILE *fp, *fopen();
    time_t clock, time();
    char *now, *ctime();

    time(&clock);
    now = ctime(&clock);
    strcpy(device,"/dev/");
    strcat(device,tty);

    if ((fp = fopen(device,"w")) == NULL)
	return;

    fprintf(fp,"\007Message from backup at %s\n\t%s\n\n\007",now,notice);
    fclose(fp);
}
'EOF'
echo extracting backup.csh
cat > backup.csh <<'EOF'
#! /bin/csh
cd '/'
set dir='/packet/etc/.backup'
set BACKUP=/packet/etc/backup
set TARLIST=/packet/etc/.backup/tarlist
set tty=`tty`
if (${tty} != '/dev/console') then
	echo "Must run on the console."
	exit 1
endif
if (!($#argv == 0)) then
usage:
    echo "Usage: backup"
    exit 1
endif
clear
menu:
echo System backup menu
echo
echo "1> daily"
echo "2> weekly"
echo "3> monthly"
echo "4> quarterly"
echo "5> root dump"
echo
echo -n "Please enter selection - "
set which = $<
switch ($which)
	case '1':
		set type='daily'
		breaksw
	case '2':
		set type='weekly'
		breaksw
	case '3':
		set type='monthly'
		breaksw
	case '4':
		set type='quarterly'
		breaksw
	case '5':
		goto rootdump
		breaksw
	case '':
		echo
		exit 0
		breaksw
	default:
		echo "Illegal option '$which'"
		goto menu
endsw
dobackup:
set tapeset = `cat ${dir}/${type}`
@ tapeset = ${tapeset} + 1
if (${tapeset} > 8) set tapeset=1
if ((${type} == 'monthly') && (${tapeset} > 6)) set tapeset=1
echo ${tapeset} > ${dir}/${type}
echo "Todays ${type} backup will be done on set #${tapeset}."
switch ($type)
    case 'daily':
	goto dodaily
	breaksw
    case 'weekly':
	goto doweekly
	breaksw
    case 'monthly':
	goto domonthly
	breaksw
    case 'quarterly':
	goto doquarterly
	breaksw
    default:
	echo "Illegal option in switch - $type"
	exit 1
	breaksw
endsw
exit -999

# do a quarterly backup
doquarterly:
set header = "Quarterly backup of system set number ${tapeset}"
$BACKUP -N -n -h "$header" | nlpr
exit

# do a monthly backup
domonthly:
set header = "Monthly backup of system set number ${tapeset}"
$BACKUP -N -n -h "$header" | nlpr
exit

# do a weekly backup
doweekly:
set header = "Weekly backup of system set number ${tapeset}"
$BACKUP -N -h "$header" | nlpr
exit

# do the daily incremental
dodaily:
set header = "Daily backup of system set number ${tapeset}"
$BACKUP -N -i -h "$header" | lpr
exit

#This section does a dump of the root filesystem
rootdump:
set tapeset = `cat ${dir}/root`
@ tapeset = ${tapeset} + 1
if (${tapeset} > 2) set tapeset=1
echo ${tapeset} > ${dir}/root
echo "Please load tape labeled root dump #${tapeset}"
echo -n Press return when ready -
set xyzzy=$<

echo "Dumping root"
nice +19 dump 0unfs /dev/rmt8 1800 /dev/rhp0a
set return=$status
if ($return != 1) then
dumperror:
	echo "Dump returned status of $return during backup."
	echo "Backup aborted. Please correct cause of tape error and restart."
	exit 1
endif

echo Generating listing of root backup.
set listing="Listing of root dump #${tapeset}."
nice +19 dumpdir | pr -h "$listing" | nlpr
set return=$status
if ($return != 0) goto dumperror
mt offl
'EOF'
echo extracting backup.diff
cat > backup.diff <<'EOF'
*** backup.old/backup.c	Fri Jul 22 10:23:18 1983
--- backup.new/backup.c	Fri Jul 22 10:59:42 1983
***************
*** 96,102
   *
   *      When            Who             Why
   *      22 April 1983   cvr             implemented
!  *
   *  
   */
  

--- 96,107 -----
   *
   *      When            Who             Why
   *      22 April 1983   cvr             implemented
!  *      22 July  1983   cvr             fixed ugly bug when backing up a
!  *                                      file with shell characters in it
!  *                                      such as foo;init;bar, which causes
!  *                                      backup to execute the 'init' command
!  *                                      which makes life interesting when
!  *                                      done by root.
   *  
   */
  
***************
*** 532,543
      }
  
  /* Put a space after the current command, and then append the filename after
!  * it. If the filename has spaces in it (Ingres does this *ugh*) then surround
!  * it by spaces. This will fail for filenames with control characters in them
!  * but since filenames shouldn't have control characters anyway I didn't
!  * try to fix this. It will show up during the backup, though, when tar tries
!  * to back up non-existent files, so you can track them down and get rid of
!  * them.
   */
      strcat(command," ");
      if (has_white(filename)) {

--- 537,543 -----
      }
  
  /* Put a space after the current command, and then append the filename after
!  * it.
   */
      strcat(command," ");
  /*
***************
*** 540,552
   * them.
   */
      strcat(command," ");
!     if (has_white(filename)) {
! 	strcat(command,"\"");
! 	strcat(command,filename);
! 	strcat(command,"\"");
!     }
!     else
! 	strcat(command,filename);
  
  /* Add the size of this file(directory) to the various counters.
   *

--- 540,553 -----
   * it.
   */
      strcat(command," ");
! /*
!  *  22 July 1983 - bug fix. Because shell metacharacters can do horribly
!  * strange things, put quotes around everything! (*sigh*) instead of just
!  * around control characters and white space - cvr
!  */
!     strcat(command,"\"");
!     strcat(command,filename);
!     strcat(command,"\"");
  
  /* Add the size of this file(directory) to the various counters.
   *
***************
*** 550,556
  
  /* Add the size of this file(directory) to the various counters.
   *
!  * IF the size of the command string is longer than it should be then
   * write this batch to the tape.
   */
      tape_bytes  += bytes;

--- 551,557 -----
  
  /* Add the size of this file(directory) to the various counters.
   *
!  * If the size of the command string is longer than it should be then
   * write this batch to the tape.
   */
      tape_bytes  += bytes;
***************
*** 909,914
  	}
  }
  /*
   */
  has_white(s)  /* returns TRUE if the string includes ' ', \n, or \t */
  char s[];

--- 910,917 -----
  	}
  }
  /*
+  * has_white() is not currently used, so it is commented out.
+  *  22 Jul 1983 - cvr
   */
  #ifdef NOT_USED
  has_white(s)  /* returns TRUE if the string includes ' ', \n, or \t */
***************
*** 910,915
  }
  /*
   */
  has_white(s)  /* returns TRUE if the string includes ' ', \n, or \t */
  char s[];
  {

--- 913,919 -----
   * has_white() is not currently used, so it is commented out.
   *  22 Jul 1983 - cvr
   */
+ #ifdef NOT_USED
  has_white(s)  /* returns TRUE if the string includes ' ', \n, or \t */
  char s[];
  {
***************
*** 920,925
  	    return(TRUE);
      return(FALSE);
  }
  /*
   */
  is_dev(buf)  /* returns TRUE if given stats show file is a special file */

--- 924,930 -----
  	    return(TRUE);
      return(FALSE);
  }
+ #endif NOT_USED
  /*
   */
  is_dev(buf)  /* returns TRUE if given stats show file is a special file */
'EOF'
echo extracting daily
cat > daily <<'EOF'
3
'EOF'
echo extracting monthly
cat > monthly <<'EOF'
6
'EOF'
echo extracting quarterly
cat > quarterly <<'EOF'
4
'EOF'
echo extracting root
cat > root <<'EOF'
2
'EOF'
echo extracting weekly
cat > weekly <<'EOF'
2
'EOF'
-- 
>From the dungeons of the Warlock:
					      Chuck Von Rospach
					      ucbvax!amd70!packet!cfv
					      (chuqui@mit-mc)  <- obsolete!