/*
 *
 *
 * The	information  in  this  document  is  subject  to  change
 * without  notice  and  should not be construed as a commitment
 * by Digital Equipment Corporation or by DECUS.
 *
 * Neither Digital Equipment Corporation, DECUS, nor the authors
 * assume any responsibility for the use or reliability of  this
 * document or the described software.
 *
 *	Copyright (C) 1980, DECUS
 *
 *
 * General permission to copy or modify, but not for profit,  is
 * hereby  granted,  provided that the above copyright notice is
 * included and reference made to  the	fact  that  reproduction
 * privileges were granted by DECUS.
 *
 */

/*
 *			A R C H I V E
 *
 * Archiver, roughly from software tools.
 *
 */

/*
 * title	ar	text file archiver
 * index		text file archiver
 * 
 * synopsis
 * 	ar [-options] [-z logfile] archive_name file[s]
 * 
 * description
 * 
 * 	Ar manages archives (libraries) of source files, allowing
 * 	a large number of small files to be stored without using
 * 	excessive system resources.  The following options may
 * 	be specified:
 *
 * 		c	Force creation of new archive
 * 		d	Delete file from archive.
 * 		i	Insert, same as update
 * 		p	Print files on standard output
 * 		r	Replace, same as update
 * 		l	List archive contents (directory)
 * 		u	Update, same as replace
 * 		x	Extract named files
 * 		v	Verbose
 * 		z	Write verbose log to indicated file
 *
 * 	The file name arguments may contain '*' and '?' wildcards, where
 * 	'*' matches any string of characters, and '?' matches one character.
 * 	('%' may be used as a synonym for '?'.)  There is a slight, but
 * 	suble difference in the way wild cards are processed for the
 * 	various commands:
 *
 * 		directory, delete, and extract
 *
 * 	Match ('*' and '?') against the files in the
 * 	archive, performing the operation on all files that
 * 	match.  Except for delete, "no argument" matches
 * 	all files in the archive.
 *
 * 		insert, replace, and update
 *
 * 	Expand the wild-card arguments against the files
 * 	stored on the operating system -- eliminating all
 * 	wild cards.  Then, match against the archived
 * 	files.  Those that match are replaced by the
 * 	file on the operating system.  After replacement,
 * 	any additional files are appended to the archive.
 * 	Files in the archive that are not in the directory
 * 	are unchanged.
 *
 * 	Currently, insert, replace, and update work the same.
 * 	If it seems reasonable, the program may be extended
 * 	as follows:
 *
 * 		insert	Add files new only
 *
 * 	Adds new files (not present in the archive)
 * 	but does not modify files currently in
 * 	the archive.  It would be an error to try
 * 	modifying a currently archived file.
 *
 * 		replace	Modify existing files only
 *
 * 	Modify files present in the archive, but do
 * 	not add new files to the archive.
 *
 * 		update	Modify existing, add new
 *
 * 	This is simple to do, but would seem to be a rich
 * 	source of user error.
 * 
 * archive file format
 * 
 * 	Archive files are standard text files.  Each archive element is
 * 	preceeded by a line of the format:
 * 
 * 		-h-	file.name	date	true_name
 *
 * 	Note that there is no line or byte count.  To prevent problems,
 * 	a '-' at the beginning of a record within a user file or embedded
 * 	archive will be "quoted" by doubling it.  The date and true filename
 * 	fields are ignored.  On Dec operating systems, file.name is
 * 	forced to lowercase.
 * 
 * diagnostics
 * 
 * 	Diagnostic messages should be self-explanatory
 * 
 * author
 * 
 * 	Martin Minow
 *
 *	Extensively reworked by Fred Fish, 1-Dec-85
 *	  - Reformatted using the "indent" program.
 *	  - Added support for macro based debugging package.
 *	  - Delinted and questionable C usages removed.
 *	  - Ported to Commodore AMIGA using Lattice C.
 * 
 */

#include <stdio.h>

#ifndef EOS
#  define EOS '\000'
#endif

#ifndef FALSE
#  define FALSE (0)
#endif

#ifndef TRUE
#  define TRUE (1)
#endif

/*
 * The following changes are needed to compile and link portar.c under
 * Berkeley UNIX 4.2bsd.  Add the following lines at the beginning of
 * the program:
 *
 * Then compile and link as follows:
 *
 * cc -O -o portar -DBSD portar.c
 *
 * Now you should be able to archive file on the vax, upload them to the
 * amiga, and the de-archive them on the amiga.
 *
 * Marco Papa
 * USC - Computer Science Dept.
 *
 * UUCP:	...!{decvax,ucbvax,hplabs}!sdcsvax!sdcrdcf!uscvax!papa
 *		...!{allegra,trwrb,trwspp,ism780,cithep,oberon}!uscvax!papa
 * CSNET:	papa@usc-cse.csnet
 * ARPA:	papa%usc-cse@csnet-relay.arpa
 */

#ifdef BSD
#include <ctype.h>
#undef tolower
#define tolower(c)	(isupper(c)?((c)+('a'-'A')):(c))
#include <strings.h>
#define strrchr=rindex
#endif
/* Hack for PCC-20 */
#ifdef TOPS20
#define void int
#include <ctype.h>
#define delete unlink
#undef tolower
#define tolower(c)	(isupper(c)?((c)+('a'-'A')):(c))

