[unix-pc.sources] file backup BETA TEST version 1/1

spear@druco.ATT.COM (Steve Spearman) (10/09/89)

-----cut here----
#To extract the files contained herein, just execute
#this file via the command:
#  sh thisfile
#where 'thisfile' is the name of this file
echo extracting 1README
cat >1README <<'!FuNkYsTuFf'
This is the uback beta release.

Uback is a backup program for the AT&T UNIX-PC (TM).
It is file oriented and uses mountable disks; this is
as opposed to the format used by cpio and most other
backup routines.

See the man page and the notes file for more information.

  BECAUSE THE PROGRAM IS DISTRIBUTED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.

Here are the files:
1README - this file
Notes - some details about the program and its limitations
makefile - usual make instructions
uback.c - main routines
disk.c - disk related routines
filldisk.c - smarts (?) for fitting files on the floppy
uback.h - common included file
fullback - a simple example script for doing full backups
partback - another example script
uback.1 - man page

To try it:
1) examine the makefile and possibly add your favorite compiler or
shared library routines
2) type make
3) give it a shot
!FuNkYsTuFf
echo extracting Notes
cat >Notes <<'!FuNkYsTuFf'
Notes on the uback program

Highpoints:
----------
Does adjust for different floppy formats/sizes - does a 'df'
on the disk to determine available blocks.

If the file is already compressed (i.e. has a .Z), it
will not be compressed again but copied as is.  If the
file is smaller than 10 blocks (or other value passed to -C)
the file will not be compressed (since small files do not
typically gain much).

Files used:
----------
/mnt/1uback.script
/tmp/uback (with a long epoch time appended)
/tmp/uback.notdone
/tmp/Utemp (followed by an integer)

Limitations/Bugs/Errata:

Algorithm for fitting files to disks is not nearly optimal

Does not allow for files with names too long for
compress to tack on a .Z - they may just
fail to compress and the program will attempt to copy them
without compression.

Since all files are copied off potentially several directory
structures onto a single floppy directory, a kludgey
rename is used if a filename conflict occurs on a single
floppy.  The name Utemp followed by a number will be used
for the file name in this case.
For this Utemp file name as well as the script 1uback.script,
the names are assumed to be unique and no checking is done
on the input file list.

Does not yet handle files > disksize

Assumes availability of /mnt for mounting the floppy.

The restoral script just uses 'cp' and 'zcat' and
does not restore directory structures, though the information
about the structure is in the restoral command and could
be used to recreate such a structure.

Uses lots of system calls and requires lots of unix commands
to be available in standard places: cp, du, mount, dismount, etc..

Does not attempt to be doing compressions and analysis while
files are copying to the disk - this would be a possible enhancement
though the UnixPC seems to slow so much for floppy I/O that
I expect little real gain.

Does use full command path names except for compress and zcat which 
must be in the path somewhere if the compress option is used.
For unpacking, assumes the existence of 'zcat' which is
usually a link to compress that causes the uncompressed
version of the file to go to stdout.

Rather than checking the floppy size constantly, it does this
once at the beginning and then takes off the size of each file
being written.  It appears there is sometimes an error of 1
between what du reports for a file and what happens on a disk,
so we allow one extra block per file.  Also, we allow some blocks
for the restoral script we build plus some fudge factor.  You
could therefore end up with some wasted space.  If backing
up a LOT of small files with large names, you perhaps could
run out of space, but I've attempted to allow enough fudge
factor.   You can adjust the constant FACTOR up if you wish.
!FuNkYsTuFf
echo extracting disk.c
cat >disk.c <<'!FuNkYsTuFf'
#include "uback.h"

/* 
 * uback disk routines
 *   - see main file uback.c for information
 */

extern char s[];		/* general purpose string . */
extern int Mounted ;		/* Boolean - is disk mounted? */
extern int Debug ;		/* Boolean - print out debug? */
extern int Verbose ;		/* Boolean - print out verbose debug? */
extern char tempname[]; 	/* temporary file name for file list */
extern char temp2name[]; 	/* 2nd temporary file name for file list */
extern char Sname[];		/* script file for restoral of backup */
extern int Compress;		/* Boolean - compress files? */
extern int Complevel;		/* Compress files with at least X blocks */
extern int Count;		/* record count in ordered file */
extern int Left;		/* records (files) left in ordered file */
extern int Current;		/* record count in ordered file */
extern long Totalblocks;	/* total block count of all files to backup */
extern long Leftblocks;		/* total blocks of remaining files to backup */
extern FILE *Backuplist;	/* backup file listing pointer */
extern char Backupname[];	/* backup file listing name */
extern struct FileList filelist[];	/* list of file sizes in blocks */
extern int Diskno;		/* The sequential disk number */

/*
 * Function:	getfiles
 *
 * Does:	gets a list of files from standard in and eventually
 *		comes back with a file pointer pointing to a file of
 *		fseek-able records containing sorted block size and
 *		file names
 */

