[comp.sources.amiga] v02i052: backup - file backup utility V2.01

page@swan.ulowell.edu (Bob Page) (11/05/88)

Submitted-by: dillon@postgres.berkeley.edu (Matt Dillon)
Posting-number: Volume 2, Issue 52
Archive-name: dos/fs/backup201.1

There are some serious bugs in backup/restore version V2.00.
This is version 2.01, and fixes the following problems:
	(1) Restore would not put files in the proper sub-directory
	(2) Append mode for backup does not work
These bugs were found by Jan Sven Trabandt.

#	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:
#	backup.c
#	Makefile
#	backup.doc
# This archive created: Fri Nov  4 17:11:15 1988
cat << \SHAR_EOF > backup.c

/*
 * BACKUP.C
 *
 * (C)Copyright 1986-88, Matthew Dillon, All Rights Reserved.
 * Permission is granted to distribute for non-profit only.
 *
 *  Thanks to Jan Sven Trabandt for finding some major bugs!
 *
 * This program will backup a filesystem or directory, creating a single
 * output file which can later be RESTORE'd from.  It is a quick way to
 * backup your work to a 'backup' disk.
 *
 *  backup [options] path path path ... path [-ooutputfile]
 *
 *	NOTE:	if -o is not specified, not output file will be created
 *
 *  options:
 *
 *    -A       ARCHIVE.  Clear the archive bit on backed up files
 *
 *    -U       UPDATE.	 Backup only those files which have the archive bit
 *	       cleared.
 *
 *    -f[#KB]  Floppy... actually, this option is used to force the backup
 *	       program to automatically split up the backup files in #KB
 *	       sections (default 800).  I.E.  -f with nothing else will
 *	       use 800KB chunks.  -f200 would use 200KB chunks, etc...
 *
 *	       PLEASE SEE THE DOCS FOR MORE INFORMATION ON FLOPPY BACKUP
 *	       AND RESTORE
 *
 *    -Fvol    example: -FDF0: -FDF1:  This command specifies the ordering
 *	       and number of (floppy) drives to backup to.  This allows
 *	       one to change floppies in one drive while it is backing up
 *	       to another.  It automatically cycles through the drives but
 *	       you still must specify an initial output file (-ofile) to
 *	       determine the base name for the files.
 *
 *	       This command also forces user prompting.
 *
 *	       PLEASE SEE THE DOCS FOR MORE INFORMATION ON FLOPPY BACKUP
 *	       AND RESTORE
 *
 *    -b       backup	(default if executable name is 'backup')
 *
 *    -r       restore	(default if executable nam is 'restore')
 *
 *    -a       append to the destination file.
 *
 *    -c       Compress files during backup
 *
 *    -s       Only display directories as we go along.
 *
 *    -l/-t    (either option) Causes a RESTORE to only LIST the files,
 *	       not do an actual restore.
 *
 *    -T       (Restore) TIMESTAMP, COMMENT, AND PROTECTION BITS ONLY.
 *	       NO files are created.  It is assumed the files already
 *	       exist.  The timestamp and other fields are transfered
 *	       from the backup file to the already-restored files (useful
 *	       if the files were restored with a copy command that did
 *	       non copy the timestamps.  Even if the backup file is old,
 *	       you can recover most of your time data).
 *
 *    -S       Silent.	Don't display files or directories.
 *
 *    -nKB     Set buffer size to use, in KB.
 *
 *    -v       Verbose... Additionaly, display those files which will NOT
 *	       be backed up.
 *
 *    -pPAT    only file-paths matching this pattern are backed up.  You
 *	       may specify more than one '-p' option.
 *
 *    -dPAT    file-paths matching this pattern are NOT backed up.  you
 *	       may specify more than one '-d' option.
 *
 *    -ofile   Output File
 *
 * ---------------------------------------------------------------------
 *
 * destination file format:
 *
 *  HDR = <HDR><N.B><datestamp> 		    -Backup Date
 *  VOL = <VOL><name_size.B><name>		    -VOLUME base
 *  DDS = <DDS><name_size.B><name>		    -down-directory
 *  END = <END><0.B>				    -up directory
 *  DAT = <DAT><N.B><datestamp> 		    -datestamp for file
 *  PRO = <PRO><4.B><protection>		    -protection for file
 *  COM = <COM><N.B><comment>			    -comment for file
 *  FIL0= <FIL0><N.B><name><size.L><data>	    -uncompressed file
 *  FIL1= <FIL1><N.B><name><size.L><usize.L><data>  -compressed form #1
 *  INC = <INC><12.B><ttlsize><strt><segsize>	    -next file is part of an
 *						     incomplete file
 */

#include <stdio.h>
#include <fcntl.h>
#include <local/typedefs.h>

#define SDIR	struct _SDIR
#define SCOMP	struct _SCOMP

SCOMP {
    MNODE   Node;
    uword   Bytes;	/*  allocated bytes		    */
    uword   N;
};

SDIR {
    MNODE   Node;	/*  node in dir tree		    */
    ubyte   Type;	/*  XDDS or XVOL		    */
    ubyte   HaveFile;	/*  Something was backed up in here */
    char    *Element;	/*  path element		    */
};

#define XVOL	0x01
#define XDDS	0x02
#define XEND	0x03
#define XDAT	0x04
#define XPRO	0x05
#define XCOM	0x06
#define XFIL0	0x87
#define XFIL1	0x88
#define XHDR	0x09
#define XINC	0x0A

ubyte	Break;
ubyte	Restore;
ubyte	ListOnly;
ubyte	ShowFiles = 1;
ubyte	ShowDirs  = 1;
ubyte	Verbose;
ubyte	Archive;
ubyte	Update;
ubyte	Append;
ubyte	Compress;
ubyte	TimeStampOnly;
char	*OutFile;
long	BacBytes;
short	BacCnt = 1;

uword	NumPicks;
uword	NumDels;
char	*Picks[32];
char	*Dels[32];

char DirPath[256];
short DPLen;

long	BufSize = 65536;
long	BufI;
ubyte	*Buf;
long	InBufSize = 8192;
long	InBufI, InBufN;
ubyte	*InBuf;

MLIST	VList;	    /*	Volume list (-F), of NODEs              */
MLIST	DList;	    /*	Directory Stack 			*/
long	CLen;	    /*	Actual compressed file output length	*/
MLIST	CList;	    /*	List of memory buffers			*/
SCOMP	*CWrite;    /*	Current memory buffer pointer		*/

extern int Enable_Abort;
extern void seekinputend();
extern FIB *GetFileInfo();
extern SCOMP *NewSComp();
extern void *malloc(), *GetHead(), *GetTail(), *GetSucc(), *GetPred();