/* Need to fake these because pcc doesn't provide them */
char *strchr(s,c)
char *s,c;
{
	register char *t;
	t = s;
	while (*t)
	    if (*(t++) == c)
		return t - 1;
	return NULL;
}
char *strrchr(s,c)
char *s, c;
{
	register char *t;
	t = s + strlen(s) - 1;  /* point at end of string */
	while (t >= s)
	    if (*(t--) == 'c')
		return t+1;
	return NULL;		/* failure */
}

#endif

/*
 *	When calling Error(), the following flag bits define
 *	optional processing.
 */

#define WARN 	(000001)		/* This is a warning message */
#define ERR  	(000002)		/* This is an error */
#define FATAL	(000004)		/* Fatal, die after message */
#define SYS	(000010)		/* System error number available */

/*
 * The two routines fwild() and fnext() are faked on unix.
 * Also faked on AMIGA for now, may do equivalent later,
 * Ditto with PCC-20 (MPK)
 */

#if unix || AMIGA || TOPS20
#  define FAKEFWILD
#endif

#ifdef FAKEFWILD
  static FILE *fwild ();
  static FILE *fnext ();
  static void fgetname ();
#else
  extern  FILE *fwild ();		/* Wild card file lookup	 */
  extern  FILE *fnext ();		/* Open next wild card file	 */
  extern void fgetname ();
#endif

#define	TEMPNAME "ar.tmp"

/*
 * arfile chains together strings of text.
 */

typedef struct arfile {
    struct arfile *l_next;	/* -> next list element		 */
    int l_flag;			/* mark if found in archive	 */
    char *l_arname;		/* archive name argument	 */
    char *l_filename;		/* directory file name		 */
} ARFILE;

/*
 * Global storage
 */

FILE *arfd = NULL;		/* Archive			 */
FILE *newfd = NULL;		/* New archive			 */
FILE *logfd;			/* Log output			 */
char *logname;			/* Name of log file 		 */
int newarchive = FALSE;		/* True if create from scratch	 */
int logging = FALSE;		/* True if log file enabled	 */
char text[513];			/* Working text			 */
char arname[81];		/* Current archive member name	 */
char filename[81];		/* Working file name		 */
char arfilename[81];		/* Archive file name		 */
char *timetext;			/* Time of day text		 */
int verbose = FALSE;		/* TRUE for verbosity		 */
int delflag = 0;		/* Delete files			 */
int directory = 0;		/* Table of contents if lit	 */
int update = 0;			/* Update files if lit		 */
int extract = 0;		/* Get files from archive	 */
int print = 0;			/* Write files to stdout	 */
int errorflag = 0;		/* Set on fatal error		 */
ARFILE *list = NULL;		/* String list header		 */

#ifdef unix
#  define delete unlink
#endif

/*
 *	The following allow use on systems that don't have my macro based
 *	debugging package.  The default, for now, is to assume it is not
 *	available.   Fred Fish, 1-Dec-85
 */
 
#ifdef DBUG
#  include <local/dbug.h>
#else	/* !DBUG */
#  define DBUG_ENTER(a)
#  define DBUG_RETURN(a) return(a)
#  define DBUG_VOID_RETURN return
#  define DBUG_2(a,b)
#  define DBUG_3(a,b,c)
#  define DBUG_4(a,b,c,d)
#  define DBUG_5(a,b,c,d,e)
#  define DBUG_PUSH(a)
#endif	/* DBUG */

/*
 *	Declare internal functions that are used before definition seen.
 */
 
static char *GetTime ();	/* Get current time as printable ascii */
static void Error ();		/* Process an error or warning */
static void doupdate ();
static void dodirectory ();
static void dodelete ();
static void doextract ();
static int replace ();
static int expandargs ();
static int findfiles ();
static int savestring ();
static void dumplist ();
static void usage ();
static void notfound ();
static int gethdr ();
static int findarg ();
static int compare ();
static int addfile ();
static void argetname ();
static void filemove ();
static void arcopy ();
static void arimport ();
static void arexport ();
static int match ();
static int breakout ();
static int match1 ();

/*
 *	Library functions.
 */

extern char *malloc ();
extern char *strcpy ();
extern char *strchr ();
extern int delete ();
extern void free ();
extern void exit ();

#ifndef fflush			/* Sometimes is a macro */
  extern void fflush ();
#endif

/*
 *	Main entry point.  Note declaration is 'int' and a meaningful
 *	value is actually returned.  Generally this becomes the exit
 *	status for the parent process.
 */
 