FILE *
getfiles()
{
char fname[FSIZE]; 	/* file name holder */
char command[FSIZE * FILESPERLINE + 90]; 	/* command for system */
int  fcount;		/* count of files on command line */
int  rc;
char *sptr;
int  len;
FILE *fptr;

	fcount = 0;
	/* make sure the temp file exists with no records */
	sprintf(command,"cat /dev/null > %s",tempname);
	VERBOSE(printf("VDBUG: Executing: %s\n",command));
	rc = system(command);

/* note that the system call to use du is done in a for loop
 * rather than passing all filenames as arguments to du.  This
 * avoids a problem with du giving a size for only one of several
 * linked files - we need the size for each since we are not
 * smart enough to link them. */
#define PCOMMAND "for F in"
	strcpy(command,PCOMMAND);	/* set up command prefix */

	/* read in file names */
	while (TRUE) {
		sptr = fgets(fname,FSIZE,stdin);
		if (sptr != NULL) {
			/* remove the \n in the name */
			len=strlen(fname);
			if (len > 0 && fname[len - 1] == '\n' ) {
				fname[len - 1] = '\0';
			}
			VERBOSE(printf("VDBUG: adding %s\n",fname));
			/* add file name to command line */
			strcat(command," ");
			strcat(command, fname);
			fcount++;
		}
		if ((fcount == FILESPERLINE) || 
		    ((sptr == NULL) && (fcount > 0))) {
			/* we also do a test to make sure each name is a file -
			 * otherwise it is ignored
			 */
			sprintf(s,"; do\n if test -f $F; then\n du -s $F\nelse\necho WARNING: $F - not a file, ignored >&2\nfi\ndone >> %s",tempname);
			strcat(command,s);
			VERBOSE(printf("VDBUG: Executing: %s\n",command));
			rc = system(command);
			if (rc != 0) 
				fprintf(stderr, "WARNING: bad return from: %s\n", command);
			/* reset command and count to beginning state */
			fcount = 0;
			strcpy(command,PCOMMAND);
		}

		if (sptr == NULL)
			break;		/* normal exit from loop */
	}

	/* Now we sort the files by the first field - blocksize   */
	/* sort in reverse order (high block first) and do unique.*/
	/* Output is in second tempfile, first is removed         */
	DEBUG(printf("DBUG: performing sort from %s to %s\n",tempname, temp2name));
	sprintf(command, "/bin/sort -nr %s | /usr/bin/uniq > %s && rm %s",
		tempname,temp2name,tempname);
	rc = system(command);
	if (rc != 0) 
		errexit("bad return from sort of tempfile");

	fptr=makeindex();	/* turn file into indexed in tempname */
	return(fptr);
}

/*
 * Function:	makeindex
 *
 * Does:	expects input file named in global variable temp2name
 *		reads in all lines and converts into an indexed file
 *		file stream whose pointer is returned (and whose name
 *		is taken from the variable tempname)
 *		Also, returns the count of records in 'Count' and 'Left'
 */

FILE
*makeindex()
{
	FILE *inptr;
	FILE *fptr;
	char record[RECSIZE];
	int size;

	inptr = fopen(temp2name, "r");	/* open for read */
	if (inptr == NULL) {
		fprintf(stderr,"File %s",temp2name);
		errexit("failed to open input tempfile for read");
	}

	fptr = fopen(tempname, "w+");	/* open for write and read */
	if (fptr == NULL)
		errexit("failed to open tempfile for update");

	Count = 0;
	DEBUG(printf("DBUG: Beginning index of file %s to %s\n",temp2name,tempname));
	/* copy over all lines into fixed size records in 2nd file */
	while (fgets(record, RECSIZE, inptr) != NULL) {
		sscanf(record,"%d%*s",&size);
		filelist[Count].size = (short) size;
		/* keep track of total block count */
		Totalblocks += (long) size;
		/* mark compressed with indication of not tried yet */
		filelist[Count].csize = USED;
		VERBOSE(printf("VDBUG: Read record size %d\n",filelist[Count].size));
		fwrite(record, sizeof(char), RECSIZE, fptr);
		Count++;
	}

	/* set the records left to count to start with */
	Left = Count;

	(void) fclose(inptr);

	(void) rewind(fptr);

	return(fptr);
}

/*
 * Function: 	mountdisk
 *
 * Does:	Anything it needs to mount a floppy as /mnt.
 *		Will do a mkfs each time as a way of clearing the
 *		disk and verifying its format.  Will optionally
 *		do a format first.
 */

void
mountdisk(blocks, inodes, fptr) 
int *blocks, *inodes;	/* (O) Blocks and inodes on mounted disk */
FILE *fptr;		/* (I) file pointer (for early end only) */
{
	int fd;
	FILE *fs;
	int rc;

	/* check for directory existence for mount point */
	if ( (fd = open("/mnt",O_RDONLY, 0)) < 0)
		errexit("Could not open directory /mnt");
	(void) close(fd);

	while (1) {
		printf("Enter Y to format the disk, Q to quit, or return for normal init\n");
		if ((rc = doagain()) == QUIT) {
			printf("halting as requested\n");
			earlyend(fptr);
		} else {
			fprintf(stderr, "Preparing disk...\n");
			if (rc == TRUE) {
				/* do format */
				if (diskinit() != 0)
					continue;
			}
		}
		/* always redo the mkfs for safety and a clean disk */
		sprintf(s,"/etc/mkfs %s >/dev/null",BFLOPPY);
		rc=system(s);
		if (rc != 0) {
			fprintf(stderr, "/etc/mkfs failed with return code %d - perhaps format or try another disk\n",rc);
			continue;
		}

		/* prepare mount command */
		Mounted = TRUE;
		sprintf(s,"mount %s /mnt",BFLOPPY);
		rc=system(s);
		if ( rc != 0 ) {
			dismount();	/* just in case */
			printf("Floppy mount to /mnt failed - perhaps try another disk\n");
			if (doagain() == QUIT) {
				printf("halting as requested\n");
				earlyend(fptr);
			}
			continue;
		} else
			break;	/* success */
	}

	/* do a df (free space check) and get blocks and inodes */
	fs=popen("/bin/df -t /mnt","r");
	if (fs == NULL)
		errexit("Bad return from '/bin/df'!");
	if (fscanf(fs, "%*s%*s%d%*s%d$*s",blocks, inodes) < 2) 
		errexit("Bad scan of blocks/inodes");
	(void) pclose(fs);

}

/* Function:	dismount
 *
 * Does:	dismounts disk if 'Mounted' indicates one is present
 *		and resets that variable to FALSE
 *
 */

void
dismount()
{
	if (Mounted == TRUE)
		(void) system("/etc/dismount -f");
	Mounted = FALSE;
}