main(ac, av)
char *av[];
{
    register short i, notdone;
    register char  *str;

    NewList(&VList);
    NewList(&DList);
    NewList(&CList);

    Enable_Abort = 0;


    for (str = av[0] + strlen(av[0]); str >= av[0] && *str != '/' && *str != ':'; --str);
    ++str;
    if ((*str|0x20) == 'r')
	Restore = 1;

    if (ac == 1) {
	printf("Backup/Restore V2.01, (c)Copyright 1988 Matthew Dillon, All Rights Reserved\n", str);
	printf("%s -rbactlvASTU -d<pat> -p<pat> -f[#kb] -F<vol> -n<#kb> -ofile\n", str);
    }

    for (i = 1; i < ac; ++i) {
	str = av[i];
	if (*str != '-')
	    continue;
	notdone = 1;
	++str;
	while (notdone && *str) {
	    switch(*str) {
	    case 'r':
		Restore = 1;
		break;
	    case 'b':
		Restore = 0;
		break;
	    case 'a':
		Append = 1;
		break;
	    case 'c':
		Compress = 1;
		break;
	    case 'd':
		Dels[NumDels++] = str + 1;
		notdone = 0;
		break;
	    case 'f':
		BacBytes = 800 * 1024;
		if (str[1] >= '0' && str[1] <= '9') {
		    BacBytes = atoi(str+1) * 1024;
		    notdone = 0;
		}
		break;
	    case 'F':
		{				    /*	strlen(str+1)+1 */
		    register NODE *node = malloc(sizeof(NODE)+strlen(str));
		    node->ln_Name = (char *)(node+1);
		    strcpy(node+1, str+1);
		    AddTail(&VList, node);
		}
		notdone = 0;
		break;
	    case 'n':
		BufSize = atoi(str+1) * 1024;
		if (BufSize <= 0)
		    BufSize = 65536;
		notdone = 0;
		break;
	    case 'o':
		OutFile = str + 1;
		notdone = 0;
		break;
	    case 'p':
		Picks[NumPicks++] = str + 1;
		notdone = 0;
		break;
	    case 's':
		ShowFiles = 0;
		break;
	    case 't':
	    case 'l':
		ListOnly = 1;
		break;
	    case 'v':
		Verbose= 1;
		break;
	    case 'A':
		Archive= 1;
		break;
	    case 'S':
		ShowFiles = 0;
		ShowDirs  = 0;
		break;
	    case 'T':
		TimeStampOnly = 1;
		break;
	    case 'U':
		Update = 1;
		break;
	    default:
		puts("failure");
		exit(20);
	    }
	    ++str;
	}
    }
    Buf = malloc(BufSize);
    if (Buf == NULL) {
	printf("Unable to malloc %ld bytes\n", BufSize);
	exit(20);
    }
    if (ListOnly)
	InBufSize = 512;	/*  small buffer to avoid read overhead */
				/*  since we are skipping the meat	*/

    InBuf = malloc(InBufSize);
    if (InBuf == NULL) {
	printf("Unable to malloc %ld bytes\n", InBufSize);
	exit(20);
    }

    if (Restore)
	RestoreFiles(ac,av);
    else
	BackupFiles(ac,av);
}

long SaveLock;

BackupFiles(ac, av)
char *av[];
{
    register short i;
    register char *str, *ptr;
    char notdone;

    if (OutFile && openoutput(OutFile, Append, ((BacBytes)?1:0)) == 0)
	exit(20);
    if (OutFile) {      /*  write header    */
	DATESTAMP Date;
	DateStamp(&Date);
	outentry(XHDR, sizeof(DATESTAMP), &Date);
    }

    SaveLock = CurrentDir(DupLock(((PROC *)FindTask(NULL))->pr_CurrentDir));

    for (i = 1; i < ac; ++i) {
	str = av[i];
	if (*str == '-')
	    continue;
	/*
	 *  Push DDS entries for each name segment of the path
	 */

	notdone = 1;
	while (notdone) {
	    for (ptr = str; *ptr && *ptr != ':' && *ptr != '/'; ++ptr);
	    switch(*ptr) {
	    case '/':   /*  normal directory    */
		*ptr = 0;
		PushDir(str, XDDS);
		str = ptr + 1;
		*ptr = '/';
		break;
	    case ':':   /*  volume              */
		*ptr = 0;
		PushDir(str, XVOL);
		str = ptr + 1;
		*ptr = ':';
		break;
	    default:	/*  directory or file	*/
		{
		    char *path = av[i];
		    FIB *fib;
		    long lock;

		    if (fib = GetFileInfo(path, &lock)) {
			if (fib->fib_DirEntryType > 0) {
			    if (str[0])
				PushDir(str, XDDS);
			    lock = scan_directory(fib, lock);
			    if (str[0])
				PopDirs(1);
			} else {
			    lock = scan_file(fib, lock);
			}
			FreeFileInfo(fib, lock);
		    } else {
			printf("Unable to get info for %s\n", av[i]);
		    }
		}
		notdone = 0;
		break;
	    }
	}
	PopDirs(-1);
    }

    UnLock(CurrentDir(SaveLock));
    if (OutFile)
	closeoutput();
}

DATESTAMP Date;
char Comment[256];
char Scr[256];
long Protection;
long IncSize;		/*  Size of entire file     */
long IncSeek;		/*  Seek offset into file   */
long IncLen;		/*  # bytes in this segment */

RestoreFiles(ac, av)
char *av[];
{
    register short i;
    register char *str;
    char notdone;
    char havedate;
    char havepro;
    char havecom;
    char haveinc;
    long bytes;
    long actual;
    long lock;

    SaveLock = CurrentDir(lock = DupLock(((PROC *)FindTask(NULL))->pr_CurrentDir));

    for (i = 1; i < ac; ++i) {
	str = av[i];
	if (*str == '-')
	    continue;
	if (openinput(str) == 0) {
	    printf("Unable to open %s for input\n", str);
	    continue;
	}
	notdone = 1;
	havedate = havepro = havecom = haveinc = 0;
	while (notdone) {
	    short c = oreadchar();
	    short l = oreadchar();
	    switch(c) {
	    case -1:
		notdone = 0;
		break;
	    case XVOL:
		oread(Scr, l);
		Scr[l] = 0;
		if (OutFile) {      /*  Restore to OutFile instead  */
		    register short j = strlen(OutFile);
		    strcpy(Scr, OutFile);
		    if (j && OutFile[j-1] == '/') {
			c = XDDS;
			Scr[j-1] = 0;
		    } else if (j && OutFile[j-1] == ':') {
			c = XVOL;
			Scr[j-1] = 0;
		    } else
			c = XDDS;
		}
		PushDir(Scr, c);
		if (ListOnly)
		    break;
		lock = Lock(DirPath, SHARED_LOCK);  /*  DirPath incs ':'    */
		if (lock == NULL && c == XDDS) {
		    if (lock = CreateDir(Scr))      /*  Scr excludes '/'    */
			UnLock(lock);
		    lock = Lock(Scr, SHARED_LOCK);
		}
		{
		    SDIR *sd = GetTail(&DList);
		    sd->HaveFile = 1;		    /*	don't remove dir    */
		}
		if (lock == NULL) {
		    printf("Unable to create directory %s\n", Scr);
		    notdone = 0;
		} else {
		    UnLock(CurrentDir(lock));
		}
		break;
	    case XDDS:
		oread(Scr, l);
		Scr[l] = 0;
		PushDir(Scr, XDDS);
		if (ListOnly)
		    break;
		lock = Lock(Scr, SHARED_LOCK);
		if (lock == NULL) {
		    if (lock = CreateDir(Scr))
			UnLock(lock);
		    lock = Lock(Scr, SHARED_LOCK);
		} else {
		    SDIR *sd = GetTail(&DList);
		    sd->HaveFile = 1;		    /*	don't remove dir    */
		}
		if (lock == NULL) {
		    printf("Unable to create directory %s\n", Scr);
		    notdone = 0;
		} else {
		    UnLock(CurrentDir(lock));
		}
		break;
	    case XEND:
		{
		    SDIR *sd = GetTail(&DList);
		    ubyte type;

		    c = 1;
		    if (!sd)
			break;
		    type = sd->Type;
		    strcpy(Scr, sd->Element);
		    c = PopDirs(1);
		    if (ListOnly)
			break;
		    if (type == XVOL)   /*  no parent directory */
			break;
		    lock = ParentDir(lock);
		    if (lock == NULL) {
			puts("Unable to ParentDir!");
			notdone = 0;
		    } else {
			if (c == 0)
			    DeleteFile(Scr);
			UnLock(CurrentDir(lock));
		    }
		}
		break;
	    case XDAT:
		if (l != sizeof(DATESTAMP)) {
		    puts("expected sizeof datestamp");
		    notdone = 0;
		    break;
		}
		oread(&Date, l);
		havedate = 1;
		break;
	    case XPRO:
		if (l != 4) {
		    puts("Expected 4 bytes for protection");
		    notdone = 0;
		    break;
		}
		oread(&Protection, l);
		havepro = 1;
		break;
	    case XCOM:
		oread(Comment, l);
		Comment[l] = 0;
		havecom = 1;
		break;
	    case XFIL0:
	    case XFIL1:
		if (!havepro)
		    Protection = 0;
		if (!havecom)
		    Comment[0] = 0;
		if (!havedate)
		    DateStamp(&Date);
		if (!haveinc)
		    IncSize = 0;

		oread(Scr, l);
		Scr[l] = 0;
		oread(&bytes, 4);       /*  length of file  */
		actual = bytes;
		if (c == XFIL1) {
		    oread(&actual, 4);
		    bytes -= 4;
		}
		setinputbound(bytes);
		{
		    short res = read_file(c, Scr, bytes, actual);
		    seekinputend();
		    if (res < 0)
			goto bend;
		}
		if (ListOnly)
		    goto bend;
		if (Archive)
		    SetProtection(Scr, Protection|FIBF_ARCHIVE);
		else
		    SetProtection(Scr, Protection&~FIBF_ARCHIVE);
		if (havedate)
		    setfiledate(Scr, &Date);
		if (havecom && Comment[0])
		    SetComment(Scr, Comment);
bend:
		havecom = havedate = havepro = haveinc = 0;
		break;
	    case XHDR:
		if (l != sizeof(DATESTAMP)) {
		    puts("expected sizeof datestamp");
		    notdone = 0;
		    break;
		}
		oread(&Date, l);
		printf(" ----- BACKUP ----- BACKUP DATE: %s\n", datetos(&Date, Scr, NULL));
		break;
	    case XINC:
		if (l != 12) {
		    puts("expected 12 bytes for XINC");
		    notdone = 0;
		    break;
		}
		oread(&IncSize, 4);
		oread(&IncSeek, 4);
		oread(&IncLen,  4);
		haveinc = 1;
		break;
	    default:
		printf("Unknown Record Type: %02x\n", c);
		notdone = 0;
		break;
	    }
	    setinputbound(-1);
	    if (mycheckbreak()) {
		Break = 1;
		notdone = 0;
		break;
	    }
	}
	if (Break)
	    break;
    }
    UnLock(CurrentDir(SaveLock));
}