int main (argc, argv)
int argc;			/* Arg count			 */
char *argv[];			/* Arg vector			 */
{
    register int i;		/* Random counter		 */
    register char *argp;	/* Arg pointer			 */

    DBUG_ENTER ("main");
    logfd = stderr;
    logname = "stderr";
    timetext = GetTime ();
    for (i = 1; i < argc; i++) {
	if ((argp = argv[i]) == NULL) {
	    continue;			/* From log file writer		 */
	}
	if (*argp == '-') {
	    /* 
	     * Process options
	     */
	    argv[i] = NULL;		/* Erase it from file stuff	 */
	    while (*++argp != EOS) {
		switch (tolower (*argp)) {
		    case '#':		/* Can not bundle with other args! */
		        DBUG_PUSH (argp);
			argp = " ";	/* Trickery to terminate arg */
			break;
		    case 'c': 
			newarchive = TRUE;
			break;
		    case 'd': 	/* Delete from archive	 */
			delflag = 1;
			break;
		    case 'p': 	/* Print on stdout	 */
			print = 1;
			break;
		    case 'l': 	/* List directory	 */
			directory = 1;
			break;
		    case 'h':	/* Explicit usage requested */
		        usage ();
			exit (0);
			break;
		    case 'i': 	/* Insert		 */
		    case 'r': 	/* Replace		 */
		    case 'u': 	/* Update modified	 */
			update = 1;
			break;
		    case 'v': 	/* Verbose		 */
			verbose = 1;
			break;
		    case 'x': 	/* Extract		 */
			extract = 1;
			break;
		    case 'z': 	/* Log file		 */
			logname = argv[i + 1];
			if ((logfd = fopen (logname, "w")) == NULL) {
			    Error (ERR|SYS|FATAL, "can't create logfile '%s'",
			    	logname);
			}
			if (verbose) {
			    fprintf (stderr, "writing log to %s\n", logname);
			}
			logging = TRUE;
			argv[i + 1] = NULL;
			break;
		    default: 
			Error (WARN|FATAL,
				"illegal option '%c', use -h for help",
				*argp);
		}
	    }
	    argv[i] = NULL;	/* Erase argument	 */
	} else if (arfd == NULL && newfd == NULL) {	/* Not option */
	    /* 
	     * First file is the archive name
	     */
	    if (newarchive || (arfd = fopen (argp, "r")) == NULL) {
		DBUG_3 ("new", "opening '%s' as new archive", argp);
		if ((newfd = fopen (argp, "w")) == NULL) {
		    Error (ERR|FATAL|SYS, "can't create archive '%s'", argp);
		} else {
		    newarchive = TRUE;
		    if (verbose) {
			fprintf (logfd, "Creating new archive '%s'\n", argp);
		    }
		}
	    }
	    argv[i] = NULL;	/* Erase argument	 */
	    (void) strcpy (arfilename, argp);
	}
    }
    if (errorflag) {
	Error (ERR|FATAL, "previous error prevents continuation");
    }
    if (!newarchive && arfd == NULL) {
	Error (ERR|FATAL, "no archive file specified, use -h for help");
    }
    /* 
     * Got all arguments.
     */
    if ((i = delflag + directory + extract + print + update) > 1) {
	Error (ERR|FATAL, "illogical option combination");
    } else if (i == 0) {
	if (verbose) {
	    fprintf (logfd, "Update selected by default\n");
	}
	update = 1;
    }
    if (!newarchive && (delflag || update)) {
	if ((newfd = fopen (TEMPNAME, "w")) == NULL) {
	    Error (ERR|FATAL|SYS, "can't create work file '%s'", TEMPNAME);
	}
    }
    /* 
     * Debugging verbosity.
     */
    if (verbose) {
	fprintf (logfd, "You have selected:");
	if (directory) fprintf (logfd, " directory");
	if (delflag) fprintf (logfd, " delete");
	if (extract) fprintf (logfd, " extract");
	if (print) fprintf (logfd, " print");
	if (update) fprintf (logfd, " update");
	if (verbose) fprintf (logfd, " and verbosity");
	fprintf (logfd, ".\nArchive file is \"%s\".\n", arfilename);
    }
    if (expandargs (argc, argv, update)) {
	Error (WARN, "errors found in arg expansion");
    }

    if (newarchive && !update) {
	fprintf (logfd, "Dummy archive created\n");
	(void) fclose (newfd);
    } else if (directory) {
	dodirectory ();
    } else if (delflag) {
	dodelete ();
    } else if (extract || print) {
	doextract (print);
    } else if (update) {
	doupdate ();
    } else {
	Error (FATAL|WARN, "no command was provided, use -h for help");
    }
    DBUG_RETURN (0);
}

/*
 * Write a table of contents
 */

static void dodirectory ()
{
    DBUG_ENTER ("dodirectory");

    text[0] = EOS;
    while (gethdr (arfd)) {
	if (findarg (arname, (char *) NULL)) {
	    printf (text);
	}
	arcopy (arfd, (FILE *) NULL);	/* Skip file		 */
    }
    DBUG_VOID_RETURN;
}


/*
 * Delete named files -- gotta have a name list
 */

static void dodelete ()
{
    register int ecount;

    DBUG_ENTER ("dodelete");
    if (list == NULL) {
	Error (ERR|FATAL, "delete by name only");
    }
    ecount = replace (arfd, newfd, FALSE, 0);
    notfound ();
    (void) fclose (arfd);
    (void) fclose (newfd);
    if (ecount == 0) {
	filemove (TEMPNAME, arfilename);
    } else {
	Error (WARN, "errors prevent deletion of archive");
	if (logging) {
	    fprintf (logfd, "Errors prevent deletion of archive\n");
	}
	if (delete (TEMPNAME) == -1) {
	    Error (WARN|SYS, "can't delete '%s'", TEMPNAME);
	}
    }
    DBUG_VOID_RETURN;
}


/*
 * Extract or print named files
 */

