
/*
 * BACKUP.C
 *
 * (C)Copyright 1986-90, 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
 *
 *	NOTE:	Any files/directories containing the keyword NOBACKUP in
 *		its comment field will not be backed up.
 *
 *  options:
 *
 *    -0       (Restore): Cause all restored files to be placed
 *			 in the current (or -o) directory.  No directory
 *			 structure is restored at all
 *
 *    -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
 *
 *    -Cpat    add file pattern to those which will not
 *	       be compressed.
 *
 *    -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
 *
 *    -Ooffset Set Offset (manual recover on restore)
 *
 * ---------------------------------------------------------------------
 *
 * 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 "defs.h"
#include <libraries/dos.h>
#include <libraries/dosextens.h>

ubyte	Break;
ubyte	Restore;
ubyte	NoStructure;
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;
short	MyBreak;

char DirPath[256];
short DPLen;

char	*BadFormat;

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

MLIST	VList;	    /*	Volume list (-F), of NODEs              */
MLIST	DList;	    /*	Directory Stack 			*/

MLIST	CSList;     /*	compression patterns			*/
MLIST	PSList;     /*	pick patterns				*/
MLIST	DSList;     /*	don't pick patterns                     */

long	CLen;	    /*	Actual compressed file output length	*/
MLIST	CList;	    /*	List of memory buffers			*/
SCOMP	*CWrite;    /*	Current memory buffer pointer		*/

extern void *GetHead(MLIST *);
extern void *GetTail(MLIST *);
extern void *GetSucc(MNODE *);
extern void *GetPred(MNODE *);

void	AddPattern  (MLIST *, char *);
void	BackupFiles (int, char **);
void	RestoreFiles(int, char **, long);
FIB	*GetFileInfo(char *, long *);
void	FreeFileInfo(FIB *, long);
void	PushDir     (char *, long);
int	PopDirs     (uword);
void	BackupFlagHasFile(void);
long	scan_directory(FIB *, long);
int	mycheckbreak(void);
long	scan_file   (FIB *, long);
int	match_file  (char *);
void	writeheaders(FIB *);
int	newfile     (void);
int	read_file   (short, char *, long, long);
int	openoutput  (char *, int, int);
void	oputc	    (char);
void	outlwatseek (long, long);
void	owrite	    (void *, long);
void	dumpoutput  (void);
void	dumpcrc     (void);
void	closeoutput (void);
long	outbytes    (void);
void	outentry    (ubyte, int, void *);
int	openinput   (char *, long);
void	closeinput  (void);
void	seekinputend(void);
void	setinputbound(long);
long	oread	    (void *, long);
void	AppendCrc   (ubyte *, long);
int	oreadchar   (void);
void	rollbackinput(void);
void	mputc	    (char);
void	mwrite	    (char *, long);
SCOMP	*NewSComp   (void);
void	transfer0   (long);
void	transfer1   (void);

int	brk	    (void);
void	UnCompressFile(long);
long	CompressFile(char *, long);


int
brk()
{
    MyBreak = 1;
    return(0);
}