FIB *
GetFileInfo(path, plock)
char *path;
long *plock;
{
    register long lock;
    register FIB *fib;

    *plock = NULL;
    if (lock = Lock(path, SHARED_LOCK)) {
	if (fib = malloc(sizeof(FIB))) {
	    if (Examine(lock, fib)) {
		*plock = lock;
		return(fib);
	    }
	    free(fib);
	}
	UnLock(lock);
    }
    return(NULL);
}

FreeFileInfo(fib, lock)
FIB *fib;
long lock;
{
    if (fib)
	free(fib);
    if (lock)
	UnLock(lock);
}


PushDir(element, type)
char *element;
{
    register SDIR *sd = malloc(sizeof(SDIR));
    register char *str = malloc(strlen(element)+1);

    strcpy(str, element);
    sd->Type = type;
    sd->HaveFile = 0;
    sd->Element = str;
    AddTail(&DList, sd);
    strcat(DirPath+DPLen, str);
    if (type == XVOL)
	strcat(DirPath+DPLen, ":");
    else
	strcat(DirPath+DPLen, "/");
    DPLen += strlen(DirPath+DPLen);
}

PopDirs(num)
uword num;
{
    register SDIR *sd, *sp;
    char lasthave = 0;

    while (num && (sd = GetTail(&DList))) {
	lasthave |= sd->HaveFile;
	if (sd->HaveFile)       /*  MUST write end-block    */
	    outentry(XEND, 0, NULL);
	if (sp = GetPred(sd))
	    sp->HaveFile |= sd->HaveFile;
	Remove(sd);
	DPLen -= strlen(sd->Element) + 1;
	if (DPLen < 0) {
	    puts("DPLEN ERROR");
	    DPLen = 0;
	}
	DirPath[DPLen] = 0;
	free(sd->Element);
	free(sd);
	--num;
    }
    return(lasthave);
}

/*
 *  SCAN_DIRECTORY()        (CORE OF BACKUP)
 */

scan_directory(dirfib, dirlock)
FIB *dirfib;
long dirlock;
{
    register FIB *fib;
    long lock;
    long save = CurrentDir(dirlock);

    while (ExNext(dirlock, dirfib) && (fib = GetFileInfo(dirfib->fib_FileName, &lock))) {
	if (fib->fib_DirEntryType > 0) {
	    PushDir(fib->fib_FileName, XDDS);
	    if (ShowDirs)
		printf("%-40s (DIR)\n", DirPath);
	    lock = scan_directory(fib, lock);
	    PopDirs(1);
	} else {
	    lock = scan_file(fib, lock);
	}
	FreeFileInfo(fib, lock);
	if (Break || mycheckbreak()) {
	    Break = 1;
	    break;
	}
    }
    CurrentDir(save);
    return(dirlock);
}

mycheckbreak()
{
    if (SetSignal(0, (SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D)) & (SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D)) {
	puts(" ***** BREAK *****");
	return(1);
    }
    return(0);
}

/*
 *  SCAN_FILE()
 *
 *  If the file is accepted, write out pending directory entries, do
 *  compression if any, and write out the file.
 */

scan_file(fib, lock)
FIB *fib;
long lock;
{
    long save;
    long n;
    char dbuf[32];

    strcat(DirPath, fib->fib_FileName);

    if (Update && (fib->fib_Protection & FIBF_ARCHIVE))
	goto nomatch;
    {
	register short i;

	for (i = 0; i < NumPicks; ++i) {
	    if (wildcmp(Picks[i], DirPath))
		break;
	}
	if (i && i == NumPicks)
	    goto nomatch;

	for (i = 0; i < NumDels; ++i) {
	    if (wildcmp(Dels[i], DirPath))
		goto nomatch;
	}
    }

    if (ShowFiles)
	printf("%-40s %6ld %s\n", DirPath, fib->fib_Size, datetos(&fib->fib_Date, dbuf, NULL));

    {
	register SDIR *sd;
	SDIR *sdb = NULL;

	for (sd = GetTail(&DList); sd; sd = GetPred(sd)) {
	    if (sd->HaveFile == 0)
		sdb = sd;
	}
	for (sd = sdb; sd; sd = GetSucc(sd)) {
	    sd->HaveFile = 1;
	    outentry(sd->Type, strlen(sd->Element), sd->Element);
	}
    }
    if (OutFile) {
	save = CurrentDir(lock);
	if (openinput("") == 0) {
	    CurrentDir(save);
	    printf("Unable to open %s\n", fib->fib_FileName);
	    goto nomatch;
	}
	if (Compress && CompressFile(fib->fib_FileName, fib->fib_Size)) {
	    if (OutFile && BacBytes && outbytes() + CLen > BacBytes) {
		if (newfile() == 0)
		    goto skip1;
	    }
	    writeheaders(fib);
	    outentry(XFIL1, strlen(fib->fib_FileName), fib->fib_FileName);
	    CLen += 4;
	    owrite(&CLen, 4);
	    CLen -= 4;
	    owrite(&fib->fib_Size, 4);
	    transfer1(fib->fib_Size);
	} else {
	    if (OutFile && BacBytes && outbytes() + fib->fib_Size > BacBytes) {
		if (newfile() == 0)
		    goto skip1;
	    }
	    if (Compress)
		rollbackinput();
	    writeheaders(fib);
	    outentry(XFIL0, strlen(fib->fib_FileName), fib->fib_FileName);
	    owrite(&fib->fib_Size, 4);
	    transfer0(fib->fib_Size);
	}
skip1:
	closeinput();
	CurrentDir(save);
    }
    if (Break)
	goto nomatch;
    if (Archive && !(fib->fib_Protection & FIBF_ARCHIVE)) {
	if (save = ParentDir(lock)) {
	    UnLock(lock);
	    save = CurrentDir(save);
	    SetProtection(fib->fib_FileName, fib->fib_Protection | FIBF_ARCHIVE);
	    lock = CurrentDir(save);
	}
    }
    DirPath[DPLen] = 0;
    return(lock);
nomatch:
    if (Verbose)
	printf("%-40s (NOT ACCEPTED)\n", DirPath, fib->fib_Size, datetos(&fib->fib_Date, dbuf, NULL));

    DirPath[DPLen] = 0;
    return(lock);
}