static void doextract (printflag)
int printflag;			/* TRUE to print, FALSE to extract */
{
    register FILE *outfd;

    DBUG_ENTER ("doextract");
    outfd = (printflag) ? stdout : NULL;
    text[0] = EOS;
    while (gethdr (arfd)) {
	if (!findarg (arname, (char *) NULL)) {
	    if (verbose) {
		fprintf (logfd, "Skipping \"%s\"\n", arname);
	    }
	    arcopy (arfd, (FILE *) NULL);		/* Skip		 */
	} else {
	    if (outfd != stdout) {
		if ((outfd = fopen (arname, "w")) == NULL) {
		    Error (ERR|SYS, "can't create '%s'", arname);
		    if (logging) {
			fprintf (logfd, "Can't create \"%s\"\n", arname);
		    }
		    arcopy (arfd, (FILE *) NULL);
		    continue;
		}
	    }
	    if (verbose) {
		fprintf (logfd, "Creating \"%s\"\n", arname);
	    }
	    arexport (arfd, outfd);
	    (void) fclose (outfd);
	    outfd = NULL;
	}
    }
    DBUG_VOID_RETURN;
}

/*
 * Update existing files, add argv[1]..argv[argc-1] at end
 */

static void doupdate ()
{
    register int ecount;
    register ARFILE *lp;

    DBUG_ENTER ("doupdate");
    ecount = 0;
    if (!newarchive) {
	DBUG_2 ("old", "update using existing archive");
	ecount = replace (arfd, newfd, TRUE, 0);
    }
    for (lp = list; lp != NULL; lp = lp -> l_next) {
	if (!lp -> l_flag) {
	    ecount += addfile (lp -> l_arname, lp -> l_filename, newfd,
	    			ecount, "Added");
	    lp -> l_flag = TRUE;
	}
    }
    if (newarchive) {
	DBUG_2 ("new", "new archive, no need to copy temp archive");
	(void) fclose (newfd);
	if (ecount) {
	    Error (WARN, "completed with %d errors", ecount);
	    if (logging) {
		fprintf (stderr, "completed with %d errors\n", ecount);
	    }
	}
    } else {
	DBUG_2 ("new", "copy temp archive to new archive");
	(void) fclose (arfd);
	(void) fclose (newfd);
	if (ecount == 0) {
	    filemove (TEMPNAME, arfilename);
	} else {
	    Error (WARN|SYS, "move of '%s' to '%s' supressed because of errors",
		    TEMPNAME, arfilename);
	    if (logging) {
		fprintf (logfd,
			"Move of %s to %s supressed because of errors\n",
			TEMPNAME, arfilename);
	    }
	}
    }
    DBUG_VOID_RETURN;
}

/*
 * Replace or delete files from the archive.  The updated archive
 * is written to outfd.
 */

static int replace (infd, outfd, updateflag, ecount)
FILE *infd;		/* Reading files from here */
FILE *outfd;		/* Writing files here */
int updateflag;		/* TRUE to update, FALSE to remove */
int ecount;
{
    DBUG_ENTER ("replace");
    text[0] = EOS;		/* Signal gethdr initialization	 */
    while (gethdr (infd)) {
        /* 
         * We have a file, is it selected?
         */
	if (findarg (arname, filename)) {
	    if (updateflag) {
		ecount += addfile (arname, filename, outfd, ecount,
				   "Replaced");
	    }
	    arcopy (infd, (FILE *) NULL);
	} else {
	    /* 
	     * Not selected for update, copy to the new archive
	     */
	    (void) fputs (text, outfd);
	    arcopy (infd, outfd);
	}
    }
    DBUG_RETURN (ecount);
}

/*
 * Process the argv[] vector, building the argument list.
 * Note: argv[1] is the first argument -- argv[0] is untouched and
 * NULL entries in argv[] are ignored.
 *
 * If updateflag is TRUE, arguments are expanded against the file
 * directory (using fwild/fnext).  If FALSE, they are used as is.
 *
 * Return TRUE if errors occurred.
 */

static int expandargs (argc, argv, updateflag)
int argc;		/* Number of arguments */
char *argv[];		/* Arg vector */
int updateflag;		/* TRUE to trigger file search */
{
    register int in;
    register int eflag;

    DBUG_ENTER ("expandargs");
    eflag = 0;
    for (in = 1; in < argc; in++) {
	if (argv[in] != NULL) {
	    if (updateflag) {
		eflag += findfiles (argv[in]);
	    } else {
		eflag += savestring (argv[in], (char *) NULL);
	    }
	}
    }
    DBUG_RETURN (eflag != 0);
}

/*
 * Archive element names, do fwild lookup to expand wildcards where possible.
 */

static int findfiles (fname)
char *fname;
{
    register int i;
    register FILE *fd;

    DBUG_ENTER ("findfiles");
    if ((fd = fwild (fname, "r")) == NULL) {
	Error (WARN|SYS, "can't open directory or file '%s'", fname);
	if (logging) {
	    fprintf (stderr, "Can't open directory or wildcard file \"%s\"\n",
		    fname);
	}
	DBUG_RETURN (1);
    }
    /* 
     * Locate each file, then save archive and file names
     */
    for (i = 0; fnext (fd) != NULL; i++) {
	argetname (fd, arname, filename);
	savestring (arname, filename);
    }
    if (i == 0) {
	Error (WARN, "no match for '%s'", fname);
	if (logging) {
	    fprintf (stderr, "Warning, no match for \"%s\"\n", fname);
	}
	DBUG_RETURN (1);
    } else if (verbose) {
	fprintf (logfd, "%d file%s in your directory match%s \"%s\"\n",
		i,
		(i > 1) ? "s" : "",
		(i == 1) ? "es" : "",
		fname);
	DBUG_RETURN (0);
    }
    DBUG_RETURN (0);
}

