
/* EMACS_MODES: c !fill tabstop=4 */

/*
 *	filehand -- Get a handle on file operations.
 *
 *
 *
 *	This file contains a number of routines which are useful in handling
 *	files.  The operationd performed are defined in the filehand.h file,
 *	as are the return codes.
 */

# include <stdio.h>
# include FHAND/filehand.h"				/* " /* Quiet cref. */

/* Debugging options. */

# ifdef TRACE
extern FILE		*trace;
# define TR(W,X,Y,Z) fprintf (trace, W, X, Y, Z)
# else
# define TR(W,X,Y,Z) /* W X Y Z */
# endif

# define READ "r"
# define APP "a"
# define BUFSIZE 512
# define SAME 0							/* Same as strcmp. */
# define BEFORE 1						/* Almost same as strcmp. */
# define AFTER (-1)						/* Ditto. */
# define ERROR (-1)
# define OFILE1 1
# define OFILE2 2
# define ALLOC1 4
# define ALLOC2 8
# define ALLOC3 16

# ifdef XALLOC
/* Use this for places where xalloc and xfree are used in the code which
 * calls filehand routines. */
USXALLOC();
# endif

/*
 *	ftrans -- Handle routine file transfer operations.
 */

ftrans (opcode, file1, file2, area, size)
int		opcode;							/* Choices are in filehand.h. */
char	*file1, *file2,					/* The files to handle. */
		*area;							/* Buffer area pointer. */
int		size;							/* Of buffer area. */
{
	FILE	*fp1, *fp2;
	int		retcode;

	TR("Ftrans: %d (%s) (%s)\n", opcode, file1, file2);
	TR("Ftrans: area=%d size=%d\n", area, size, EMPTY);

	if ((opcode == RENAME || opcode == CPY) &&
	  (fp2 = fopen (file2, READ)) != NULL) {
		if (fclose (fp2) == ERROR)
			return (RECURSE + DESTEXISTS);
		return (DESTEXISTS);
		}
	
	if ((opcode == MOVE || opcode == COPYOVER)
	  && (fp2 = fopen (file2, READ)) != NULL) {
		if (fclose (fp2) == ERROR)
			return (RECURSE + LIVEDEST);
		if (unlink (file2) == ERROR)
			return (LIVEDEST);
		}

	if ((fp1 = fopen (file1, READ)) == NULL)
		return (NOSOURCE);

	if ((fp2 = fopen (file2, APP)) == NULL) {
		if (fclose (fp1) == ERROR)
			return (RECURSE + NODEST);
		return (NODEST);
		}

	retcode = copyrest (fp1, fp2, area, size);
	if (fclose (fp1) == ERROR)
		retcode = LIVESRC;
	if (fclose (fp2) == ERROR)
		retcode = NOCLOSE;
	if (retcode != DONE)
		return (retcode);
	if ((opcode == MOVE || opcode == RENAME) && unlink (file1) == ERROR)
		return (LIVESRC);
	TR("Ftrans: normal return\n", EMPTY, EMPTY, EMPTY);
	return (DONE);
}

copyrest (fp1, fp2, place, size)		/* Copy the rest of a file. */
FILE	*fp1, *fp2;						/* Use these file pointers. */
char	*place;							/* A buffer to use. */
int		size;							/* Size of buffer. */
{
	char	*malloc (),					/* Memory allocater. */
			*space;						/* Area being used. */
	unsigned count;						/* Of bytes read. */
	int		retcode = DONE;

	TR("Copyrest: fp1=%d fp2=%d place=%d ", fp1, fp2, place);
	TR("size=%d\n", size, EMPTY, EMPTY);

	if (place == EMPTY) {
		if (size <= 0)
			size = BUFSIZE;
		if ((space = malloc ((unsigned) size)) == EMPTY) {
			TR("Copyrest: no space\n", EMPTY, EMPTY, EMPTY);
			return (NOSPACE);
			}
		}
	else {
		if (size <= 0)
			return (BADSIZE);
		space = place;
		}

	while ((count = fread (space, sizeof (char), (unsigned) size, fp1)) >
	  (unsigned) 0)
		if (fwrite (space, sizeof (char), count, fp2) != count) {
			retcode = COPYERROR;
			break;
			}

	if (place == EMPTY)
		free (space);
	TR("Copyrest: returns %d\n", retcode, EMPTY, EMPTY);
	return (retcode);
}

getrec (file, buffer, recsep, rectype, maxlen)
FILE	*file;							/* The open file to read from. */
char	buffer[],						/* The buffer to read into. */
		recsep;							/* The character to break on. */