writeheaders(fib)
register FIB *fib;
{
    outentry(XDAT, sizeof(DATESTAMP), &fib->fib_Date);
    outentry(XPRO, 4, &fib->fib_Protection);
    if (fib->fib_Comment[0])
	outentry(XCOM, strlen(fib->fib_Comment), fib->fib_Comment);
}

/*
 *  (1) Write out XEND's to finish this archive,
 *  (2) Open a new output file
 *  (3) Write out XDDS's to build back to the current position
 */

newfile()
{
    {
	register SDIR *sd;

	for (sd = GetTail(&DList); sd; sd = GetPred(sd)) {
	    if (sd->HaveFile)
		outentry(XEND, 0, NULL);
	}
    }
    ++BacCnt;
    if (OutFile && openoutput(OutFile, Append, 1) == 0) {
	Break = 1;
	return(0);
    }
    {
	register SDIR *sd;
	DATESTAMP Date;
	DateStamp(&Date);
	outentry(XHDR, sizeof(DATESTAMP), &Date);
	for (sd = GetHead(&DList); sd; sd = GetSucc(sd)) {
	    if (sd->HaveFile)
		outentry(sd->Type, strlen(sd->Element), sd->Element);
	}
    }
    return(1);
}

read_file(type, fname, inbytes, outbytes)
short type;
char *fname;
{
    char dbuf[32];

    strcat(DirPath, fname);
    {
	register short i;

	for (i = 0; i < NumPicks; ++i) {
	    if (wildcmp(Picks[i], DirPath))
		break;
	}
	if (i && i == NumPicks) {
	    if (Verbose)
		printf("%-40s (NOT ACCEPTED)\n", DirPath);
	    goto nomatch;
	}

	for (i = 0; i < NumDels; ++i) {
	    if (wildcmp(Dels[i], DirPath)) {
		if (Verbose)
		    printf("%-40s (NOT ACCEPTED)\n", DirPath);
		goto nomatch;
	    }
	}
    }

    printf("%-40s %6ld %6ld %s %s\n", DirPath, inbytes, outbytes, datetos(&Date, dbuf, NULL), Comment);

    if (ListOnly)
	goto nomatch;
    if (TimeStampOnly)
	goto matchskip;

    openoutput(fname, 0, 0);
    switch(type) {
    case XFIL0:
	transfer0(inbytes);
	break;
    case XFIL1:
	UnCompressFile(inbytes);
	transfer1(outbytes);
	break;
    }
    closeoutput();
matchskip:
    DirPath[DPLen] = 0;
    return(1);
nomatch:
    DirPath[DPLen] = 0;
    return(-1);
}

/*
 *  FILE SUPPORT
 */

static int Infd = -1;
static int Outfd = -1;
static long OutBytes;

openoutput(name, append, enabtail)
char *name;
{
    char *ptr = name;
    static NODE *VNode;     /*	Volume node */
    long lock;
    extern int errno;

    if (Outfd >= 0) {
	dumpoutput();
	close(Outfd);
	Outfd = -1;
    }
    if (enabtail) {
	if (VNode)
	    VNode = GetSucc(VNode);
	if (!VNode)
	    VNode = GetHead(&VList);
	if (VNode) {
	    ptr = malloc(strlen(VNode->ln_Name)+strlen(name)+8);
	    sprintf(ptr, "%s%s.%02ld", VNode->ln_Name, name, BacCnt);
	} else {
	    ptr = malloc(strlen(name)+8);
	    sprintf(ptr, "%s.%02ld", name, BacCnt);
	}
    }
    OutBytes = 0;
    while (GetHead(&VList)) {
	short c;
	short d;

	fprintf(stderr, "Ready for %s (y=go,n=abort) -", ptr);
	fflush(stderr);
	if ((c = getc(stdin)) == EOF) {
	    fprintf(stderr, "EOF, aborted\n");
	    c = 'n';
	}
	while ((d = getc(stdin)) != EOF && d != '\n');
	if ((c|0x20) == 'y')
	    break;
	if ((c|0x20) == 'n')
	    goto skip;
    }
    if (enabtail && SaveLock)                 /*  original directory  */
	lock = CurrentDir(SaveLock);
    if (append) {
	Outfd = open(ptr, O_WRONLY|O_CREAT|O_APPEND);
	if (Outfd >= 0) {
	    OutBytes = lseek(Outfd, 0L, 2);
	    if (!append)
		lseek(Outfd, 0L, 0);
	}
    } else {
	Outfd = open(ptr, O_WRONLY|O_CREAT|O_TRUNC);
    }
    if (enabtail && SaveLock)                 /*  back to before      */
	CurrentDir(lock);
    if (Outfd < 0)
	printf("Unable to open output file %s (%ld)\n", ptr, errno);
skip:
    BufI = 0;
    if (enabtail)
	free(ptr);
    return(Outfd >= 0);
}

oputc(v)
char v;
{
    ++OutBytes;
    if (Outfd >= 0) {
	if (BufI == BufSize)
	    dumpoutput();
	Buf[BufI++] = v;
    }
}

owrite(buf, n)
register char *buf;
register long n;
{
    register long avail;

    OutBytes += n;
    if (Outfd >= 0) {
	while (BufI + n > BufSize) {
	    avail = BufSize - BufI;
	    bmov(buf, Buf + BufI, avail);
	    n  -= avail;
	    buf+= avail;
	    BufI = BufSize;
	    dumpoutput();
	}
	bmov(buf, Buf + BufI, n);
	BufI += n;
    }
}

dumpoutput()
{
    if (Outfd >= 0 && BufI) {
	write(Outfd, Buf, BufI);
	BufI = 0;
    }
}

closeoutput()
{
    if (Outfd >= 0) {
	dumpoutput();
	close(Outfd);
	Outfd = -1;
    }
}

outbytes()
{
    return(OutBytes);
}

/*
 *  <type><len><buf>
 */

outentry(type, len, buf)
ubyte type;
ubyte len;
ubyte *buf;
{
    OutBytes += len + 2;
    if (Outfd >= 0) {
	if (BufI + len + 2 >= BufSize)
	    dumpoutput();
	Buf[BufI+0] = type;
	Buf[BufI+1] = len;
	bmov(buf, Buf+BufI+2, len);
	BufI += len + 2;
    }
}

ulong OMax;