/*
 * Insert text into the list in sorted order (on datum).
 * Warn (and fail on) duplicates.
 */

static int savestring (datum, file)
char *datum;			/* Archive element name */
char *file;			/* May be NULL if not necessary */
{
    register ARFILE *next;
    register ARFILE **prev;
    register ARFILE *new;
    char *ardatum;
    char *arfile;
    int comp;

    DBUG_ENTER ("savestring");
    arfile = NULL;
    if (file != NULL) {
	arfile = (char *) malloc ((unsigned) (strlen (file) + 1));
	if (arfile == NULL) {
	    Error (ERR|FATAL|SYS, "can't allocate any more memory");
	}
	(void) strcpy (arfile, file);
    }
    if ((ardatum = (char *) malloc ((unsigned) (strlen (datum) + 1))) == NULL
	    || (new = (ARFILE *) malloc (sizeof (ARFILE))) == NULL) {
	    Error (ERR|FATAL|SYS, "can't allocate any more memory");
    }
    (void) strcpy (ardatum, datum);
    new -> l_flag = FALSE;
    new -> l_arname = ardatum;
    new -> l_filename = arfile;
    prev = &list;
    next = list;
    while (next != NULL && (comp = compare (datum, next -> l_arname)) > 0) {
	if (comp == 0) {
	    Error (WARN, "duplicate argument '%s'", datum);
	    if (arfile) {
		free (arfile);
	    }
	    free (ardatum);
	    free ((char *) new);
	    DBUG_RETURN (TRUE);
	}
	prev = &next -> l_next;
	next = *prev;
    }
    *prev = new;
    new -> l_next = next;
    DBUG_RETURN (FALSE);
}


#ifdef DEADCODE		/* Not used, leftover from what?  (fnf) */

/*
 * Dump archive name list -- used for debugging only
 */

static void dumplist ()
{
    register ARFILE *lp;

    DBUG_ENTER ("dumplist");
    if ((lp = list) == NULL) {
	Error (WARN, "list is empty");
    } else {
	while (lp != NULL) {
	    fprintf (stderr, "%s, \"%s\"",
	    		(lp -> l_flag) ? "    found" : "not found",
			lp -> l_arname);
	    if (lp -> l_filename == NULL) {
		fprintf (stderr, "\n");
	    } else {
		fprintf (stderr, "%s\n", lp -> l_filename);
	    }
	    lp = lp -> l_next;
	}
    }
    DBUG_VOID_RETURN;
}

#endif	/* DEADCODE */

static char *documentation[] = {
    "Usage: portar -cdhilpruvx archive files",
    "",
    "  c  Create a new archive",
    "  d  Delete named files from archive",
    "  h  Print this help info",
    "  i  Insert named files into archive",
    "  l  List archive directory",
    "  p  Print named files on standard output",
    "  r  Replace named files",
    "  u  Update -- replace named files",
    "  v  Verbose -- give running commentary",
    "  x  Extract -- copy named files to current directory",
    "  z  Put logfile in file named in next argument",
    "",
    "i, r, and u, are identical",
    "",
    NULL
};

static void usage ()
{
    register char **dp;
    
    DBUG_ENTER ("usage");
    for (dp = documentation; *dp != NULL; dp++) {
	printf ("%s\n", *dp);
    }
    DBUG_VOID_RETURN;
}

static void Error (flags, fmt, arg1, arg2, arg3)
int flags;
char *fmt;
char *arg1;
char *arg2;
char *arg3;
{
    fprintf (stderr, "portar: ");
    if (flags & WARN) {
	fprintf (stderr, "warning -- ");
    } else if (flags & ERR) {
	fprintf (stderr, "error -- ");
    }
    fprintf (stderr, fmt, arg1, arg2, arg3);
    if (flags & SYS) {
	perror ("");
    } else {
	fprintf (stderr, "\n");
    }
    (void) fflush (stderr);
    if (flags & FATAL) {
	exit (1);
    }
}

/*
 * Called from dodelete() to warn the user about files that were
 * to be deleted, but which were not in the archive.
 */

static void notfound ()
{
    register ARFILE *lp;

    DBUG_ENTER ("notfound");
    for (lp = list; lp != NULL; lp = lp -> l_next) {
	if (!lp -> l_flag) {
	    Error (WARN|SYS, "can't delete '%s'", lp -> l_arname);
	    if (logging) {
		fprintf (stderr, "Can't delete \"%s\" -- not found\n",
			lp -> l_arname);
	    }
	}
    }
    DBUG_VOID_RETURN;
}

/*
 * If text is null, read a record, returning TRUE if text contains a header.
 * Parse the header into arname.
 */

static int gethdr (fd)
FILE *fd;
{
    register char *tp;
    register char *np;

    DBUG_ENTER ("gethdr");
    if (text[0] == EOS) {
	for (;;) {	/* Search for first header MPK 16-Jan-86 */
	   if (fgets (text, (int) sizeof (text), fd) == NULL)
	      DBUG_RETURN (FALSE);
	   if ((text[0] == '-') && (text[1] == 'h') && (text[2] == '-'))
	      break; /* found a header */
        } 
    }
    else /* check current line for header */
	if (text[0] != '-' || text[1] != 'h' || text[2] != '-') {
	   DBUG_RETURN (FALSE);
        }

    for (tp = &text[3]; *tp && *tp <= ' '; tp++);
    for (np = &arname[0]; *tp > ' '; *np++ = *tp++);
    *np = EOS;
    DBUG_RETURN (TRUE);
}