main(ac, av)
char **av;
{
    register short i, notdone;
    register char  *str;
    long manOffset = 0;
    short xac = 0;
    static char *Xav[256];

    BadFormat = "Bad Format";
    SetSignal(0, SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D);
    onbreak(brk);

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

    NewList(&PSList);
    NewList(&DSList);
    NewList(&CSList);

    AddPattern(&CSList, "*.Z");
    AddPattern(&CSList, "*.ARC");
    AddPattern(&CSList, "*.ZOO");
    AddPattern(&CSList, "*.LZH");

    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.06, (c)Copyright 1988,1989 Matthew Dillon, All Rights Reserved\n", str);
	printf("Backup/Restore is useful for HD backup and file transfers\n");
	printf("%s -rbactlvASTU -d<pat> -p<pat> -f[#kb] -F<vol> -n<#kb> -ofile <file-dir-list>\n", str);
	puts("\nread the docs for more info.  Example for use w/ file transfer:");
	puts("\t1> backup -c file/dir -oout.bak     -create archive");
	puts("\t1> restore -t out.bak               -list archive");
	puts("\t1> restore out.bak [-odest]         -restore archive");
    }

    for (i = 1; i < ac; ++i) {
	str = av[i];
	if (*str != '-') {
	    if (xac == sizeof(Xav)/sizeof(Xav[0])) {
		printf("Maximum %d files/dirs specifiable on command line\n", sizeof(Xav)/sizeof(Xav[0]));
		exit(15);
	    }
	    Xav[xac++] = str;
	    continue;
	}
	notdone = 1;
	++str;

	while (notdone && *str) {
	    switch(*str) {
	    case '0':
		NoStructure = 1;
		break;
	    case 'r':
		Restore = 1;
		break;
	    case 'b':
		Restore = 0;
		break;
	    case 'a':
		Append = 1;
		break;
	    case 'c':
		Compress = 1;
		break;
	    case 'd':
		AddPattern(&DSList, 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 */
		    char *name = (str[1]) ? str + 1 : av[++i];
		    NODE *node = malloc(sizeof(NODE) + strlen(name) + 1);
		    node->ln_Name = (char *)(node + 1);
		    strcpy((char *)(node + 1), name);
		    AddTail(&VList, node);
		}
		notdone = 0;
		break;
	    case 'O':
		{
		    char *name = (str[1]) ? str + 1 : av[++i];
		    manOffset = atoi(name);
		}
		Overide = 1;
		notdone = 0;
		break;
	    case 'n':
		{
		    char *name = (str[1]) ? str + 1 : av[++i];
		    BufSize = atoi(name) * 1024;
		}
		if (BufSize <= 0)
		    BufSize = 65536;
		notdone = 0;
		break;
	    case 'o':
		{
		    char *name = (str[1]) ? str + 1 : av[++i];
		    OutFile = name;
		}
		notdone = 0;
		break;
	    case 'p':
		{
		    char *name = (str[1]) ? str + 1 : av[++i];
		    AddPattern(&PSList, name);
		}
		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 'C':
		{
		    char *name = (str[1]) ? str + 1 : av[++i];
		    AddPattern(&CSList, name);
		}
		notdone = 0;
		break;
	    case 'S':
		ShowFiles = 0;
		ShowDirs  = 0;
		break;
	    case 'T':
		TimeStampOnly = 1;
		break;
	    case 'U':
		Update = 1;
		break;
	    default:
		puts("failure, backup w/ no args for help");
		exit(20);
	    }
	    ++str;
	}
    }
    if (i > ac) {
	puts("Expected argument to last option!");
	exit(20);
    }
    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(xac, Xav, manOffset);
    else
	BackupFiles(xac, Xav);
    return(0);
}

void
AddPattern(list, str)
MLIST *list;
char *str;
{
    register NODE *node = malloc(sizeof(NODE));

    AddTail(list, node);
    node->ln_Name = str;
}


long SaveLock;