openinput(name)
char *name;
{
    if (Infd >= 0)
	close(Infd);
    Infd = open(name, O_RDONLY);
    InBufI = InBufN = 0;
    OMax = -1;
    return(Infd >= 0);
}

closeinput()
{
    if (Infd >= 0)
	close(Infd);
    Infd = -1;
}

void
seekinputend()
{
    register long inbuf   = InBufI - InBufN;
    register long forward = OMax;

    if (forward > inbuf) {
	lseek(Infd, forward - inbuf, 1);
	OMax = InBufI = InBufN = 0;
	return;
    }
    InBufN += forward;
}

setinputbound(max)
{
    OMax = max;
}

oread(buf, n)
char *buf;
long n;
{
    long x = 0;
    long avail;

    if (Infd < 0)
	return(0);
    if (n > OMax)
	n = OMax;

    while (n > (avail = InBufI - InBufN)) {
	if (InBufN == -1)
	    return(0);
	bmov(InBuf + InBufN, buf, avail);
	OMax-= avail;
	n   -= avail;
	buf += avail;
	x   += avail;
	InBufI = read(Infd, InBuf, InBufSize);
	InBufN = 0;
	if (InBufI <= 0) {
	    InBufI = 0;
	    return(x);
	}
    }
    bmov(InBuf + InBufN, buf, n);
    InBufN += n;
    x += n;
    OMax -= n;
    return(x);
}

oreadchar()
{
    if (!OMax || Infd < 0)
	return(-1);
    if (InBufN == InBufI) {
	if (InBufN < 0)
	    return(EOF);
	InBufI = read(Infd, InBuf, InBufSize);
	InBufN = 0;
	if (InBufI == 0) {
	    InBufN = InBufI = -1;
	    return(-1);
	}
    }
    return(InBuf[InBufN++]);
}

rollbackinput()
{
    if (Infd >= 0)
	lseek(Infd, 0L, 0);
    InBufI = InBufN = 0;
}

mputc(v)
char v;
{
    register SCOMP *sc = CWrite;

    ++CLen;
    if (sc->N == sc->Bytes) {
	sc = GetSucc(sc);
	if (sc == NULL);
	    sc = NewSComp();
	if (sc == NULL) {
	    puts("SCOMP FAILED");
	    return(0);
	}
	sc->N = 0;
	CWrite = sc;
    }
    ((char *)(sc + 1))[sc->N++] = v;
}

mwrite(buf, n)
char *buf;
long n;
{
    register SCOMP *sc = CWrite;
    register long avail;

    CLen += n;
    while ((avail = sc->Bytes - sc->N) < n) {
	bmov(buf, (char *)(sc + 1) + sc->N, avail);
	buf += avail;
	n -= avail;
	sc->N = sc->Bytes;
	sc = GetSucc(sc);
	if (sc == NULL)
	    sc = NewSComp();
	if (sc == NULL) {
	    puts("SCOMP FAILED");
	    return(0);
	}
	sc->N = 0;
    }
    bmov(buf, (char *)(sc + 1) + sc->N, n);
    sc->N += n;
    CWrite = sc;
}

SCOMP *
NewSComp()
{
    register SCOMP *sc = malloc(sizeof(SCOMP) + 8192);

    if (sc) {
	sc->Bytes = 8192;
	sc->N = 0;
	AddTail(&CList, sc);
    }
    return(sc);
}

transfer0(n)
long n;
{
    register long len;

    if (Outfd < 0)
	return(n);
    for (len = BufSize - BufI; n; len = BufSize - BufI) {
	if (len == 0) {
	    dumpoutput();
	    len = BufSize;
	}
	if (n < len)
	    len = n;
	oread(Buf + BufI, len);
	BufI += len;
	n -= len;
	OutBytes += len;
    }
}

/*
 *  Compression Routines
 *
 *  transfer1(n)    : Backup:   copy compression buffer to output file
 */

transfer1(a)
{
    register long len;
    register SCOMP *sc = GetHead(&CList);
    register ubyte *ptr;
    long n = CLen;

    if (Outfd < 0)
	return(n);
    for (sc = GetHead(&CList); sc && n; sc = GetSucc(sc)) {
	len = sc->Bytes;
	ptr = (ubyte *)(sc + 1);
	if (n < len)
	    len = n;
	n -= len;
	while (len > BufSize - BufI) {
	    bmov(ptr, Buf + BufI, BufSize - BufI);
	    ptr += BufSize - BufI;
	    len -= BufSize - BufI;
	    OutBytes += BufSize - BufI;
	    BufI = BufSize;
	    dumpoutput();
	}
	bmov(ptr, Buf + BufI, len);
	BufI += len;
	OutBytes += len;
    }
    if (n)
	puts("Unexpected EOF in compression file");
}

#asm

	    ;	Taken from my DRES.LIBRARY so we don't have to open the
	    ;	library.

	    public  _GetHead
	    public  _GetTail
	    public  _GetSucc
	    public  _GetPred

_GetSucc:
_GetHead:   move.l  4(sp),A0
	    move.l  (A0),A0
	    tst.l   (A0)
	    bne     .gh1
.ghz	    sub.l   A0,A0
.gh1	    move.l  A0,D0
	    rts

_GetTail:   move.l  4(sp),A0
	    move.l  8(A0),A0
	    tst.l   4(A0)
	    beq     .ghz
	    move.l  A0,D0
	    rts

_GetPred:   move.l  4(sp),A0
	    move.l  4(A0),A0
	    tst.l   4(A0)
	    beq     .ghz
	    move.l  A0,D0
	    rts

#endasm

#define ngetchar()  oreadchar()
#define nputchar(n) mputc(n)

#ifndef min
#define min(a,b)        ((a>b) ? b : a)
#endif

#define BITS		13

#if BITS == 16
#define HSIZE  69001	       /* 95% occupancy */
#endif
#if BITS == 15
#define HSIZE  35023	       /* 94% occupancy */
#endif
#if BITS == 14
#define HSIZE  18013	       /* 91% occupancy */
#endif
#if BITS == 13
#define HSIZE  9001	       /* 91% occupancy */
#endif
#if BITS <= 12
#define HSIZE  5003	       /* 80% occupancy */
#endif

typedef long		code_int;
typedef long		count_int;
typedef unsigned char	char_type;

#define MAXCODE(n_bits)  ((1 << (n_bits)) - 1)
#define INIT_BITS 9			/* initial number of bits/code */

int n_bits;				/* number of bits/code		    */
int maxbits;				/* user settable max # bits/code    */
code_int maxcode;			/* maximum code, given n_bits	    */
code_int maxmaxcode;			/* should NEVER generate this code  */

count_int   htab[HSIZE];
uword	    codetab[HSIZE];

#define htabof(i)       htab[i]
#define codetabof(i)    codetab[i]

code_int hsize = HSIZE; 		/* for dynamic table sizing */

#define tab_prefixof(i)     codetabof(i)
#define tab_suffixof(i)     ((char_type *)(htab))[i]
#define de_stack	    ((char_type *)&tab_suffixof(1<<BITS))

code_int free_ent;			/* first unused entry */

code_int getcode();

#define CHECK_GAP 10000 /* ratio check interval */

int	block_compress = 1;
int	clear_flg;
long	ratio;
count_int checkpoint;

/*
 * the next two codes should not be changed lightly, as they must not
 * lie within the contiguous general code space.
 */

#define FIRST	257	/* first free entry */
#define CLEAR	256	/* table clear output code */

static int offset;
long int in_count = 1;			/* length of input */

/*
 *  Compress a file to memory-buffers and return TRUE if the compressed
 *  size is smaller than the actual size.
 */