/*
 * If name is in the list, mark it as "found" and return TRUE.
 * If true, and fname is not NULL, fname will have the file argument.
 */

static int findarg (name, fname)
char *name;
char *fname;			/* Gets full file name		 */
{
    register ARFILE *lp;

    DBUG_ENTER ("findarg");
    if ((lp = list) == NULL) {
	if (fname != NULL) {
	    fname[0] = '\000';
	}
	DBUG_RETURN (TRUE);
    }
    while (lp != NULL) {
	if (match (name, lp -> l_arname)) {
	    lp -> l_flag = TRUE;
	    if (fname != NULL) {
		if (lp -> l_filename == NULL) {
		    fname[0] = EOS;
		} else {
		    (void) strcpy (fname, lp -> l_filename);
		}
	    }
	    DBUG_RETURN (TRUE);
	}
	lp = lp -> l_next;
    }
    DBUG_RETURN (FALSE);
}

/*
 * Compare strings (note: case insensitive)
 */

static int compare (string1, string2)
register char *string1;
register char *string2;
{
    DBUG_ENTER ("compare");
    while (tolower (*string1) == tolower (*string2)) {
	if (*string1 == NULL) {
	    DBUG_RETURN (0);
	}
	string1++;
	string2++;
    }
    DBUG_RETURN ((tolower (*string1) > tolower (*string2)) ? 1 : -1);
}

/*
 * Add file "fname" (archive element "name") to the archive
 */

static int addfile (name, fname, outfd, ecount, why)
char *name;			/* Archive element name */
char *fname;			/* Archive file name */
FILE *outfd;			/* Output file, already open */
int ecount;			/* Current error count (updated */
char *why;			/* Why are we here -- for verbosity */
{
    register FILE *infd;

    DBUG_ENTER ("addfile");
    if ((infd = fopen (fname, "r")) == NULL) {
	Error (WARN|SYS, "'%s' archive member '%s' not found", why,
		(fname == NULL) ? "{Null}" : fname);
	if (logging) {
	    fprintf (stderr, "%s archive member \"%s\" not found\n", why,
		    (fname == NULL) ? "{Null}" : fname);
	}
	ecount++;
    } else {
#ifdef DECUS
	fgetname (infd, filename);
#else
	(void) strcpy (filename, fname);
#endif
	if (verbose) {
	    fprintf (logfd, "%s archive member \"%s\" (%s)\n", why, name,
	    		filename);
	}
	fprintf (outfd, "-h- %s\t%s\t%s\n", name, timetext, filename);
	arimport (infd, outfd);
	(void) fclose (infd);
    }
    DBUG_RETURN (ecount);
}

/*
 * Get file name, stripping off device:[directory] and ;version.
 * The archive name ("FILE.EXT" is written to outname, while the
 * full file name is written to outfilename.  On a dec operating system,
 * outname is forced to lowercase.
 */

static void argetname (fd, outname, outfilename)
FILE *fd;
char *outname;			/* Archive name */
char *outfilename;		/* Full file name */
{
    register char *tp;
#ifndef unix
    char bracket;
#endif
    extern char *strrchr ();

    DBUG_ENTER ("argetname");
    fgetname (fd, outfilename);
    (void) strcpy (outname, outfilename);
#ifdef	unix
    /* 
     * outname is after all directory information
     */
    if ((tp = strrchr (outname, '/')) != NULL) {
	(void) strcpy (outname, tp + 1);
    }
#else
    if ((tp = strrchr (outname, ';')) != NULL) {
	*tp = EOS;
    }
    while ((tp = strchr (outname, ':')) != NULL) {
	(void) strcpy (outname, tp + 1);
    }
    switch (outname[0]) {
	case '[': 
	    bracket = ']';
	    break;
	case '<': 
	    bracket = '>';
	    break;
	case '(': 
	    bracket = ')';
	    break;
	default: 
	    bracket = EOS;
	    break;
    }
    if (bracket != EOS) {
	if ((tp = strchr (outname, bracket)) == NULL) {
	    Error (WARN, "? Illegal file name '%s'", outfilename);
	} else {
	    (void) strcpy (outname, tp + 1);
	}
    }
    for (tp = outname; *tp != EOS; tp++) {
	*tp = tolower (*tp);
    }
#endif
    DBUG_VOID_RETURN;
}


/*
 * "Rename" inname to outname the hard way.
 */

static void filemove (inname, outname)
char *inname;
char *outname;
{
    register FILE *infd;
    register FILE *outfd;
    long int nrecords;

    DBUG_ENTER ("filemove");
    if (verbose) {
	fprintf (logfd, "Copying %s to %s\n", inname, outname);
    }
    if ((infd = fopen (inname, "r")) == NULL) {
	Error (ERR|FATAL|SYS, "can't open '%s' for input", inname);
    }
    if ((outfd = fopen (outname, "w")) == NULL) {
	Error (ERR|FATAL|SYS, "can't open '%s' for write", outname);
    }
    for (nrecords = 0; fgets (text, (int) sizeof (text), infd) != NULL; nrecords++) {
	(void) fputs (text, outfd);
    }
#ifdef DECUS
    fgetname (infd, text);
#else
    (void) strcpy (text, inname);
#endif
    (void) fclose (infd);
    (void) fclose (outfd);
    if (delete (text) == -1) {
	Error (WARN|SYS, "can't delete '%s'", text);
    }
    if (verbose) {
	fprintf (logfd, "Archive %s contains %ld records.\n", outname,
			nrecords);
    }
    DBUG_VOID_RETURN;
}