void
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 = 0; i < ac; ++i) {
	str = av[i];

	/*
	 *  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((uword)-1);
    }

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

DATESTAMP Date;
ulong Crc;		/*  compare w/	*/
ulong CrcGen;
ulong CrcSeek;		/*  (backup) where in file is CrcGen lw?    */
char CrcEna;
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 */

void
RestoreFiles(ac, av, startoffset)
char **av;
long startoffset;
{
    register short i;
    register char *str;
    char notdone;
    char havedate;
    char havecrc;
    char havepro;
    char havecom;
    char haveinc;
    long bytes;
    long actual;
    long baselock;
    long lock;
    PROC *proc = (PROC *)FindTask(NULL);

    if (OutFile) {
	lock = Lock(OutFile, SHARED_LOCK);
	if (lock == NULL && (lock = CreateDir(OutFile))) {
	    UnLock(lock);
	    lock = Lock(OutFile, SHARED_LOCK);
	}
    } else {
	lock = Lock("", SHARED_LOCK);
    }
    if (!lock) {
	printf("Unable to lock/create %s\n", (OutFile) ? OutFile : "<currentdir>");
	return;
    }
    baselock = lock;
    SaveLock = CurrentDir(DupLock(baselock));

    for (i = 0; i < ac; ++i) {
	str = av[i];

	lock = CurrentDir(SaveLock);
	if (openinput(str, startoffset) == 0) {
	    startoffset = 0;
	    printf("Unable to open %s for input\n", str);
	    CurrentDir(lock);
	    continue;
	}
	startoffset = 0;
	CurrentDir(lock);

	lock = DupLock(baselock);
	UnLock(CurrentDir(lock));

	notdone = 1;
	havedate = havecrc = havepro = havecom = haveinc = 0;

	while (notdone) {
	    short c = oreadchar();
	    short l = oreadchar();
recover:
	    switch(c) {
	    case -1:		/*  EOF */
		notdone = 0;
		break;
	    case 0:		/*  NUL */
	    case 'z'&0x1F:      /*  ^Z  */
		if (Overide == 0)
		    notdone = 0;
		break;
	    case XVOL:
	    case XDDS:
		oread(Scr, l);
		Scr[l] = 0;

		/*
		 *  In the replacement of the first
		 *  argument case, we are already in
		 *  the proper directory.
		 */

		if (EMPTYLIST(DList) && OutFile) {
		    register short j = strlen(OutFile);

		    UnLock(CurrentDir(DupLock(SaveLock)));
		    c = XDDS;
		    strcpy(Scr, OutFile);
		    if (j--) {
			if (OutFile[j] == ':') {
			    Scr[j] = 0;
			    c = XVOL;
			}
			if (OutFile[j] == '/')
			    Scr[j] = 0;
		    }
		}
		PushDir(Scr, c);
		if (ShowDirs)
		    printf("%-40s\n", DirPath);
		if (ListOnly)
		    break;
		if (NoStructure == 0) {
		    if (c == XVOL) {
			lock = Lock(DirPath, SHARED_LOCK);  /*  DirPath incs ':'    */
		    } else {
			lock = Lock(Scr, SHARED_LOCK);
			if (lock == NULL && (lock = CreateDir(Scr))) {
			    UnLock(lock);
			    lock = Lock(Scr, SHARED_LOCK);
			}
		    }
		}
		{
		    SDIR *sd = GetTail(&DList);
		    sd->HaveFile = 1;		    /*	don't remove dir    */
		}
		if (NoStructure == 0) {
		    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;
		    if (NoStructure == 0) {
			lock = ParentDir(proc->pr_CurrentDir);
			if (lock == NULL) {
			    puts("Unable to ParentDir!");
			    notdone = 0;
			} else {
			    UnLock(CurrentDir(lock));
			    /*
			    if (c == 0)
				DeleteFile(Scr);
			    */
			}
		    }
		}
		break;
	    case XCRC:
		if (l != 4) {
		    puts(BadFormat);
		    notdone = SkipBad(&c, &l);
		    goto recover;
		}
		oread(&Crc, l);
		havecrc = 1;
		break;
	    case XDAT:
		if (l != sizeof(DATESTAMP)) {
		    puts(BadFormat);
		    notdone = SkipBad(&c, &l);
		    goto recover;
		}
		oread(&Date, l);
		havedate = 1;
		break;
	    case XPRO:
		if (l != 4) {
		    puts("Expected 4 bytes for protection");
		    notdone = SkipBad(&c, &l);
		    goto recover;
		}
		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 (havecrc && Crc != CrcGen)
		    printf("WARNING, Crc failed! %s\n", Scr);
		if (havedate)
		    setfiledate(Scr, &Date);
		if (havecom && Comment[0])
		    SetComment(Scr, Comment);
bend:
		havecom = havecrc = havedate = havepro = haveinc = 0;
		break;
	    case XHDR:
		if (l != sizeof(DATESTAMP)) {
		    puts("expected sizeof datestamp");
		    notdone = SkipBad(&c, &l);
		    goto recover;
		}
		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 = SkipBad(&c, &l);
		    goto recover;
		}
		oread(&IncSize, 4);
		oread(&IncSeek, 4);
		oread(&IncLen,  4);
		haveinc = 1;
		break;
	    default:
		printf("Unknown Record Type: %02x\n", c);
		notdone = SkipBad(&c, &l);
		goto recover;
	    }
	    setinputbound(-1);
	    if (mycheckbreak()) {
		Break = 1;
		notdone = 0;
		break;
	    }
	}
	if (Break)
	    break;
    }
    UnLock(baselock);
    UnLock(CurrentDir(SaveLock));
}

