
/*
 * 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;
}