int		rectype,						/* FIXED or VARYING length. */
		maxlen;							/* Maximum record length. */
{
	int		count;						/* Of characters read. */
	char	lc;

	TR("Getrec: file=%d recsep=(%c) maxlen=%d", file, recsep, maxlen);
	TR(" rectype=%d buffer=%d\n", rectype, buffer, EMPTY);

	if (rectype == FIXED) {
		TR("Getrec: FIXED record size\n", EMPTY, EMPTY, EMPTY);
		count = fread (buffer, sizeof (char), (unsigned) maxlen, file);
		if (count == 0)
			return (FILE1EOF);
		else if (count == maxlen)
			return (DONE);
		else
			return (SHORTREC);
		}
	else if (rectype == VARIED) {
		TR("Getrec: VARIED record size\n", EMPTY, EMPTY, EMPTY);
		count = 0;
		while ((lc = fgetc (file)) != EOF) {
			if (lc == recsep) {
				buffer[count] = NULL;
				TR("Getrec: returns buffer=(%s)\n", buffer, EMPTY, EMPTY);
				return (DONE);
				}
			else {
				buffer[count++] = lc;
				if (count >= maxlen)
					return (BIGREC);
				}
			}
		TR("Getrec: end of file\n", EMPTY, EMPTY, EMPTY);
		buffer[count] = NULL;
		return (FILE1EOF);
		}
	else
		return (BADTYPE);				/* Unknown record type. */
}

rec_cmp (args, fmatch, recsep)			/* Compare a record with fmatch. */
char	*args[], *fmatch[], recsep;
{
	int		i,							/* For looping. */
			cmpstat;					/* Comparison value. */

	cmpstat = SAME;
	for (i = 0; fmatch[i] != EMPTY; i++) {
		cmpstat = strcmp (fmatch[i], args[i]);
		if (fmatch[i][0] == recsep || cmpstat == 0)
			cmpstat = SAME;
		else if (cmpstat > 0) {
			cmpstat = BEFORE;			/* fmatch must follow args. */
			/* That is, we could still match. */
			break;
			}
		else {
			cmpstat = AFTER;
			break;						/* The for loop. */
			}
		}
	TR("Rec_cmp: returns %d\n", cmpstat, EMPTY, EMPTY);
	return (cmpstat);
}

argchop (s, fldsep, sargs)				/* Seperate s into its fields. */
char	*s,								/* The string to seperate. */
		fldsep,							/* Seperates the fields. */
		**sargs;						/* Pointers to the fields. */
{
	int		i = 0,						/* For looping. */
			subs = 0;					/* Counter of fields. */

	TR("Argchop: s=(%s) fldsep=(%c) sargs=%d\n", s, fldsep, sargs);

	while (s[i] == fldsep || (fldsep == WHITE && (s[i] == ' '
	  || s[i] == '\t')))
		s[i++] = NULL;					/* Skip initial blank fields. */

	while (s[i] != NULL) {
		if (s[i] == fldsep || (fldsep == WHITE && (s[i] == ' '
		  || s[i] == '\t')))
			s[i] = NULL;				/* Insert a string terminater. */
		else if ((i > 0 && s[i - 1] == NULL) || i == 0)
			sargs[subs++] = &s[i];		/* An argument begins here. */
		i++;
		}
	sargs[subs] = EMPTY;
# ifdef TRACE							/* Echo the fields. */
	for (i = 0; sargs[i] != EMPTY; i++)
		TR(".. %d (%s)\n", i, sargs[i], EMPTY);
# endif
	TR("Argchop: normal return\n", EMPTY, EMPTY, EMPTY);
	return (DONE);
}

/*
 *	sweep -- One shot file modification.  Execute the request and exit.
 */

sweep (opcode, file1, file2, recsep, fldsep, maxlen, fmatch, usrbuf, usrmatch,
  chop, compare)
int		opcode;							/* The operation to perform. */
char	*file1,							/* The search/source file. */
		*file2,							/* The scratch file.  (Required.) */
		recsep,							/* Record seperator. */
		fldsep;							/* Field seperator. */
int		maxlen;							/* Maximum length of record. */
char	*fmatch[],						/* Fields to match. */
		*usrbuf,						/* User's copy of matching record. */
		*usrmatch[];					/* Pointers to arguments. */