SkipBad(pc, pl)
short *pc;
short *pl;
{
    long skip = 0;
    long offset = otellread();
    short notdone = 1;
    short c;
    short l;

loop:
    while ((c = oreadchar()) >= 0 && c != XDAT)
	++skip;
    if (c == XDAT) {
	l = oreadchar();
	++skip;
	if (l != sizeof(DATESTAMP))
	    goto loop;
    }
    printf("Error at offset %d, skipping %d bytes", offset, skip);
    if (c < 0) {
	printf(" (EOF reached!)");
	notdone = 0;
    }
    printf("\n");
    *pc = c;
    *pl = l;
    return((int)notdone);
}

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);
}

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

void
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 if (type == XDDS)
	strcat(DirPath+DPLen, "/");
    DPLen += strlen(DirPath+DPLen);
}

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

    while (num && (sd = GetTail(&DList))) {
	lasthave |= sd->HaveFile;
	if (!Restore && 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((int)lasthave);
}

void
BackupFlagHasFile()
{
    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);
    }
}


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

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

    if (Update == 0)        /* force save entire tree */
	BackupFlagHasFile();
    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);
	    if (NoBack(fib) == 0)
		lock = scan_directory(fib, lock);
	    PopDirs(1);
	} else {
	    if (NoBack(fib) == 0)
		lock = scan_file(fib, lock);
	}
	FreeFileInfo(fib, lock);
	if (Break || mycheckbreak()) {
	    Break = 1;
	    break;
	}
    }
    CurrentDir(save);
    return(dirlock);
}

int
mycheckbreak()
{

    if (MyBreak || (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.
 */

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

    strcat(DirPath, fib->fib_FileName);

    if (Update && (fib->fib_Protection & FIBF_ARCHIVE))
	goto nomatch;
    if (!match_file(DirPath))
	goto nomatch;

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

    BackupFlagHasFile();
    if (OutFile) {
	save = CurrentDir(lock);
	if (openinput("", 0) == 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();
	} 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);
	}
	outlwatseek(CrcSeek, CrcGen);
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);
}

int
match_file(name)
char *name;
{
    register NODE *node;

    for (node = (NODE *)PSList.mlh_Head; node->ln_Succ; node = node->ln_Succ) {
	if (WildCmp(node->ln_Name, name))
	    break;
    }
    if (node->ln_Succ == NULL && !EMPTYLIST(PSList))
	return(0);

    for (node = (NODE *)DSList.mlh_Head; node->ln_Succ; node = node->ln_Succ) {
	if (WildCmp(node->ln_Name, name))
	    return(0);
    }
    return(1);
}

void
writeheaders(fib)
register FIB *fib;
{
    long dummy = 0;
    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);
    outentry(XCRC, 4, &dummy);
    CrcSeek = outbytes() - 4;
}

/*
 *  (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
 */

int
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);
}

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

    strcat(DirPath, fname);

    if (!match_file(DirPath)) {
	if (Verbose)
	    printf("%-40s (NOT ACCEPTED)\n", DirPath);
	goto nomatch;
    }

    if (ShowFiles)
	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:
	CrcGen = 0;
	CrcEna = 1;
	UnCompressFile(inbytes);
	CrcEna = 0;
	save = CrcGen;
	transfer1();                /*  ??? needed ??? */
	CrcGen = save;
	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;

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

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

void
outlwatseek(pos, lw)
long pos;
long lw;
{
    long index = BufI + pos - OutBytes; /*  index into buffer	*/

    if (index > BufI - 4) {
	puts("SW ERROR");
	return;
    }
    if (index >= 0) {
	Buf[index+0] = lw >> 24;
	Buf[index+1] = lw >> 16;
	Buf[index+2] = lw >> 8;
	Buf[index+3] = lw;
    } else {				/*  uh oh   */
	if (Outfd >= 0) {
	    long old = lseek(Outfd, 0, 1);  /*  save current pos */
	    lseek(Outfd, pos, 0);           /*  seek back        */
	    write(Outfd, (char *)&lw, 4);   /*  write data       */
	    lseek(Outfd, old, 0);           /*  seek to end      */
	}
    }
}

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

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

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

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