CompressFile(name, fsize)
{
    long fcode;
    code_int i = 0;
    int c;
    code_int ent;
    int disp;
    code_int hsize_reg;
    int hshift;

    if (wildcmp("*.Z", name) || wildcmp("*.ARC", name) || wildcmp("*.ZOO", name)) {
	printf("  Will not compress %s\n", name);
	return(0);
    }

    CLen = 0;
    CWrite = GetHead(&CList);
    if (CWrite == NULL)
	CWrite = NewSComp();
    CWrite->N = 0;

    bzero(htab, sizeof(htab));
    bzero(codetab, sizeof(codetab));

    hsize = HSIZE;
    if ( fsize < (1 << 12) )
	hsize = min ( 5003, HSIZE );
    else if ( fsize < (1 << 13) )
	hsize = min ( 9001, HSIZE );
    else if ( fsize < (1 << 14) )
	hsize = min ( 18013, HSIZE );
    else if ( fsize < (1 << 15) )
	hsize = min ( 35023, HSIZE );
    else if ( fsize < 47000 )
	hsize = min ( 50021, HSIZE );

    offset = clear_flg = ratio = 0;
    in_count = 1;
    checkpoint = CHECK_GAP;
    n_bits  = INIT_BITS;		/* number of bits/code		    */
    maxbits = BITS;			/* user settable max # bits/code    */
    maxcode = MAXCODE(INIT_BITS);       /* maximum code, given n_bits       */
    maxmaxcode = 1 << BITS;		/* should NEVER generate this code  */
    free_ent = ((block_compress) ? FIRST : 256 );

    ent = ngetchar();

    hshift = 0;
    for ( fcode = (long) hsize;  fcode < 65536L; fcode *= 2L )
	hshift++;
    hshift = 8 - hshift;		/* set hash code range bound */

    hsize_reg = hsize;
    cl_hash((count_int)hsize_reg);      /* clear hash table */

    while ((c = ngetchar()) != EOF) {
	in_count++;
	fcode = (long) (((long) c << maxbits) + ent);
	i = ((c << hshift) ^ ent);      /* xor hashing */

	if (htabof (i) == fcode) {
	    ent = codetabof(i);
	    continue;
	} else if ((long)htabof (i) < 0)    /* empty slot */
	    goto nomatch;
	disp = hsize_reg - i;		/* secondary hash (after G. Knott) */
	if (i == 0)
	    disp = 1;
probe:
	if ((i -= disp) < 0)
	    i += hsize_reg;

	if (htabof (i) == fcode) {
	    ent = codetabof(i);
	    continue;
	}
	if ((long)htabof (i) > 0)
	    goto probe;
nomatch:
	output ((code_int) ent);
	ent = c;
	if (free_ent < maxmaxcode) {
	    codetabof(i) = free_ent++; /* code -> hashtable */
	    htabof(i) = fcode;
	}
	else if ((count_int)in_count >= checkpoint && block_compress)
	    cl_block ();
    }

    /*
     * Put out the final code.
     */

    output((code_int)ent);
    output((code_int)-1);

    return(CLen < fsize);
}

static char buf[BITS];

char_type lmask[9] = {0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00};
char_type rmask[9] = {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};

output( code )
code_int  code;
{
    register int r_off = offset, bits= n_bits;
    register char * bp = buf;

    if ( code >= 0 ) {
	/*
	 * Get to the first byte.
	 */
	bp += (r_off >> 3);
	r_off &= 7;
	/*
	 * Since code is always >= 8 bits, only need to mask the first
	 * hunk on the left.
	 */
	*bp = (*bp & rmask[r_off]) | (code << r_off) & lmask[r_off];
	bp++;
	bits -= (8 - r_off);
	code >>= 8 - r_off;
	/* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
	if ( bits >= 8 ) {
	    *bp++ = code;
	    code >>= 8;
	    bits -= 8;
	}
	/* Last bits. */
	if(bits)
	    *bp = code;

	offset += n_bits;
	if (offset == (n_bits << 3)) {
	    bp = buf;
	    bits = n_bits;
	    mwrite(bp, bits);
	    bp += bits;
	    bits = 0;
	    offset = 0;
	}

	/*
	 * If the next entry is going to be too big for the code size,
	 * then increase it, if possible.
	 */

	if (free_ent > maxcode || (clear_flg > 0)) {
	    /*
	     * Write the whole buffer, because the input side won't
	     * discover the size increase until after it has read it.
	     */
	    if (offset > 0)
		mwrite(buf, n_bits);
	    offset = 0;

	    if (clear_flg) {
		n_bits = INIT_BITS;
		maxcode = MAXCODE(INIT_BITS);
		clear_flg = 0;
	    } else {
		n_bits++;
		if (n_bits == maxbits)
		    maxcode = maxmaxcode;
		else
		    maxcode = MAXCODE(n_bits);
	    }
	}
    } else {
	/*
	 * At EOF, write the rest of the buffer.
	 */
	if (offset > 0)
	    mwrite(buf, (offset + 7) / 8);
	offset = 0;
    }
}


char *
xrindex(s, c)            /* For those who don't have it in libc.a */
register char *s, c;
{
    char *p;
    for (p = NULL; *s; s++) {
	if (*s == c)
	    p = s;
    }
    return(p);
}


cl_block()             /* table clear for block compress */
{
    register long int rat;

    checkpoint = in_count + CHECK_GAP;

    if (in_count > 0x007fffff) { /* shift will overflow */
	rat = CLen >> 8;
	if (rat == 0) {          /* Don't divide by zero */
	    rat = 0x7fffffff;
	} else {
	    rat = in_count / rat;
	}
    } else {
	rat = (in_count << 8) / CLen;      /* 8 fractional bits */
    }
    if (rat > ratio) {
	ratio = rat;
    } else {
	ratio = 0;
	cl_hash ( (count_int) hsize );
	free_ent = FIRST;
	clear_flg = 1;
	output ( (code_int) CLEAR );
    }
}

cl_hash(hsize)          /* reset code table */
	register count_int hsize;
{
	register count_int *htab_p = htab+hsize;
	register long i;
	register long m1 = -1;

	i = hsize - 16;
	do {				/* might use Sys V memset(3) here */
		*(htab_p-16) = m1;
		*(htab_p-15) = m1;
		*(htab_p-14) = m1;
		*(htab_p-13) = m1;
		*(htab_p-12) = m1;
		*(htab_p-11) = m1;
		*(htab_p-10) = m1;
		*(htab_p-9) = m1;
		*(htab_p-8) = m1;
		*(htab_p-7) = m1;
		*(htab_p-6) = m1;
		*(htab_p-5) = m1;
		*(htab_p-4) = m1;
		*(htab_p-3) = m1;
		*(htab_p-2) = m1;
		*(htab_p-1) = m1;
		htab_p -= 16;
	} while ((i -= 16) >= 0);
	for ( i += 16; i > 0; i-- )
		*--htab_p = m1;
}