int		(*chop)(),						/* Routine to seperate the fields. */
		(*compare)();					/* Routine to compare records. */
{
	char	**arg,						/* Pointers to the fields. */
			*realrec,					/* A copy of what was actually read. */
			*hackrec;					/* A place to keep the fields. */

	int		cmpstat,					/* Comparison status. */
			retcode,					/* Return code. */
			action = 0,					/* Record of what we have done. */
			rectype,					/* FIXED or VARIED record size. */
			i, j;						/* Looping variable. */

	FILE	*fp1, *fp2;					/* Pointers to file1 and file2. */

	char	*malloc (),					/* Memory allocation. */
			*strncpy ();				/* String copy in lPW. */

	TR("Sweep: opcode=%d file1=(%s) file2=(%s)\n", opcode, file1, file2);
	TR(".. recsep=(%c) fldsep=(%c) maxlen=%d ", recsep, fldsep, maxlen);
	TR("usrbuf=%d usrmatch=%d chop=%d\n", usrbuf, usrmatch, chop);
	TR(".. usrbuf=(%s) compare=%d\n", usrbuf, compare, EMPTY);
#ifdef TRACE
	TR(".. fmatch ..\n", EMPTY, EMPTY, EMPTY);
	for (i = 0; fmatch[i] != EMPTY; i++)
		TR("%d (%s)\n", i, fmatch[i], EMPTY);
#endif

	if ((fp1 = fopen (file1, READ)) == NULL) {
		if (opcode == PUTNOW || (opcode & INSERT) > 0) {
			if ((fp1 = fopen (file1, APP)) == NULL)
				return (NOSOURCE);
			if (fclose (fp1) == ERROR)
				return (NOSOURCE);
			if ((fp1 = fopen (file1, READ)) == NULL)
				return (NOSOURCE);
			TR("Sweep: made a blank file1\n", EMPTY, EMPTY, EMPTY);
			opcode = PUTNOW;			/* Since buffers are unneeded. */
			}
		else
			return (NOSOURCE);
		}

	if ((opcode & VERIFY) == 0) {
		if ((fp2 = fopen (file2, READ)) != NULL) {
			if (fclose (fp2) == ERROR || unlink (file2) == ERROR) {
				if (fclose (fp1) == ERROR)
					return (RECURSE + LIVEDEST);
				return (LIVEDEST);
				}
			}
		if ((fp2 = fopen (file2, APP)) == NULL) {
			if (fclose (fp1) == ERROR)
				return (RECURSE + NODEST);
			return (NODEST);
			}
		action += OFILE2;				/* Flag file2 as open. */
		}

	action += OFILE1;					/* Flag file1 as open. */

	TR("Sweep: file1%s open\n", ((action & OFILE2) > 0) ? " and file2" : "",
	  EMPTY, EMPTY);

	if (opcode == PUTNOW)
		goto begin;						/* Because we need no allocations. */

	/* At this point, we begin allocating the three areas that  this routine
	 * needs.  realrec will contain a copy of what was really read.  hackrec
	 * will provide space to store its individual fields.  arg will  provide
	 * space for the pointers the those fields.  The general scheme used for
	 * allocation and use of these areas is as follows:
	 *
	 *			Given:	usrbuf		--			usrbuf		--
	 *					usrmatch	usrmatch	--			--
	 *			---------------------------------------------------
	 *			arg		usrmatch/1	usrmatch/2	ALLOC3		ALLOC3
	 *			---------------------------------------------------
	 *			realrec	ALLOC1		ALLOC1		usrbuf/3	ALLOC1
	 *			---------------------------------------------------
	 *			hackrec	usrbuf/4	ALLOC2		ALLOC2		ALLOC2
	 *			---------------------------------------------------
	 *
	 * Note 1: On DELETE this  array will hold pointers to the actual fields
	 *  of the DELETED record.  On INSERT and REPLACE, it will hold pointers
	 *  into usrbuf which will be MEANINGLESS.  On VERIFY, it will also hold
	 *	pointers, but they are to the matched record.  On PUTNOW, this array
	 *	is unaffected.
	 *
	 * Note 2: On all operations, the pointers in this array will be useless
	 *	upon return.
	 *
	 * Note 3: On DELETE and VERIFY, this array will hold the actual  record
	 *	deleted or matched.  On INSERT or REPLACE, this record will hold the
	 *	actual  record  inserted  or  replaced (and is not used internally).
	 *	PUTNOW has no effect.
	 *
	 * Note 4: On DELETE and VERIFY, this array will hold the actual  record
	 *	deleted or matched.  On INSERT and REPLACE, this array will hold the
	 *	actual record inserted or replaced (in which case, the specification
	 *	of usrmatch will put only junk into that array, as in note 1).  This
	 *	area is not used internally for an INSERT or REPLACE.  This array is
	 *	not affected by PUTNOW.
	 */

	if (maxlen <= 0) {
		retcode = BADSIZE;
		goto bye;
		}

	retcode = NOSPACE;					/* All errors will be for this. */

	if (usrbuf == EMPTY) {				/* We must allocate a buffer. */
		TR("Sweep: no usrbuf\n", EMPTY, EMPTY, EMPTY);
		if ((realrec = malloc ((unsigned) maxlen)) == EMPTY)
			goto bye;
		action += ALLOC1;				/* A buffer was allocated. */
		TR("Sweep: allocated (1) realrec=%d\n", realrec, EMPTY, EMPTY);
		if ((hackrec = malloc ((unsigned) maxlen)) == EMPTY)
			goto bye;
		action += ALLOC2;
		TR("Sweep: allocated (2) hackrec=%d\n", hackrec, EMPTY, EMPTY);
		if (usrmatch == (char**) NULL) { /* User provided no field pointers. */
			j = (maxlen / 2 + 1) * sizeof (char*);
			if ((arg = (char**) malloc ((unsigned) j)) == (char**) NULL)
				goto bye;
			action += ALLOC3;			/* Pointer space was allocated. */
			TR("Sweep: allocated (3) arg=%d\n", arg, EMPTY, EMPTY);
			}
		else
			arg = usrmatch;
		}
	
	else {								/* A usrbuf area was specified */
		TR("Sweep: usrbuf given\n", EMPTY, EMPTY, EMPTY);
		if (usrmatch == (char**) NULL) {
			TR("Sweep: no usrmatch\n", EMPTY, EMPTY, EMPTY);
			if ((opcode & REPLACE) > 0 || (opcode & INSERT) > 0) {
				/* Alas, we cannot use the area. */
				if ((realrec = malloc ((unsigned) maxlen)) == EMPTY)
					goto bye;
				action += ALLOC1;
				TR("Sweep: allocated (4) realrec=%d\n", realrec, EMPTY, EMPTY);
				}
			else
				realrec = usrbuf;
			if ((hackrec = malloc ((unsigned) maxlen)) == EMPTY)
				goto bye;
			action += ALLOC2;
			TR("Sweep: allocated (5) hackrec=%d\n", hackrec, EMPTY, EMPTY);
			j = (maxlen / 2 + 1) * sizeof (char*);
			if ((arg = (char**) malloc ((unsigned) j)) == (char**) NULL)
				goto bye;
			action += ALLOC3;
			TR("Sweep: allocated (6) arg=%d\n", arg, EMPTY, EMPTY);
			}
		else {							/* We have usrbuf and usrmatch. */
			if ((opcode & REPLACE) > 0 || (opcode & INSERT) > 0) {
				if ((hackrec = malloc ((unsigned) maxlen)) == EMPTY)
					goto bye;
				action += ALLOC2;
				TR("Sweep: allocated (7) hackrec=%d\n", hackrec, EMPTY, EMPTY);
				}
			else
				hackrec = usrbuf;
			arg = usrmatch;
			if ((realrec = malloc ((unsigned) maxlen)) == EMPTY)
				goto bye;
			action += ALLOC1;
			TR("Sweep: allocated (8) realrec=%d\n", realrec, EMPTY, EMPTY);
			}
		}

begin:

	if (opcode == PUTNOW) {				/* Insert a message at beginning. */
		if ((action & OFILE2) > 0) {
			fprintf (fp2, "%s%c", usrbuf, recsep);
			retcode = DONE;
			goto bye;
			}
		else {
			retcode = NOTPUT;
			goto bye;
			}
		}

	if (recsep == NULL)
		rectype = FIXED;
	else
		rectype = VARIED;

	while ((i = getrec (fp1, realrec, recsep, rectype, maxlen)) == DONE) {

		/* Copy the buffer */
		strncpy (hackrec, realrec, maxlen);

		/* Seperate the command into arguments. */
		if (chop == (int (*)()) NULL)
			i = argchop (hackrec, fldsep, arg);
		else
			i = (*chop) (hackrec, fldsep, arg);
		if (i != DONE)
			break;						/* Something funny in record. */

		/* Now, determine the alphabetic status of this record. */
		if (compare == (int (*)()) NULL)
			cmpstat = rec_cmp (arg, fmatch, recsep);
		else
			cmpstat = (*compare) (arg, fmatch, recsep);

		/* Now, decide to continue or not. */
		if ((opcode & SEQUENTIAL) == 0 && cmpstat != SAME)
			continue;
			/* We continue if the search is not sequential and the record
			 * did not match. */

		if (cmpstat == SAME) {
			TR("Sweep: Enter SAME section\n", EMPTY, EMPTY, EMPTY);
			if ((opcode & INSERT) > 0)
				retcode = NOTNEW;
			else if ((opcode & VERIFY) > 0)
				retcode = FOUND;
			else {
				if ((opcode & REPLACE) > 0 && usrbuf != EMPTY)
					fprintf (fp2, "%s%c", usrbuf, recsep);
					/* And if this was a DELETE, nothing is printed. */
				retcode = DONE;
				}
			goto bye;
			}

		else if (cmpstat == BEFORE) {
			TR("Sweep: Enter BEFORE section\n", EMPTY, EMPTY, EMPTY);
			/* Put the record into the "scratch" file. */
			if ((opcode & VERIFY) == 0)
				fprintf (fp2, "%s%c", realrec, recsep);
			}

		else {							/* cmpstat == AFTER. */
			TR("Sweep: enter AFTER section\n", EMPTY, EMPTY, EMPTY);
			/* Match goes (or should have come) before this one. */
			if (opcode == SEQINSERT) {
				if (usrbuf != EMPTY)
					fprintf (fp2, "%s%c", usrbuf, recsep);
				/* Put in the INSERTed one, then replace the old one. */
				fprintf (fp2, "%s%c", realrec, recsep);
				retcode = DONE;
				}
			else
				/* We never get here on non-sequential searches. */
				retcode = NOTFOUND;
			goto bye;
			}
		}								/* End of while loop. */
	TR("Sweep: end of record loop\n", EMPTY, EMPTY, EMPTY);

	if (i != FILE1EOF) {
		TR("Sweep: funny death\n", EMPTY, EMPTY, EMPTY);
		retcode = i;					/* So user knows about bad data. */
		goto bye;
		}

	if (fclose (fp1) == ERROR)
		retcode = NOCLOSE;
	action -= OFILE1;

	if (((opcode & VERIFY) | (opcode & REPLACE) | (opcode & DELETE)) > 0) {
		TR("Sweep: not there\n", EMPTY, EMPTY, EMPTY);
		if ((opcode & VERIFY) == 0) {
			if (fclose (fp2) == ERROR)
				retcode = RESETERR;
			action -= OFILE2;
			if (unlink (file2) == ERROR) {
				retcode = RESETERR;		/* Should never happen? */
				goto bye;
				}
			}
		retcode = NOTFOUND;
		goto bye;
		}

	if ((opcode & INSERT) > 0) {
		TR("Sweep: insert tail record\n", EMPTY, EMPTY, EMPTY);
		if (usrbuf != EMPTY)
			fprintf (fp2, "%s%c", usrbuf, recsep);
		retcode = DONE;
		}
	else
		retcode = ABEND;				/* Should never happen? */

bye:

	TR("Sweep: closing retcode=%d action=%d\n", retcode, action, EMPTY);
	if (retcode == DONE) {				/* Successful on a file update. */
		if ((action & OFILE1) > 0 && (action & OFILE2) > 0)
			retcode = copyrest (fp1, fp2, EMPTY, BUFSIZE);
		}

	if ((action & OFILE1) > 0) {
		if (fclose (fp1) == ERROR)
			retcode = NOCLOSE;
		TR("Sweep: file1 closed\n", EMPTY, EMPTY, EMPTY);
		}
	if ((action & OFILE2) > 0) {
		if (fclose (fp2) == ERROR)
			retcode = NOCLOSE;
		TR("Sweep: file2 closed\n", EMPTY, EMPTY, EMPTY);
		}

	/* With the files closed, we now replace file1 with file2 (if needed). */
	if (retcode == DONE) {
		if ((retcode = ftrans (MOVE, file2, file1, EMPTY, BUFSIZE)) != DONE)
			retcode += RECURSE;
		TR("Sweep: final transfer\n", EMPTY, EMPTY, EMPTY);
		}
	if ((action & ALLOC1) > 0) {
		TR("Sweep: free realrec\n", EMPTY, EMPTY, EMPTY);
		free (realrec);
		}
	if ((action & ALLOC2) > 0) {
		TR("Sweep: free hackrec\n", EMPTY, EMPTY, EMPTY);
		free (hackrec);
		}
	if ((action & ALLOC3) > 0) {
		TR("Sweep: free arg\n", EMPTY, EMPTY, EMPTY);
		free ((char*) arg);
		}
	return (retcode);
}

static char Sccsid[] = "@(#)filehand.c	2.1	";