long
outbytes()
{
    return(OutBytes);
}

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

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

ulong OMax;

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

void
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;
}

void
setinputbound(max)
long max;
{
    OMax = max;
}

long
oread(buf, n)
void *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);
	movmem(InBuf + InBufN, buf, avail);
	AppendCrc(buf, avail);
	OMax-= avail;
	n   -= avail;
	buf = (void *)((char *)buf + avail);
	x   += avail;
	InBufI = read(Infd, InBuf, InBufSize);
	InBufN = 0;
	if (InBufI <= 0) {
	    InBufI = 0;
	    return(x);
	}
    }
    movmem(InBuf + InBufN, buf, n);
    AppendCrc(buf, n);
    InBufN += n;
    x += n;
    OMax -= n;
    return(x);
}

long
otellread()
{
    long pos = 0;
    if (Infd >= 0)
	pos = lseek(Infd, 0L, 1) - (InBufI - InBufN);
    return(pos);
}

int
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);
	}
    }
    if (CrcEna)
	CrcGen = (CrcGen << 3) ^ InBuf[InBufN] ^ (CrcGen >> 29);
    return((int)InBuf[InBufN++]);
}

void
AppendCrc(buf, bytes)
register ubyte *buf;
register long bytes;
{
    if (CrcEna) {
	while (bytes--)
	    CrcGen = (CrcGen << 3) ^ *buf++ ^ (CrcGen >> 29);
    }
}

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

void
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;
	}
	sc->N = 0;
	CWrite = sc;
    }
    ((char *)(sc + 1))[sc->N++] = v;
}

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

    CLen += n;
    while ((avail = sc->Bytes - sc->N) < n) {
	movmem(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;
	}
	sc->N = 0;
    }
    movmem(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);
}

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

    if (Outfd < 0)
	return;

    CrcGen = 0;
    CrcEna = 1;
    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;
    }
    CrcEna = 0;
}

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

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

    if (Outfd < 0)
	return;
    CrcGen = 0;
    CrcEna = 1;
    for (sc = GetHead(&CList); sc && n; sc = GetSucc(sc)) {
	len = sc->Bytes;
	ptr = (ubyte *)(sc + 1);
	if (n < len)
	    len = n;
	n -= len;

	AppendCrc(ptr, len);

	while (len > BufSize - BufI) {
	    movmem(ptr, Buf + BufI, BufSize - BufI);
	    ptr += BufSize - BufI;
	    len -= BufSize - BufI;
	    OutBytes += BufSize - BufI;
	    BufI = BufSize;
	    dumpoutput();
	}
	movmem(ptr, Buf + BufI, len);
	BufI += len;
	OutBytes += len;
    }
    CrcEna = 0;
    if (n)
	puts("Unexpected EOF in compression file");
}

/*
 *  NoBack() returns true if 'NOBACK' is found in the fib_Comment
 *  field.
 */

NoBack(fib)
FIB *fib;
{
    char *str = fib->fib_Comment;

    while (*str) {
	if (*str == 'N' && strncmp(str, "NOBACK", 6) == 0)
	    return(1);
	++str;
    }
    return(0);
}

/*
 *  quick hack to fix some macro bugs...
 */

long
CTOB(cptr)
void *cptr;
{
    return((long)cptr >> 2);
}

void *
BTOC(bptr)
long bptr;
{
    return((void *)(bptr << 2));
}

void *
GetHead(list)
MLIST *list;
{
    MNODE *node = list->mlh_Head;
    if (node->mln_Succ)
	return(node);
    return(NULL);
}

void *
GetTail(list)
MLIST *list;
{
    MNODE *node = list->mlh_TailPred;
    if (node->mln_Pred)
	return(node);
    return(NULL);
}

void *
GetSucc(node)
MNODE *node;
{
    node = node->mln_Succ;
    if (node->mln_Succ)
	return(node);
    return(NULL);
}

void *
GetPred(node)
MNODE *node;
{
    node = node->mln_Pred;
    if (node->mln_Pred)
	return(node);
    return(NULL);
}