UnCompressFile(insize)
{
    register char_type *stackp;
    register int finchar;
    register code_int code, oldcode, incode;

    /*
     * As above, initialize the first 256 entries in the table.
     */

    bzero(htab, sizeof(htab));
    bzero(codetab, sizeof(codetab));

    offset = clear_flg = ratio = 0;
    in_count = 1;
    checkpoint = CHECK_GAP;
    n_bits  = INIT_BITS;		/* number of bits/code		    */
    maxbits = BITS;			/* user settable max # bits/code    */
    maxcode = MAXCODE(INIT_BITS);       /* maximum code, given n_bits       */
    maxmaxcode = 1 << BITS;		/* should NEVER generate this code  */

    for ( code = 255; code >= 0; code-- ) {
	tab_prefixof(code) = 0;
	tab_suffixof(code) = (char_type)code;
    }
    free_ent = ((block_compress) ? FIRST : 256 );

    finchar = oldcode = getcode();
    if (oldcode == -1)          /* EOF already? */
	return; 		/* Get out of here */
    oputc((char)finchar);       /* first code must be 8 bits = char */
    stackp = de_stack;

    while ((code = getcode()) > -1) {
	if ((code == CLEAR) && block_compress) {
	    for (code = 255; code >= 0; code--)
		tab_prefixof(code) = 0;
	    clear_flg = 1;
	    free_ent = FIRST - 1;
	    if ((code = getcode()) == -1)   /* O, untimely death! */
		break;
	}
	incode = code;
	/*
	 * Special case for KwKwK string.
	 */
	if (code >= free_ent) {
	    *stackp++ = finchar;
	    code = oldcode;
	}

	/*
	 * Generate output characters in reverse order
	 */
	while ( code >= 256 ) {
	    *stackp++ = tab_suffixof(code);
	    code = tab_prefixof(code);
	}
	*stackp++ = finchar = tab_suffixof(code);

	/*
	 * And put them out in forward order
	 */
	do
	    oputc (*--stackp);
	while (stackp > de_stack);

	/*
	 * Generate the new entry.
	 */
	if ((code=free_ent) < maxmaxcode) {
	    tab_prefixof(code) = (unsigned short)oldcode;
	    tab_suffixof(code) = finchar;
	    free_ent = code+1;
	}
	/*
	 * Remember previous code.
	 */
	oldcode = incode;
    }
}

code_int
getcode()
{
    /*
     * On the VAX, it is important to have the register declarations
     * in exactly the order given, or the asm will break.
     */

    register code_int code;
    static int offset = 0, size = 0;
    static char_type buf[BITS];
    register int r_off, bits;
    register char_type *bp = buf;

    if (clear_flg > 0 || offset >= size || free_ent > maxcode) {
	/*
	 * If the next entry will be too big for the current code
	 * size, then we must increase the size.  This implies reading
	 * a new buffer full, too.
	 */
	if ( free_ent > maxcode ) {
	    n_bits++;
	    if ( n_bits == maxbits )
		maxcode = maxmaxcode;	/* won't get any bigger now */
	    else
		maxcode = MAXCODE(n_bits);
	}
	if ( clear_flg > 0) {
	    maxcode = MAXCODE (n_bits = INIT_BITS);
	    clear_flg = 0;
	}
	size = oread(buf, n_bits);
	if (size <= 0)
	    return -1;			/* end of file */
	offset = 0;
	size = (size << 3) - (n_bits - 1);
    }
    r_off = offset;
    bits = n_bits;

	/*
	 * Get to the first byte.
	 */
	bp += (r_off >> 3);
	r_off &= 7;
	/* Get first part (low order bits) */

	code = (*bp++ >> r_off);

	bits -= (8 - r_off);
	r_off = 8 - r_off;		/* now, offset into code word */
	/* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */
	if ( bits >= 8 ) {
	    code |= *bp++ << r_off;
	    r_off += 8;
	    bits -= 8;
	}
	/* high order bits. */
	code |= (*bp & rmask[bits]) << r_off;

    offset += n_bits;

    return code;
}

SHAR_EOF
cat << \SHAR_EOF > Makefile

#   Makefile for backup

SYMS=	include:symbols.m
SYMC=	include:local/makesymbols.c
CFLAGS= +L +I$(SYMS)
LFLAGS= +Q
DD=	srcc:
OD=	T:

$(DD)backup:    backup.c
    cc $(CFLAGS) backup.c -o $(OD)backup.o
    ln +Q $(OD)backup.o -lsup32 -lc32 -o $(DD)backup
    delete $(OD)backup.o

$(SYMS):    $(SYMC)
    make -f include:local/Makefile