/* 
 * function:	diskinit
 *
 * Does:	initializes disk using iv via system call
 *
 */
int
diskinit()
{
int rc;
	sprintf(s,"/etc/iv -i %s /usr/lib/iv/FD10nl",FLOPPY);
	rc=system(s);
	if (rc != 0) {
		fprintf(stderr, "/etc/iv failed with return code %d - perhaps need a new disk\n",rc);
	}
	return(rc);
}

/* 
 * Function:	copyfile
 *
 * Does:	copies the file passed as 'realname' to the disk
 *		mounted on /mnt via a system call.  Also writes
 *		restoral cp command to script 'Sname'
 *		Optionally removes file from hard disk.
 *
 */

int
copyfile(realname, wasname, iscompressed, n)
char *realname;		/* (I) the name of the file to copy from hard disk */
char *wasname;		/* (I) the name passed into the program */
int iscompressed;	/* (I) boolean - if TRUE, remove realname when done */
int n;			/* (I) a number for a filename if needed */
{
int rc;
char cpcommand[FSIZE * 2 + 60 ];	/* part of copy command */
	
	/* first, construct the kludge command to see if another file of */
	/* the same name exists on the floppy.  If so, we use the process*/
	/* ID instead as a file name. */
	sprintf(s,"A=/mnt/`basename %s`; if test -r $A ; then\n A=/mnt/Utemp%d;echo WARNING: using name $A to prevent duplicate name on floppy \n fi; ", realname,n);

	/* Now set up restoral command for script and also the actual copy */
	/* print out the original name as a progress report */
	if (iscompressed == TRUE) {
		printf("Copying %s (compressed)\n",wasname);
		sprintf(cpcommand,"/bin/echo \"zcat $A > %s\" >> %s; /bin/cp %s $A",
			wasname, Sname, realname);
	} else {
		printf("Copying %s\n",wasname);
		sprintf(cpcommand,"/bin/echo \"cp $A %s\" >> %s; /bin/cp %s $A",
			wasname, Sname, realname);
	}

	/* put the two halfs of the command together */
	strcat(s,cpcommand);

	/* Echo restoral command to script and do actual copy */
	VERBOSE(fprintf(stderr,"VDBUG: Executing %s\n",s));
	rc=system(s);

	/* remove the temporary compressed file */
	if (iscompressed == TRUE &&	/* check names just for safety */
	    strcmp(realname, wasname) != 0) {
		sprintf(s, "/bin/rm -f %s",realname);
		(void) system(s);
	}

	if (rc == 0) {
		/* now add location of this file to listing file */
		fprintf(Backuplist, "Disk %d: %s\n",Diskno, wasname);
		return(TRUE);
	} else {
		/* now add failure of this file to listing file */
		fprintf(Backuplist, "Disk %d: %s *FAILED*\n",Diskno, wasname);
		return(FALSE);
	}
}
!FuNkYsTuFf
echo extracting filldisk.c
cat >filldisk.c <<'!FuNkYsTuFf'
#include "uback.h"

extern char s[];	/* general purpose string for system calls and misc. */
extern int Mounted ;	/* Boolean - is disk mounted? */
extern int Debug ;		/* Boolean - print out debug? */
extern int Verbose ;		/* Boolean - print out verbose debug? */
extern char tempname[]; 	/* temporary file name for file list */
extern char temp2name[]; 	/* 2nd temporary file name for file list */
extern char Sname[];		/* script file for restoral of backup */
extern int Compress;		/* Boolean - compress files? */
extern int Complevel;		/* Compress files with at least X blocks */
extern int Count;		/* record count in ordered file */
extern int Left;		/* records (files) left in ordered file */
extern int Current;		/* record count in ordered file */
extern long Totalblocks;	/* total block count of all files to backup */
extern long Leftblocks;		/* total blocks of remaining files to backup */
extern FILE *Backuplist;	/* backup file listing pointer */
extern char Backupname[];	/* backup file listing name */
extern struct FileList filelist[];	/* list of file sizes in blocks */
extern int Diskno;		/* The sequential disk number Currently writing */

/* Function:	filldisk
 *
 * Does:	All the real brains (if any) of this program.
 *		Tries to fit files, possibly compressed, onto
 *		a disk with certain blocks and inodes and do
 *		so in some reasonably efficient way.
 *		Basically, it takes the sorted list of size
 *		from large to small and tries to fit them
 *		in by linearly trying each one in the list.
 *		Only compressed files are a wrinkle - to not
 *		have to compress them all at once and store
 *		them on hard disk, we only compress if we
 *		KNOW we can use the file right away.  This
 *		means the size must already fit on the disk.
 *		We allow one exception - we try all large
 *		files on the top of the list for each new disk
 *		until one won't fit after compress - it is
 *		saved around and put on the next disk as the
 *		first thing.
 *
 */