/*
 * Copy (or skip if outfd == NULL) to next header
 */

static void arcopy (infd, outfd)
register FILE *infd;
register FILE *outfd;
{
    DBUG_ENTER ("arcopy");
    while (fgets (text, (int) sizeof (text), infd) != NULL) {
	if (text[0] == '-' && text[1] != '-') {
	    DBUG_VOID_RETURN;
	}
	if (outfd != NULL) {
	    (void) fputs (text, outfd);
	}
    }
    text[0] = EOS;		/* EOF signal		 */
    DBUG_VOID_RETURN;
}

/*
 * Import text, writing it in the secret ar format.
 */

static void arimport (infd, outfd)
register FILE *infd;
register FILE *outfd;
{
    DBUG_ENTER ("arimport");
    while (fgets (text, (int) sizeof (text), infd) != NULL) {
	if (text[0] == '-') {
	    (void) putc ('-', outfd);			/* Quote	 */
	}
	(void) fputs (text, outfd);
    }
    DBUG_VOID_RETURN;
}

/*
 * Read secret archive format, writing archived data to outfd.
 * Clean out extraneous <cr>,<lf>'s
 */

static void arexport (infd, outfd)
register FILE *infd;
register FILE *outfd;
{
    register char  *tp;

    DBUG_ENTER ("arexport");
    while (fgets (text, (int) sizeof (text), infd) != NULL) {
	tp = &text[strlen (text)];
	if (tp > &text[1] && *--tp == '\n' && *--tp == '\r') {
	    *tp++ = '\n';
	    *tp = EOS;
	}
	if (text[0] == '-') {
	    if (text[1] != '-') {
		DBUG_VOID_RETURN;
	    }
	    (void) fputs (text + 1, outfd);
	} else {
	    (void) fputs (text, outfd);
	}
    }
    text[0] = EOS;
    DBUG_VOID_RETURN;
}

/*
 * Pattern match between
 *	name	string argument (FILE.EXT format)
 *	pattern	which may contain wildcards.
 *
 * Note: '*' matches all but '.' separator between file and ext.
 *	'?' matches one character, but not '.'
 *
 */

typedef struct filename {
    char namepart[10];
    char typepart[4];
} FILENAME;

static int match (name, pattern)
register char *name;
register char *pattern;
{
    FILENAME namebuff;
    FILENAME patternbuff;
    int result;

    DBUG_ENTER ("match");
    if (breakout (name, &namebuff) || breakout (pattern, &patternbuff)) {
	result = FALSE;
    } else {
	result = (match1 (namebuff.namepart, patternbuff.namepart)
	    && match1 (namebuff.typepart, patternbuff.typepart));
    }
    DBUG_RETURN (result);
}

/*
 * Parse arg ("foo.bar") into "foo" and "bar"
 * Return TRUE if trouble.
 */

static int breakout (arg, buff)
char *arg;
FILENAME * buff;
{
    register char *ap;
    register char *bp;
    register int dotseen;
    int size;

    DBUG_ENTER ("breakout");
    dotseen = FALSE;
    ap = arg;
    bp = buff -> namepart;
    buff -> typepart[0] = EOS;
    size = (sizeof buff -> namepart) - 1;
    while (*ap != EOS) {
	if (*ap == '.') {
	    if (dotseen++) {			/* 2 dots	 */
		DBUG_RETURN (TRUE);
	    } else {
		ap++;
		*bp = EOS;
		bp = buff -> typepart;
		size = (sizeof buff -> typepart) - 1;
		continue;
	    }
	}
	if (size-- <= 0) {			/* 2 big	 */
	    DBUG_RETURN (TRUE);
	}
	*bp++ = *ap++;
    }
    *bp = EOS;
    DBUG_RETURN (FALSE);
}

/*
 * Recursive routine to match "name" against "pattern".
 * Returns TRUE if successful.
 */

static int match1 (name, pattern)
register char *name;		/* What to look for */
register char *pattern;		/* May have wildcard */
{
    register char pattbyte;
    char namebyte;

    DBUG_ENTER ("match1");
    for (;;) {
        /* 
         * First check for pattern ending in '*' -- this has to succeed
         */
	if ((pattbyte = *pattern++) == '*' && *pattern == EOS) {
	    DBUG_RETURN (TRUE);
	}
        /* 
         * If not, then if both strings finish equally, it succeeds.
         */
	if ((namebyte = *name) == EOS && pattbyte == EOS) {
	    DBUG_RETURN (TRUE);
	}
        /* 
         * Not at end of the name string.
         */
	switch (pattbyte) {
	    case EOS: 		/* End of pattern -> failure	 */
		DBUG_RETURN (FALSE);
	    case '*': 		/* Wild card means "advance"	 */
		do {
		    if (match1 (name, pattern)) {
			DBUG_RETURN (TRUE);
		    }
		} while (*name++ != EOS);
		DBUG_RETURN (FALSE);	/* Did our best			 */
	    default: 
		if (tolower (namebyte) != tolower (pattbyte)) {
		    DBUG_RETURN (FALSE);
		}
	    case '?': 		/* One byte joker		 */
	    case '%': 		/* RT11 one byte joker		 */
		name++;		/* Matched this one		 */
	}
    }
}