SHAR_EOF
cat << \SHAR_EOF > backup.doc

			       BACKUP.DOC

				 V2.01

			     3 November 1988
	 (c)Copyright 1988, Matthew Dillon, All Rights Reserved
		   Freely Distributable for non-profit only
	       (this is the same document as backup.doc v2.00)

				  (I)
				OVERVIEW

    Backup and Restore (same executable, just renamed) allow you to backup
    any directory tree with optional compression, and later extract all
    or part of the tree.  The protection, date, and file comment is saved
    with each file.

    SEE SECTION (6), FLOPICAL BACKUP, for options pertaining to backing
    up directly to floppies.

    LIMITATIONS:    When using -c (compress), the entire result of the
    compressed file must fit into memory.  Any files (after compression,
    if any) larger than a floppy when backing up to floppies cannot be
    handled yet.


				  (I)
			      INSTALLATION

    Copy the (1) executable to your C: directory twice, naming one
    'Backup' and the other 'Restore'.  The program looks at the first
    character of its name to determine whether the default is to backup
    or restore.  If you don't have the space, all is not lost ... you
    simply need to specify backup or restore explicitly with the options
    (-b or -r).

				   (2)
			     EXOTIC FEATURES

    Backup does not write directory trees which contain no files.  Thus,
    when doing incremental backups on a large hard disk, the structure of
    directory sub-trees which have no new files in them will not be
    written.  So if you only modified a couple of files since the last
    backup, there won't be 50K of structure-overhead.

    You can specify compression (-c) in the backup.  All files are
    compressed (there must be enough RAM to hold the largest file in
    its compressed state).  Those files which would turn out larger after
    compression than before are NOT compressed.  Certain file suffixes
    cause the compression attempt to be skipped entire:  .Z, .ARC, and .ZOO
    files

	  IT IS SUGGESTED YOU USE THE COMPRESSION FEATURE EVEN THOUGH IT
	  SLOWS DOWN THE BACKUP.  The resulting backup file will be
	  significantly reduced in size.  Even though a full backup will
	  take a long time, you only do that every once in a while and
	  as incremental backups are shorter, they take much less time.

    'Pick' patterns are supported (backup only paths which match the
    specified patterns), and 'Discard' patterns are supported (do not backup
    paths which match the specified patterns).	Discard patterns override
    Pick patterns.

				  (3)
				OPTIONS

    Here is a list of Backup/Restore options.

	-A	ARCHIVE. (Backup) Causes the archive bit to be cleared
		on files being backed up.  (Protection bit -> 1)

		(Restore):  If specified, the archive bit is cleared on
		restored files.  Otherwise the archive bit will be set,
		causing the files to be backed up again on the next
		incremental backup.

	-U	UPDATE.  (Backup) Restricts the backup to only those files
		which have the archive bit set.  (Protection bit == 0)

	-b	BACKUP	(this is the default if the first character in
		the executable is a 'b')

	-r	RESTORE (this is the default if the first character in
		the executable is an 'r')

	-a	APPEND (Backup)     Cause the backup to be appended to
		the specified (-o) file rather than overwriting it.  Useful
		for incremental backups.

	-c	COMPRESS (Backup)   Cause all files to be compressed, if
		possible.  Restore automatically decompresses files which
		have been compressed.  This is the same algorithm used
		in the UNIX compress command.

		NO COMPRESSION WILL EVEN BE ATTEMPED ON FILES WITH THE
		FOLLOWING SUFFIXES: .Z .ARC .ZOO

	-f[#kb] Turn on backup fragmentation and specify the fragment size,
		default 800K (for backing up to floppies).  NOTE: Currently,
		files larger than the fragment size cannot be backed up!

		Note: a suffix is added to the file name w/ a sequence
		number, but each fragment is a complete backup file unto
		itself and may be restored out of order.

		Any numerical value specified is in K, i.e. -f800 == 800K

	-Fvol:	Add the volume (df0:, df1:, etc..) to the drive sequencing
		list.  The idea is to specify several -F options which are
		cycled through and prefix each fragment.  This also
		enables prompting... the program prompts whenever it
		switches to the next fragment.

		If -F is not specified at all and -f is, no prompting
		occurs.

	-s	Only display directories as we go along

	-S	Silent... don't display files or directories

	-v	Verbose... display those files which will NOT be backed
		up as well as those that will.

	-l/-t	(both do the same thing).  (Restore)   LIST the archive
		only, do not do an actual restore.

	-T	(Restore)   Restore ONLY the timestamp, comment, and
		protection fields.  This is incredibly useful to
		restore timestamp information to files that already
		exist.	NO files are overwritten or created on the
		destination volume(s).

	-n#	(Backup)    Sets the output buffer size, in KILOBYTES.
		Default is 64.

	-pPAT	(Backup)    Restrict the backup to files which match the
		specified pattern.  Up to 32 patterns may be specified.

		(Restore)   Extract only those files which match the
		specified pattern.

		NOTE:  The pattern is matched with the COMPLETE PATH as
		shown in the -t option.  '*' and '?' wildcarding is supported

	-dPAT	(Backup)    Do not backup files which match the specified
		pattern.   Up to 32 patterns may be specified.

		(Restore)   Do not extract files which match the specified
		pattern.

		NOTE: The pattern is matched with the COMPLETE PATH as
		shown in the -t option.  '*' and '?' wildcarding is supported.

	-oFILE	(Backup)    Specify the OUTPUT FILE for a backup.  IF NO
		OUTPUT FILE IS SPECIFIED, NO OUTPUT FILE IS CREATED.  I.E.
		you can take a second pass just to clear the archive bit.

		(Restore)   Specify the directory to place the restored
		directory tree.  (This overrides the volume the backup
		was saved from).  If no directory specified, the original
		volume used in the backup will be restored to.	If no
		volume was specified in the backup but rather a relative
		path was, the restore will be relative to the current
		directory.

				  (4)
		      STANDARD METHOD OF BACKING UP

    Note that with combinations of -o, -A, and -U, you have many choices
    available to you when backing up files.  For hard disks, backups are
    normally done in two phases:

	-Once a month do a complete backup of your hard disk
	-Every day do a much smaller 'incremental' backup which backs
	 up only those files which have been modified/created since the
	 last backup.

	The master backup, plus the N incremental backups together
	reconstruct your disk up to the moment.  After a while the
	number of incremental backups will be so great (and messy),
	that you will want to do another complete backup and start
	again.

	IN THIS WAY, the COMPRESS option can be put to good use.  The
	incremental backups are usually small, and can be compressed
	relatively quickly.  The complete backup should also be compressed
	(-c in case you forgot) though this will take a much longer time...
	but then again the complete backup is not done every day.

    UPDATING THE ARCHIVE BIT ON THE FLY VERSES TAKING A SECOND PASS.
    Updating the archive bit involves writing to the disk.  For complete
    safety you probably do NOT want to update the archive bit while you
    are backing up the files in question.  The following two-pass method
    works well:

		(COMPLETE BACKUP)

	backup	-c -d"*.o" volume: -ooutput
	backup	-A volume:

		(INCREMENTAL BACKUP)

	backup -U -c -d"*.o" volume: -ooutput
	backup -A volume:

    The -d option is saying "Don't backup all those object modules I left
    laying around".

    The first command backs up only those files which have be modified
    or created but does NOT update the archive bit.  The second command
    updates the archive bit without doing anything else (no output file
    is specified and thus none is created).  The second command goes
    extremely quickly since files are not actually read.

    For your INCREMENTAL backups, risking modifying the archive bit while
    backing the files up is feasible,  Since very few files will be
    backed up (comparitively):

	backup -a -UA -c -d"*.o" volume: -ooutput

    In this case I use the -a option (append).  You might NOT want to do
    this and save the incremental backups as separate files.


				  (5)
		      STANDARD METHOD OF RESTORING

    Restoring a backup to the original volume: (Restore or Backup -r)

    NOTE:  If the -A option is specified, restored files will have
    their Archive bit cleared.	If -A is NOT specified, files will have
    their archive bit set and will thus be re-backed up on the next
    incremental backup.

    NOTE:  When restoring files, remember that the incremental backups
    will have the newest modifications and should be specified LAST:

	Restore completebkfile incr1 incr2 incr3....

    Partial Restore using -p:

	Restore -pstuffIWant bkfile1 bkfile2 bkfile3...

    ARCHIVE LISTING:

	Restore -t bkfile1 bkfile2....

	NOTE:	Two 'sizes' are given in the listing.  The first
	is the # bytes the file takes up in the archive, the
	second is the actual size of the file.


				  (6)
		      FLOPICAL BACKUP AND RESTORE

    There are two problems associated with floppy backup and restore:

    (1) A single file on the HD may be two big for a single floppy
    (2) It may take a lot of floppies to backup an HD

    Additionaly, one wants the following conveniences:

    (1) Be able to backup to a logical progression of (floppy) drives,
	inserted blank disks in while the system is working on other
	drives.

    (2) Be able to restore the same way

    (3) If one or more floppies is destroyed be able to restore from the
	remaining floppies.

	    Currently, you can specify which devices to cycle through
	    with the -F option (-Fdf0: -Fdf1: -Fdf2:), but BACKUP will
	    always prompt you before continuing.  Since it uses the
	    console device, you can specify the yes response as many
	    times as you have floppies in ready drives.

	    Currently, files larger than the specified -f size (800K
	    default) cannot be handled if using -f/-F ... However, by
	    enabling compression larger files can still fit.

	    Currently, the entire compressed result of a file must be
	    able to fit into memory during the backup.


				   ***

    BACKUP.	Specify the -f option and then -Fvol: for each floppy
    drive (in the order you want) to backup to.  Example:

	-f -Fdf0: -Fdf1:

    (Also keep in mind all the options listed in sections (3), (4), & (5))
    Currently, you must have PRE-FORMATTED all your backup disks since the
    backup/restore works through normal files.	The system will prompt for
    readyness every time it completes a section.

    The backup process never deletes files, only creates or overwrites
    them.  Thus, accidently sticking in a non-blank or incorrect backup
    disk will probably result in a disk-full requester.... not Fatal, though
    inconvenient.

    You must still specify the -o option to name the output file, which will
    be prefixed with the next -F volume in sequence and suffixed with the
    sequence number. (.nn)  The disk labels for your floppies may all be
    the same.

    EXAMPLE:

	backup	-c -d"*.o" -f -Fdf0: -Fdf1: -Fdf2: volume: -oxxbak
	backup	-A volume:

	NOTE:	You do NOT specifiy a volume prefix in the -o option ..
	those specified by -F are automatically cycled through as the
	prefix.

    * If your hard disk has enough space on it I suggest you backup to an
      alternate partition using only -f (not -F which forces user prompts),
      then copy the chunks to floppies after the entire backup has
      completed.

				   ***

    RESTORE.	Restore works the same way.  EACH DISK IS INDEPENDANT!	You
    may insert disks in any order to be restored.  Simply execute the
    standard restore command in section (5) for each floppy.

    Destroyed disks may be skipped.  Currently, no support for partially
    restored disks exists... i.e. the archive has be clean.



SHAR_EOF
#	End of shell archive
exit 0
-- 
Bob Page, U of Lowell CS Dept.  page@swan.ulowell.edu  ulowell!page
Have five nice days.