int
filldisk(blocks,inodes,fptr)
int blocks;
int inodes;
FILE *fptr;
{
char fname[FSIZE];	/* file name */
char newname[FSIZE];	/* name after compress */
char cpname[FSIZE];	/* name as copied */
static char lastname[FSIZE];	/* last filename compressed but not used */
int temp;
int diskblocks;		/* hold copy of initial available blocks */
int nblocks;		/* new block count after compress */
int iscompressed;	/* boolean - is file compressed already? */
int min;		/* smaller number of blocks for file */

	/* first, check for Current pointer equal to the Current */
	/* count - if so, we are done.  This should only happen  */
	/* if count is zero */
	if (Current >= Count) {	
		printf("NOTE: No files written on disk\n");
		return(NOMORE);
	}

	diskblocks = blocks;

	if (Compress == TRUE)
		printf("Compressing and copying files...\n");

	/* now get all the biggest ones until one won't fit */
	while (TRUE) {
		/* files may already be used in a previous pass - don't */
		/* bother with them at all */
		if (filelist[Current].size == USED) {
			goto advance;
		}

		iscompressed = NOTYET;

		/* check size of file - see if it will fit on the   */
		/* remaining space on this disk, or even fit on its */
		/* own.  As a side effect, compress will happen at  */
		/* this point if compress is on. This is the only   */
		/* way we will ever compress a file and not use it  */
		/* immediately (but save name for next pass) - if it*/
		/* is too big for this disk, but will fit on a new  */
		/* blank one. */
		/* First check if this is a file we already compressed */
		/* and could not fit on the last pass (there can only  */
		/* be one of these */
		if (filelist[Current].csize != USED &&	/* compress tried */
		   filelist[Current].csize != FAILED) {	/* compress failed */
			min = filelist[Current].csize;
			iscompressed = TRUE;
			/* restore name from previous compress */
			strcpy(newname, lastname);
			DEBUG(fprintf(stderr,"DBUG: Already compressed file, saved name: %s\n",newname));
			/* below check for min should never happen */
			/* for this case unless perhaps a smaller  */
			/* disk is being used this time.  If so,   */
			/* the compress will be done again for no  */	
			/* reason - oh well */
		} else {
			min = filelist[Current].size;
		}

		/* read in file name */
		getname(fptr, Current, filelist[Current].size, fname);

		/* if file may be too big, must do some checking and */
		/* a possible compress and see if still too big */
		if (min >= blocks) {
			if (Compress == TRUE &&
			    filelist[Current].csize != FAILED) {
				iscompressed = docompress(fname, min, 
					newname, &nblocks);
				if (iscompressed  == TRUE) {
					DEBUG(fprintf(stderr, "DBUG: compress success on %s was %d now %d\n", 
						fname, min, nblocks));
					/* on success, store compressed size */
					min = nblocks;
					filelist[Current].csize = nblocks;
				} else {
					DEBUG(fprintf(stderr, 
					  "DBUG: compress fail on %s\n", fname));
					/* remember failure so we don't try */
					/* again */
					filelist[Current].csize = FAILED;
				}
			}

			/* now check if file is too big for whole disk */
			if (min > diskblocks) {
				fprintf(stderr, "WARNING: file %s too big - not copied\n",fname);
				/* mark file as not usable */
				filelist[Current].size = USED;
				filelist[Current].csize = USED;
				Left--;	/* keep track of files remaining */
				if ( iscompressed  == TRUE ) {
					/* save some space and cleanup the */
					/* compressed file now */
					sprintf(s, "/bin/rm -f %s", newname);
					(void) system(s);
				}
				goto advance;
			}
			/* if we got here, the file COULD fit on a disk */
			/* but maybe not on THIS disk.  */
			if (min >= blocks) {
				/* save name to use on next new disk */
				DEBUG(fprintf(stderr,"DBUG: File %s too big %d\n",
					fname, min));
				/* save name for compress case */
				if (iscompressed == TRUE) {
					strcpy(lastname, newname);
					DEBUG(fprintf(stderr,"DBUG: Save name: %s\n",lastname));
				}
				break;	/* quit advancing Current pointer */
					/* and go to 'temp' advancement   */
			}
			/* otherwise, we have a file we can handle now */
		}

		/* if we got here, we have a file that will fit. */
		/* it may or may not have been compressed yet */
		/* now do compress for files if not yet tried */
		if (Compress == TRUE && iscompressed == NOTYET &&
			    filelist[Current].csize != FAILED) {
			iscompressed = docompress(fname, min, 
				newname, &nblocks);
			if (iscompressed  == TRUE) {
				DEBUG(fprintf(stderr, "DBUG: compress success on %s was %d now %d\n", 
					fname, min, nblocks));
				/* on success, store compressed size */
				min = nblocks;
			} else {
				DEBUG(fprintf(stderr, 
				  "DBUG: compress fail on %s\n", fname));
			}
		}
		/* for compressed files, get the newname */
		if (iscompressed == TRUE) {
			strcpy(cpname,newname);
			/* min will already be set to compressed size*/
		} else {
			getname(fptr,Current,filelist[Current].size,fname);
			strcpy(cpname,fname);
			min = filelist[Current].size;
		}

		/* show the Current file as used */
		filelist[Current].size = USED;
		filelist[Current].csize = USED;
		Left--;	/* keep track of files remaining */

		/* subtract blocks regardless of whether it works or not */
		Leftblocks -= min;

		/* copy it to disk */
		if ( copyfile(cpname, fname, iscompressed, Current) == TRUE) {
			/* account for blocks and inode - for */
			/* blocks, allow for a rounding error */
			/* that seems to occur sometimes */
			blocks -= (min + 1);
			inodes--;
			DEBUG(printf("DBUG: Added %s, size %d, left %d blocks, %d inodes\n",cpname, min, blocks, inodes));
		} else {
			fprintf(stderr,"ERROR: copy failed on %s, not backed up!\n",fname);
			blocks = 0;	/* force new disk */
			break;
		}

advance:
		Current++; 	/* move the beginning pointer along */
		if (Current >= Count) {	/* all done! */
			printf("Normal end of all files\n");
			return(NOMORE);
		}
		/* check for inode capacity also */
		if (inodes <= 1) {
			DEBUG(printf("DBUG: Filled disk inodes from Current\n"));
			return(MORE);
		}
	}

	/* Since we got here, we have some more files but the next
	 * big one will not fit.  So go down the list and look for
	 * those that will fit.
	 */
	temp = Current + 1;
	while (temp < Count) {
		/* temp counter files will never yet have been compressed.   */
		/* Now only compress a file that would fit anyway - not real */
		/* optimal but it saves doing extra compresses or having     */
		/* to store the names of those that have already been        */
		/* compressed. */
		min = filelist[temp].size;
		if ((min != USED) &&
		    (min < blocks)) {	/* allow one extra block at least */

			/* get file name */
			getname(fptr,temp,filelist[temp].size,fname);
			/* put default name for file in cpname */
			strcpy(cpname,fname);

			/* and do compress if appropriate */
			if (Compress == TRUE ) {
				iscompressed = docompress(fname, min, 
					newname, &nblocks);
				if (iscompressed  == TRUE) {
					/* on success, use compressed size */
					DEBUG(fprintf(stderr, "DBUG: compress success on %s was %d now %d\n", fname,min,nblocks));
					min = nblocks;
					strcpy(cpname,newname);
				} else {
					DEBUG(fprintf(stderr, 
					  "DBUG: compress fail on %s\n", fname));
				}
			}

			/* At this point fname has either the original or */
			/* compressed name and min has the corresponding  */
			/* blocks */

			/* mark this file as used */
			filelist[temp].size = USED;
			filelist[temp].csize = USED;
			Left--;	/* keep track of files remaining */
			/* subtract blocks whether it works or not */
			Leftblocks -= min;

			/* copy it to disk */
			if ( copyfile(cpname, fname, iscompressed, temp) == TRUE) {
				/* account for blocks and inode - for */
				/* blocks, allow for a rounding error */
				/* that seems to occur sometimes */
				blocks -= (min + 1);
				inodes--;
				DEBUG(printf("DBUG: Added %s, size %d, left %d blocks, %d inodes\n",cpname, min, blocks, inodes));
			} else {
				if ((blocks - min) < 10) {
				/* if we are almost at the end of the disk,  */
				/* assume this was a fit problem due to size */
				/* discrepancy and just go on to next disk   */
					DEBUG(fprintf(stderr,
					   "DBUG: last copy FAILED from temp - assuming size\n"));
					return(MORE);
				} else {
					fprintf(stderr,"ERROR: copy failed on %s, not backed up!\n",fname);
					blocks = 0;	/* force new disk */
				}
			}

			/* If blocks is zero, this will fall out of the */
			/* loop bottom eventually, but we'll check here */
			if (blocks <= 0) {
				DEBUG(fprintf(stderr,
				   "DBUG: Filled disk blocks exactly from temp\n"));
				return(MORE);
			}
			if (inodes <= 1) {
				DEBUG(fprintf(stderr,
					"DBUG: Filled disk inodes from temp\n"));
				return(MORE);
			}
		}
		temp++;
	}
	DEBUG(fprintf(stderr, "DBUG: Filled Current disk blocks with temp\n"));
	/* because we had to skip at least one 'Current' file, we know */
	/* we are not done yet */
	return(MORE);
}