#ifdef	FAKEFWILD

/* Set if a file is open	 */
/*  0	nothing open		 */
/* +1	open, fnext not called	 */
/* +2	fnext called once	 */
static int  fake_flag = 0;

static char fake_name[81];	/* Name of file */

/*
 * "setup" to open a wildcard file name
 */

static FILE *fwild (fname, mode)
char *fname;
char *mode;
{
    register FILE *fd;

    DBUG_ENTER ("fwild");
    if (fake_flag != 0) {
	Error (WARN, "fwild/fnext out of sync");
    }
    fake_flag = 0;
    if ((fd = fopen (fname, mode)) != NULL) {
	fake_flag++;
	(void) strcpy (fake_name, fname);
    }
    DBUG_RETURN (fd);
}

static FILE *fnext (fd)
FILE *fd;
{
    DBUG_ENTER ("fnext");
    switch (fake_flag) {
	case 1: 
	    fake_flag++;	/* First call after fwild	 */
	    DBUG_RETURN (fd);	/* File is "open"		 */
	case 2: 
	    fake_flag = 0;	/* Second call of fnext		 */
	    (void) fclose (fd);	/* Close existing file		 */
	    fake_name[0] = EOS;	/* Zap file name		 */
	    DBUG_RETURN ((FILE *)NULL);	/* No more files left		 */
	default: 
	    Error (WARN, "fnext called without calling fwild");
	    DBUG_RETURN ((FILE *) NULL);
    }
}

/*
 *	Note, this only works for files opened via fwild/fnext. (fnf)
 */
 
static void fgetname (fd, name)
FILE *fd;
char *name;
{
    if (fd != NULL) {
	(void) strcpy (name, fake_name);
    }
}

#endif	/* FAKEFWILD */

#ifndef unix
perror (sp)
char *sp;
{
    if (sp != NULL) {
	fprintf (stderr, "%s", sp);
    }
    fprintf (stderr, ": <unknown error>");
}
#endif

#ifdef AMIGA

int delete (name)
char *name;
{
    int status;
    extern int DeleteFile ();
    
    DBUG_ENTER ("delete");
    if (DeleteFile (name)) {		/* Returns 0 or 1 */
	status = 0;			/* Success */
    } else {
	status = -1;			/* Failure */
    }
    DBUG_RETURN (status);
}

#endif	/* AMIGA */

/* 
 * Setup the time of day, erasing trailing '\n'
 */

#ifdef AMIGA
#  undef TRUE	/* TRUE, FALSE, and NULL are all redefined in types.h, */
#  undef FALSE	/* which is ultimately pulled in.  They have the same */
#  undef NULL	/* numeric values but are not enclosed in parens. */
#  include <libraries/dosextens.h>
#  include <libraries/dos.h>
#endif
#ifdef TOPS20
#include <jsys.h>
#define left(n) ((n) >> 18)
#define right(n) ((n) & 0777777)

char *days[7] = { "Mon","Tue","Wed","Thu","Fri","Sat","Sun" };
char *months[12] = { "Jan","Feb","Mar","Apr","May","Jun",
			  "Jul","Aug","Sep","Oct","Nov","Dec" };
int align;	/* Encourage pcc-20 to align next instruction (?) */
#endif

static char *GetTime ()
{
#ifdef unix
    register char *cp;
    register char *now;
    long timval;
    extern long time ();
    extern char *ctime ();

    (void) time (&timval);
    now = ctime (&timval);
    cp = now + strlen (now);
    while (cp > now && *--cp <= ' ');
    cp[1] = EOS;
    return (now);
#else
#ifdef AMIGA
    auto struct DateStamp now;
    static char buffer[64];
    extern struct DateStamp *DateStamp ();

    if (DateStamp (&now) == NULL) {
	Error (WARN|SYS, "can't get current date");
	now.ds_Days = 0;
	now.ds_Minute = 0;
	now.ds_Tick = 0;
    }
    /* Kinda ugly for now, just dump structure as ascii. */
    sprintf (buffer, "%u:%u:%u", now.ds_Days, now.ds_Minute, now.ds_Tick);
    return (buffer);
#else
#ifdef TOPS20
/* MPK 16-Jan-86 */
	int ACin[5], ACout[5];
	int curtime, year, day, month, weekday, hour, minute, second;
	static char buffer[70]; /* nice and big */

	/* Get date and time */
	jsys (JSgtad,ACin,ACout);

	ACin[2] = ACout[1];
	ACin[4] = 0;			/* Apply DST as appropriate */
	jsys (JSodcnv,ACin,ACout);	/* Convert the time */
	
	/* Parse all the fields */
	year = left(ACout[2]);
	month = right(ACout[2]);	 /* Month (0-11) in right   */

	day = left(ACout[3]);
	weekday = right(ACout[3]);	/* 0 = monday */

	ACout[4] = right(ACout[4]);	/* Mask of left half */
	hour = (ACout[4] / 3600) % 3600;
	minute = (ACout[4] / 60) % 60;
	second = ACout[4] % 60;

	sprintf (buffer,"%3s %3s %2d %2d:%02d:%02d %4d",
		days[weekday], months[month],day,hour,minute,second,year);
	return (buffer);
#endif
#else
    return ("<time currently unavailable>");
#endif	/* AMIGA */
#endif	/* unix */
}