!FuNkYsTuFf
echo extracting fullback
cat >fullback <<'!FuNkYsTuFf'
#example script 
#simple full backup - don't back up things we already have or that
# are temporary/volatile
echo "Should be run as ROOT"
touch $HOME/.fullback
find / -type f -print | \
egrep -v "/tmp|mnttab|utemp|pwcntl|wtmp|^/usr/spool/news|/usr/spool/lp/request|/usr/spool/uucp/.Log|/usr/spool/uucp/.Status|/usr/lib/ua/STORE|LCK\.\." | \
uback -c $*
!FuNkYsTuFf
chmod +x fullback
echo extracting makefile
cat >makefile <<'!FuNkYsTuFf'
# Makefile for the uback unix-pc backup program
# You may wish to change the CC to your favorite shared
# library compiler

CFLAGS= -g 
#CFLAGS= -O
OBJ= disk.o uback.o filldisk.o

all: uback

uback:	$(OBJ)
	$(CC) $(CFLAGS) -o uback $(OBJ)

disk.o: disk.c uback.h

uback.o: uback.c uback.h

filldisk.o: filldisk.c uback.h

clean:
	rm -f *.o nohup.out core

superclean:
	rm -f *.o nohup.out core uback uback.cpio uback.shar

backup:
	ls *.c *.h makefile *.1 Notes 1README partback fullback | uback -c

shar:
	ls *.c *.h makefile *.1 Notes 1README partback fullback | mextract uback.shar

cpio:
	ls *.c *.h makefile *.1 Notes 1README partback fullback | cpio -oc > uback.cpio
!FuNkYsTuFf
echo extracting partback
cat >partback <<'!FuNkYsTuFf'
#example partial backup script - don't back up things we already have or that
# are temporary/volatile
echo "Should be run as ROOT"
find / -newer $HOME/.fullback -type f -print | \
find / -type f -print | \
egrep -v "/tmp|mnttab|utemp|pwcntl|wtmp|^/usr/spool/news|/usr/spool/lp/request|/usr/spool/uucp/.Log|/usr/spool/uucp/.Status|/usr/lib/ua/STORE|LCK\.\." | \
uback -c $*
!FuNkYsTuFf
chmod +x partback
echo extracting uback.1
cat >uback.1 <<'!FuNkYsTuFf'
.TH UBACK 1 local
.SH NAME
uback \- copy (backup) files to the UNIXPC floppy
.SH SYNOPSIS
.B uback
[
.B -c
] 
[
.B -C N
]
[
.B -h
]
.SH USAGE
-c indicates to compress files where practical
.br
-C followed by an integer gives the minimum block size for
which a compress should be attempted (default is 10)
.br
-h gives a synopsis of the command line options
.SH DESCRIPTION
.I Uback
takes a list of files as standard input and copies them to
mountable UNIX(TM)PC floppies.
It analyzes all files and sorts them internally to try and
pack them reasonably efficiently onto multiple floppies.
.PP
Unlike large cpio backups,
.I Uback
writes separately to each floppy, so you can retrieve
files by just using one floppy later.
The files are written to a single directory on the floppy
and a restoral script 
.I 1uback.script
is also written to the diskette - this can be executed to
put files back where they belong.
.PP
Since duplicate filenames on a single diskette would be a
problem,
.I uback
will use a temporary name
.I Utemp
(followed by a number) for any duplicates that may arise - the
restoral script will still restore them to their correct name.
.PP
The optional compression is done in a temporary directory,
and, if successful, the compressed version is copied to
disk.
When compressing, the program recognizes the .Z suffix and
will not attempt to re-compress such files.
The compress option requires the presence of the 
.I compress 
command somewhere in the PATH.
The restoral script assumes the presence of the uncompress
command
.I zcat.
.PP
The program assumes the floppy is mounted as /mnt.
You may need to create such a directory if one does not already
exist.
.PP
Each floppy can optionally be formatted.  In any case, mkfs
is run to initialize the file system and erase the disk.
.PP
The program gives you a running dialog of how many files
still to copy and how many blocks remain.  You can make some
rough estimates of how many floppies will be required, but
compressing, inode limitations, and packing
inefficiencies makes this mostly a guess.  My floppies
fit about 780 or so blocks apiece.
.PP
If you are in the middle of a big backup and have to quit, giving
the 'Q' option between diskettes will cause the 
.I /tmp/uback.notdone
file to be written out with a list of all files not yet copied.
This can then be used as input for a subsequent invocation of
.I uback
to continue the backup.
.SH EXAMPLE
find / -print | uback -c 
.br
.in +5
this will back up all files on the system and attempt to compress them.
Note that there are some files you may not actually want to back
up for a full backup - see the 'fullback' script included with
this package for an example.
.in -5
.sp
ls *.c | uback -C20
.br
.in +5
This will backup all .c files in the current directory and will
try to compress them if they are at least 20 blocks in size.
.in -5
.sp
To restore files:
.sp
.in +5
.nf
Search /tmp/uback* for the file(s) and find the floppy number.
Insert the floppy.
Do: mount /dev/fp021 /mnt
Then: ls /mnt
Then do: 'cp', or execute the /mnt/1uback.script, etc.
And when done: dismount -f
.fi
.in -5
.SH FILES
/mnt/1uback.script - restoral script on the floppy.
.br
/mnt/Utemp[NUMBER] - substitute file name used for duplicate file names
.br
/tmp/uback[EPOCHTIME] - hard disk record of what files were put where
.br
/tmp/uback.notdone - list of files NOT backed up if programs quits early (and
cleanly)
.SH SEE ALSO
cpio(1), compress(1local)
.SH BUGS AND LIMITATIONS
Assumes its files are unique and doesn't handle any user
files with the same names.
Depends on lots of UNIX-PC files and commands being in the
normal places.
Does not backup files bigger than a single floppy (but will
warn you that it couldn't do them).
Backs up files, not directory structures per se, so this probably is not
appropriate for your only full backup.
See also the Notes file.
!FuNkYsTuFf
echo extracting uback.c
cat >uback.c <<'!FuNkYsTuFf'
#include "uback.h"

/*
 * Program:	uback
 *
 * Version: 	0.5
 *
 * Does:	provides backup facilities for an AT&T UNIX-PC (TM)
 *		aka 7300 or 3b1 using the built-in floppy drive.
 *		Takes a list of file names in stdin and attempts
 *		to fit them in a reasonably efficient manner on
 *		a series of mountable floppy disks.  Also produces
 *		a record of all files and what disk they are on
 *		in /tmp, plus a restoral file on each floppy
 *
 * Author:	S. G. Spearman   spear@booboo.att.com  9/26/89
 *
 * Released into the Public Domain
 *   BECAUSE THE PROGRAM IS DISTRIBUTED FREE OF CHARGE, THERE IS NO WARRANTY
 * FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
 * OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
 * PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
 * OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
 * TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
 * PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
 * REPAIR OR CORRECTION.
 *
 * History:
 *	9/26/89 - version 0.5 beta out for test
 *	10/9/89 - docs upgraded, version 0.6 beta out for test
 *
 */

#define NAME	"uback"	/* name of this backup program */
char s[SSIZE];		/* general purpose string for system calls and misc. */
int Mounted = FALSE;	/* Boolean - is disk mounted? */
int Debug = FALSE;	/* Boolean - print out debug? */
int Verbose = FALSE;	/* Boolean - print out verbose debug? */
int Compress = FALSE;	/* Boolean - Files should be compressed ? */
int Complevel = 10;	/* Compress files with at least this many blocks */
char tempname[L_tmpnam]; 	/* temporary file name for file list */
char temp2name[L_tmpnam]; 	/* 2nd temporary file name for file list */
char dirname[L_tmpnam]; 	/* temporary directory name for compressing */
char Sname[] = "/mnt/1uback.script";	/* script for restoring backup */
char Savename[] = "/tmp/uback.notdone";	/* script for continuing backup */
int Count;		/* record count in ordered file */
int Left;		/* records (files) left in ordered file */
int Current = 0;	/* record count in ordered file */
long Totalblocks = 0;	/* total block count of all files to backup */
long Leftblocks = 0;	/* total block count of remaining files to backup */
struct FileList filelist[MAXFILES];	/* list of file sizes in blocks */
FILE *Backuplist;	/* pointer to backup listing file */
FILE *Saveptr;		/* pointer to a list of files not saved */
char Backupname[20];	/* name of backup listing file */
int Diskno;		/* The sequential disk number Currently writing */

extern void initscript();
extern void initlist();
extern void initsave();
extern long time();

FILE *Terminal;


main(argc, argv)
int argc;
char **argv;
{
int c;
extern int optind;	/* getopt arguments */
extern char *optarg;
int blocks;	/* number of blocks on a disk */
int inodes;	/* and inodes */
FILE *fptr;	/* pointer to ordered file */

	/* process arguments */
	while ((c = getopt(argc, argv, "DdhcC:")) != EOF) {
		switch (c) {
		case 'D':
			Verbose = TRUE;
			/* Fall through */
		case 'd':
			Debug = TRUE;
			break;
		case 'c':
			Compress = TRUE;
			break;
		case 'C':	/* compress level - what block minimum */
			Complevel = atoi(optarg);
			printf("Compressing files bigger than %d blocks\n",
				Complevel);
			Compress = TRUE;
			break;
		case '?':
		case 'h':
			printf("Usage: %s [-c] [-C N] [-h]\n",NAME);
			exit(0);
			break;
		}
	}

	Mounted = FALSE;	/* no disk loaded to start */

	doinit();		/* initialize signals, etc. */ 

	printf("Analyzing files..\n");
	fptr = getfiles();	/* create a list of all the files */

	DEBUG(fprintf(stderr, "DBUG: output is in %s\n",tempname));

	Leftblocks = Totalblocks;	/* init block counts for backup */

	Diskno = 0;
	do {
		dismount();	/* remove any disk already in */

		Diskno++;
		printf("%d of %d files, %ld of %ld blocks (uncompressed) remains\n",Left,Count,Leftblocks,Totalblocks);
		printf("Please put in disk number %d\n",Diskno);

		/* get a disk loaded and returns blocks and inodes available */
		mountdisk(&blocks,&inodes, fptr);
		DEBUG(fprintf(stderr, "DBUG: blocks is %d, inodes is %d\n",
			blocks, inodes));

		blocks -= FACTOR;	/* allow room for script */
		initscript();	/* get another restoral script started */

		/* and then filldisk actually puts it on the disk */
	} while (filldisk(blocks,inodes,fptr) == MORE);

	cleanup();
	exit(0);
}

/*
 * Function:	getname
 *
 * Does:	gets a file name from off the list created in an
 *		indexed file.  Takes the record number and a block
 *		value used as verification and returns the
 *		filename in fname.
 */

getname(fptr,num,size,fname)
FILE *fptr;	/* (I) file pointer from which to read */
int num;	/* (I) record number to read */
short size;	/* (I) block number to expect in record read */
char fname[];	/* (O) filename read */
{
int lsize;
	if (fseek(fptr,(long) (num * (RECSIZE)), 0) != 0)
		errexit("bad fseek on file");
	if (fscanf(fptr,"%d%s",&lsize,fname) < 1)
		errexit("bad fscanf after fseek");
	if (size != lsize) {
		DEBUG(fprintf(stderr,
			"DBUG: Record %d, Read %d, expected %d\n",num, lsize, size));
		errexit("Internal file size mismatch!");
	}
	return;
}

/* Function: docompress
 *
 * Does:	tries to compress file after copying it to a
 *		temporary directory.  If du returns a block size
 *		we assume the file compressed ok, if it returns
 *		0 there was some problems.  If OK, the name of
 *		the file is returned along with the blocksize
 *
 *		This routine will return FALSE immediately if
 *		the file size is already below Complevel or
 *		if the file has a .Z suffix
 */

int
docompress(fname, inblocks, outname, blocks)
char fname[]; 	/* (I) file name holder */
int inblocks;	/* (I) input blocks for sanity check */
char *outname; 	/* (O) output file name holder */
int *blocks;	/* (O)	blocks of the compressed file */
{
FILE *fs;
int result = TRUE;	/* default success return */
int len;

	/* check for cases to fail immediately */
	if (inblocks < Complevel)
		return(FALSE);
	/* check for .Z suffix indicating already compressed file */
	len = strlen(fname);
	if (len >= 3 && fname[len-1] == 'Z' && fname[len-2] == '.')
		return(FALSE);

	/* copy file to temporary directory and then du it */
	sprintf(s,"A=%s/`/bin/basename %s`; /bin/cp %s $A; compress $A 2>/dev/null; /bin/du -s $A.Z",
		dirname, fname, fname);
	fs=popen(s,"r");
	if (fs == NULL)
		errexit("Bad return from compress '/bin/du'!");
	if (fscanf(fs, "%d%s",blocks, outname ) < 2) {
		fprintf(stderr, "WARNING: compress problem with %s\n",
			fname);
		result = FALSE;
	} else {
		if (*blocks <= 0)
			result = FALSE;
		if (*blocks > inblocks) {
			fprintf(stderr, "WARNING: compressing file %s failed - made it bbigger??\n",fname);
			result = FALSE;
			DEBUG(fprintf(stderr, "DBUG: New blocks %d, was %d\n",
				*blocks, inblocks));
		}
	}
	(void) pclose(fs);

	/* cleanup if we did not do compress ok */
	if (result == FALSE) {
		/* we don't remove $A.Z because there is a SMALL chance */
		/* that the failure is due to a file of the same name   */
		/* already compressed in the temp directory.            */
		sprintf(s,"A=%s/`/bin/basename %s`; /bin/rm -f $A",
			dirname, fname);
		(void) system(s);
	}
	return(result);
}

/*
 * Function:	errexit
 *
 * Does:	your basic abort if an error occurs
 */

void
errexit(str)
char *str;
{
	fprintf(stderr,"%s\n",str);
	cleanup();
	exit(1);
}


/* 
 * function:	doagain
 *
 * Does:	gets string from Terminal - if first character is Y
 *		it returns TRUE, else FALSE
 *
 */
int
doagain() {
#define RSIZE 5
char response[RSIZE];
	(void) fgets(response, RSIZE, Terminal);
	if (response[0] == 'y' || response[0] == 'Y') {
		return(TRUE);
	} else if (response[0] == 'q' || response[0] == 'q') {
		return(QUIT);
	} else {
		return(FALSE);
	}
}

/* 
 * Function:	sighandle
 *
 * Does:	handles a signal - basically quits
 */

sighandle()
{
	/* ignore any repeats */
	signal(SIGHUP, SIG_IGN);
	signal(SIGINT, SIG_IGN);
	signal(SIGQUIT, SIG_IGN);
	signal(SIGTERM, SIG_IGN);
	/* hope we are where system calls will work! */
	cleanup();
	exit(1);
}

/*
 * Function:	doinit
 *
 * Does:	initialize variables, set signal handling and
 *		open up a stream to the terminal for input from
 *		the user.  Also opens backuplist file.
 */

doinit()
{
int rc;
	signal(SIGHUP, sighandle);
	signal(SIGINT, sighandle);
	signal(SIGQUIT, sighandle);
	signal(SIGTERM, sighandle);

	(void) tmpnam(tempname);
	(void) tmpnam(temp2name);

	/* make a temporary directory for compressing files into */
	if (Compress == TRUE) {
		(void) tmpnam(dirname);
		sprintf(s,"mkdir %s",dirname);
		rc=system(s);
		if (rc != 0) {
			fprintf(stderr, "Temporary directory: %s\n",dirname);
			errexit("Failed to mkdir temp directory - halting");
		}
	}
	
	/* get a file descriptor for the terminal */
	Terminal = fopen( "/dev/tty", "r" );
	if (Terminal == NULL) {
		errexit("Failed to open /dev/tty - halting");
	}

	/* now create a backup listing file on the hard disk */
	initlist();

	/* and create a file for unsaved files in case of early termination */
	initsave();
}

/*
 * Function:	earlyend
 *
 * Does:	saves all files not already backed up.  This is done
 *		on a requested halt while between disks and allows the
 *		list to be used on a subsequent request.
 */

void
earlyend(fptr)
FILE *fptr;
{
char fname[FSIZE];	/* file name */
	while (Current < Count) {
		if (filelist[Current].size != USED) {
			/* read in file name */
			getname(fptr, Current, filelist[Current].size, fname);
			fprintf(Saveptr,"%s\n",fname);
		}
		Current++;
	}
	(void) fclose(Saveptr);

	printf("See the file %s for a list of files NOT backed up\n", Savename);

	cleanup();	/* other common ending stuff */
	exit(2);
}

/* 
 * Function: cleanup
 *
 * Does:	cleanup all the temp files and directories that are around 
 */

void
cleanup()
{
	if (Compress == TRUE) {
		sprintf(s, "/bin/rm -rf %s %s %s",tempname, temp2name, dirname);
	} else {
		sprintf(s, "/bin/rm -rf %s %s",tempname, temp2name);
	}
	(void) system(s);
	dismount();
	(void) fclose(Backuplist);
	printf("See the file %s for a list of files backed up\n",Backupname);
	return;
}

/* Function:	initscript
 *
 * Does:	initializes temporary script for restoral of files
 *		copied to floppy
 *
 */

void
initscript()
{
int rc;
	/* make file for commands on restoring script */
	sprintf(s,"echo \"#script for restoring backup\n#done `date` from directory `pwd`\" > %s",Sname);
	rc=system(s);
	if (rc != 0) {
		fprintf(stderr, "Script file: %s\n",Sname);
		errexit("Failed to create temporary script file");
	}
	return;
}

/* Function:	initlist
 *
 * Does:	initializes list of files copied to floppy
 *
 */

void
initlist()
{
int rc;
	/* the name has epoch time at the end */
	sprintf(Backupname, "/tmp/uback%ld", time(0L));

	/* create the file via shell for an easy way to get date and pwd */
	sprintf(s,"echo \"#List of backup by uback\n#done `date` from directory `pwd`\" > %s",Backupname);
	rc=system(s);
	if (rc != 0) {
		fprintf(stderr, "Listing file: %s\n",Sname);
		errexit("Failed to create backup listing file");
	}

	/* now open up the file for append */
	Backuplist = fopen( Backupname, "a" );
	if (Backuplist == NULL) {
		fprintf(stderr, "Listing file: %s\n",Sname);
		errexit("Failed to open list file - halting");
	}
	return;
}

/* Function:	initsave
 *
 * Does:	initializes list of files not yet saved
 *
 */

void
initsave()
{

	/* open up the file for write */
	Saveptr = fopen( Savename, "w" );
	if (Saveptr == NULL) {
		fprintf(stderr, "Files-not-saved file: %s\n",Sname);
		errexit("Failed to open file - halting");
	}
	return;
}
!FuNkYsTuFf
echo extracting uback.h
cat >uback.h <<'!FuNkYsTuFf'
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>

/* Common header file for uback program */

/* both the following debug macros assume single statement
 * arguments */
#define DEBUG(action)	if (Debug == TRUE) action
#define VERBOSE(action)	if (Verbose == TRUE) action

#define SSIZE 1000	/* general purpose string size */
#define FSIZE 120		/* max file name string size */
#define RECSIZE	FSIZE + 15	/* indexed file record size */
#define MAXFILES	6000	/* max files supported for program */
#define FACTOR	7	/* blocks left on disk for the script we     */
			/* construct for recovery as well as for     */	
			/* a fudge factor - too big will waste space */
			/* too little, and a file may fail to copy.  */
			/* Note that an extra block is added to each */
			/* file already which can be a significant   */
			/* fudge factor */

/* block floppy device */
#define BFLOPPY	"/dev/fp021"
/* char floppy device */
#define FLOPPY	"/dev/rfp020"

#define	TRUE	1	/* return codes */
#define	FALSE	0
#define	NOTYET	2
#define QUIT	3

#define MORE	1	/* more return codes */
#define NOMORE	2

#define FILESPERLINE	8	/* max files per 'du' command line */

#define USED	-1	/* File already used flag */
#define FAILED	-2	/* File compress already tried and failed flag */

/* structures */
struct FileList {	/* file size index for retrieving files from disk */
	int size;		/* file size in blocks */
	int csize;		/* compressed file size in blocks */
};

/* function declarations */
extern void errexit();
extern void mountdisk();
extern void cleanup();
extern void dismount();
extern void earlyend();
extern FILE *getfiles();
extern FILE *makeindex();
!FuNkYsTuFf