/*
 *                     RCS file name handling
 */
/****************************************************************************
 *                     creation and deletion of semaphorefile,
 *                     creation of temporary filenames and cleanup()
 *                     pairing of RCS file names and working file names.
 *                     Testprogram: define PAIRTEST
 ****************************************************************************
 *
 * Copyright (C) 1982 by Walter F. Tichy
 *                       Purdue University
 *                       Computer Science Department
 *                       West Lafayette, IN 47907
 *
 * All rights reserved. No part of this software may be sold or distributed
 * in any form or by any means without the prior written permission of the
 * author.
 * Report problems and direct all inquiries to Tichy@purdue (ARPA net).
 */


/* $Log:	C.rcsfnms $
 * Revision 1.1  90/07/15  16:54:45  pmoore
 * Initial revision
 * 
 * Revision 4.6  87/12/18  11:40:23  narten
 * additional file types added from 4.3 BSD version, and SPARC assembler
 * comment character added. Also, more lint cleanups. (Guy Harris)
 *
 * Revision 4.5  87/10/18  10:34:16  narten
 * Updating version numbers. Changes relative to 1.1 actually relative
 * to verion 4.3
 *
 * Revision 1.3  87/03/27  14:22:21  jenkins
 * Port to suns
 *
 * Revision 1.2  85/06/26  07:34:28  svb
 * Comment leader '% ' for '*.tex' files added.
 *
 * Revision 1.1  84/01/23  14:50:24  kcs
 * Initial revision
 *
 * Revision 4.3  83/12/15  12:26:48  wft
 * Added check for KDELIM in file names to pairfilenames().
 *
 * Revision 4.2  83/12/02  22:47:45  wft
 * Added csh, red, and sl file name suffixes.
 *
 * Revision 4.1  83/05/11  16:23:39  wft
 * Added initialization of Dbranch to InitAdmin(). Canged pairfilenames():
 * 1. added copying of path from workfile to RCS file, if RCS file is omitted;
 * 2. added getting the file status of RCS and working files;
 * 3. added ignoring of directories.
 *
 * Revision 3.7  83/05/11  15:01:58  wft
 * Added comtable[] which pairs file name suffixes with comment leaders;
 * updated InitAdmin() accordingly.
 *
 * Revision 3.6  83/04/05  14:47:36  wft
 * fixed Suffix in InitAdmin().
 *
 * Revision 3.5  83/01/17  18:01:04  wft
 * Added getwd() and rename(); these can be removed by defining
 * V4_2BSD, since they are not needed in 4.2 bsd.
 * Changed sys/param.h to sys/types.h.
 *
 * Revision 3.4  82/12/08  21:55:20  wft
 * removed unused variable.
 *
 * Revision 3.3  82/11/28  20:31:37  wft
 * Changed mktempfile() to store the generated file names.
 * Changed getfullRCSname() to store the file and pathname, and to
 * delete leading "../" and "./".
 *
 * Revision 3.2  82/11/12  14:29:40  wft
 * changed pairfilenames() to handle file.sfx,v; also deleted checkpathnosfx(),
 * checksuffix(), checkfullpath(). Semaphore name generation updated.
 * mktempfile() now checks for nil path; lastfilename initialized properly.
 * Added Suffix .h to InitAdmin. Added testprogram PAIRTEST.
 * Moved rmsema, trysema, trydiraccess, getfullRCSname from rcsutil.c to here.
 *
 * Revision 3.1  82/10/18  14:51:28  wft
 * InitAdmin() now initializes StrictLocks=STRICT_LOCKING (def. in rcsbase.h).
 * renamed checkpath() to checkfullpath().
 */


#include "rcsbase.h"

extern FILE *finptr;		/* RCS input file descriptor                 */
extern FILE *frewrite;		/* New RCS file descriptor                   */
extern char *RCSfilename, *workfilename;	/* filenames                     */
extern char *basename;		/* RCS/working file base name                */
struct filestat RCSfilestat, workfilestat;	/* File attributes for RCS
						 * and working file        */

char tempfilename[NCPFN + 10];	/* used for derived file names               */
char sub1filename[NCPPN];	/* used for files path/file.sfx,v            */
char sub2filename[NCPPN];	/* used for files path/RCS/file.sfx,v        */
char semafilename[NCPPN];	/* name of semaphore file                    */
int madesema;			/* indicates whether a semaphore file has
				 * been set */
char *tfnames[10] =		/* temp. file names to be unlinked when
				 * finished   */
{nil, nil, nil, nil, nil, nil, nil, nil, nil, nil};
int lastfilename = -1;		/* index of last file name in tfnames[]      */


struct compair
{
	char *prefix;
	char *comlead;
};

/* comtable pairs each filename suffix with a comment leader. The comment   */
/* leader is placed before each line generated by the $Log keyword. This    */
/* table is used to guess the proper comment leader from the working file's */
/* suffix during initial ci (see InitAdmin()). Comment leaders are needed   */
/* for languages without multiline comments; for others they are optional.  */

#define DEFAULT_COMMENT "# "	/* Default for files with no directory part */

struct compair comtable[] = {
	"asm", "; ",		/* assembler	*/
	"c", " * ",		/* C		*/
	"f", "c ",		/* fortran	*/
	"h", " * ",		/* C-header	*/
	"l", " * ",		/* lex		*/
	"p", " * ",		/* pascal	*/
	"s", "; ",		/* assembler	*/
	"tex", "% ",		/* tex		*/
	"y", " * ",		/* yacc		*/
	nil, ""			/* default for unknown dir part; must always
				 * be last */
};


/* Function: checks ferror(fptr) and aborts the program if there were
 * errors; otherwise closes fptr.
 */
void ffclose(FILE * fptr)
{
	if (ferror(fptr) || fclose(fptr) == EOF)
		faterror("File read or write error; file system full?");
}

/* Function: Checks whether a semaphore file exists for RCSfilename. If yes,
 * returns false. If not, creates one if makesema==true and returns true
 * if successful. If a semaphore file was created, madesema is set to true.
 * The name of the semaphore file is put into variable semafilename.
 *
 * Archimedes version: Always return true without creating a semaphore file,
 * as the OS is not multi-user anyway.
 */
int trysema(char *RCSfilename, int makesema)
{
	USE(RCSfilename);
	USE(makesema);
	return true;
}

/* Function: delete the semaphore file if madeseam==true;
 * sets madesema to false.
 *
 * Archimedes version: Always return true without looking for a semaphore
 * file, as the OS is not multi-user anyway.
 */
int rmsema(void)
{
	return true;
}

/* Function: Free any allocated filenames in the tfname array, and reset
 * the lastfilename pointer to -1 (no temp files used).
 */
void InitCleanup(void)
{
	register int i;
	register char *tp;

	for (i = 0; i <= lastfilename; ++i)
	{
		if ((tp = tfnames[i]) != nil)
			free(tp);
	}

	lastfilename = -1;	/* initialize pointer */
}

/* Function: closes input file and rewrite file.
 * Deletes files in tfnames[], and frees allocated names.
 */
int cleanup(void)
{
	register int i;
	register char *tp;

	if (finptr != NULL)
		VOID fclose(finptr);

	if (frewrite != NULL)
		VOID fclose(frewrite);

	for (i = 0; i <= lastfilename; ++i)
	{
		tp = tfnames[i];
		if (*tp)
			VOID remove(tp);
	}

	InitCleanup();

	return (rmsema());
}

/* Function: Creates a temporary filename, by prefixing filename with the
 * temporary files directory. The filename is held in storage obtained from
 * malloc(). Any previously allocated storage is freed. The new file name is
 * saved in a free slot in tfnames. Because of storage in tfnames, cleanup()
 * can remove the file later. lastfilename indicates the highest occupied
 * slot in tfnames.
 * Returns a pointer to the filename created.
 */
char *mktempfile(char *filename)
{
	register char *tp;

	lastfilename++;

	if ((tp = tfnames[lastfilename]) == nil)
		free(tp);

	return (tfnames[lastfilename] = mktemp(filename));
}

/* Function: Initializes an admin node. The comment leader has already
 * been set up by pairfilenames().
 */
void InitAdmin(void)
{
	Head = Dbranch = nil;
	AccessList = nil;
	Symbols = nil;
	Locks = nil;
	StrictLocks = STRICT_LOCKING;

	Lexinit();		/* Note: if finptr==NULL, reads nothing; only
				 * initializes */
}

/* Function: Given pointers to argc and argv, wildcard_expand goes through
 * argv, performing wildcard expansion on each entry in turn. The values
 * of argc and argv are modified on return.
 *
 * Note that the initial values of argc and argv must be advanced past
 * the command name, and any options.
 */
void wildcard_expand (int *argcp, char ***argvp)
{
	int allocated = 10;
	int nargc = 0;
	char **nargv = (char **) malloc (allocated * sizeof (char **));

	char **av = *argvp;
	int ac = *argcp;

	char *name;

	/* Have we run out of memory? */
	if (nargv == 0)
		faterror("Out of memory");

	for (; ac > 0; --ac, ++av)
	{
		for (name = dirscan(*av); name; name = dirscan(0))
		{
			if (nargc >= allocated)
			{
				allocated += 10;
				nargv = (char **) realloc (nargv, allocated * sizeof (char **));

				/* Have we run out of memory? */
				if (nargv == 0)
					faterror("Out of memory");

			}

			nargv[nargc++] = strdup(name);
		}
	}

	nargv = (char **) realloc (nargv, (nargc + 1) * sizeof (char **));

	/* Have we run out of memory? */
	if (nargv == 0)
		faterror("Out of memory");

	/* Terminate the new argv */
	nargv[nargc] = 0;

	*argcp = nargc;
	*argvp = nargv;
}

/* Function: Given a filename fname, findpairfile scans argv for a pathname
 * ending in suff. If found, returns a pointer to the pathname, and sets
 * the corresponding pointer in argv to nil. Otherwise returns fname.
 * argc indicates the number of entries in argv. Some of them may be nil.
 */
char *findpairfile(int argc, char *argv[], char *suff, char *fname)
{
	register char **next, *match;
	register int count;
	register int i;
	register int len = strlen(suff);
	int sep;

	for (next = argv, count = argc; count > 0; ++next, --count)
	{
		if (*next == nil)
			continue;

		i = strlen(*next) - len;

		if (i < 0)
			continue;

		sep = (i == 0 ? '.' : (*next)[i - 1]);

		if ((sep == '.' || sep == ':') && strlcmp(&(*next)[i], suff) == 0)
		{
			match = *next;
			*next = nil;
			return match;
		}
	}

	return fname;
}

/* Function: Pairs the filenames pointed to by argv; argc indicates
 * how many there are.
 * Places a pointer to the RCS filename into RCSfilename,
 * and a pointer to the name of the working file into workfilename.
 * If both the workfilename and the RCS filename are given, and tostdout
 * is true, a warning is printed.
 *
 * The work and RCS file attributes (as returned from OS_File 5) are stored
 * in workfilestat and RCSfilestat, respectively.
 *
 * If the RCS file exists, it is opened for reading, the file pointer
 * is placed into finptr, and the admin-node is read in; returns 1.
 * If the RCS file does not exist and mustread == true, an error is printed
 * and 0 returned.
 * If the RCS file does not exist and mustread == false, the admin node
 * is initialized to empty (Head, AccessList, Locks, Symbols, StrictLocks,
 * Dbranch) and -1 returned.
 *
 * 0 is returned on all errors. Files that are directories are errors.
 * Also calls InitCleanup();
 */
int pairfilenames(int argc, char **argv, int mustread, int tostdout)
{
	register char *sp, *tp;
	register char *p, *q, *prefix;
	register int i;
	int returncode;

	static char *RCSPrefix = NULL;
	static int RCSPrefixLen = 0;

	/* Get the prefix to use for RCS files from <RCS$Prefix> */
	if (RCSPrefix == NULL)
	{
		if ((RCSPrefix = getenv("RCS$Prefix")) != NULL)
		{
			RCSPrefix = strdup(RCSPrefix);
			RCSPrefixLen = strlen(RCSPrefix);
		}
		else
		{
			RCSPrefix = "RCS.";
			RCSPrefixLen = 4;
		}
	}
		

	/* If we have already paired this filename, stop now */
	if (*argv == nil)
		return 0;

	/* KDELIM causes havoc in keyword expansion, so reject it */
	if (rindex(*argv, KDELIM) != 0)
	{
		error("RCS file name may not contain %c", KDELIM);
		return 0;
	}

	/* Clear up the temporary filenames array, etc. */
	InitCleanup();

	/*
	 * First of all, we must split the current file name into two parts.
	 * The first is the 'pure' file name, which excludes all directory
	 * and filing system prefixes, except possibly for a standard
	 * directory prefix (such as "C." or "TeX."). The full list of
	 * acceptable directory prefixes is given in the comtable array. The
	 * second is the remainder of the file name. This will be tested
	 * later, to see if it ends in "RCS." (when the current filename is
	 * an RCS file rather than a work file).
	 */

	/* First, strip off a filing system prefix */
	p = strchr(*argv, ':');
	if (p == nil)
		p = *argv;
	else
		++p;

	/* Now get the end of the directory prefix */
	q = strrchr(p, '.');

	if (q == nil)
	{
		/* No directory prefix - pure filename has no directory */
		basename = p;
		Comment = DEFAULT_COMMENT;
	}
	else
	{
		/*
		 * Assume no valid directory prefix, and then go through the
		 * compair table looking for the directory. If it's there,
		 * include the directory in the pure filename, and record the
		 * comment leader. Otherwise, use the base file name and the
		 * default comment leader for an unknown dir (the last entry
		 * in comtable)
		 */

		basename = q + 1;
		*q = 0;

		prefix = strrchr(p, '.');
		if (prefix == nil)
			prefix = p;
		else
			++prefix;

		for (i = 0;; ++i)
		{
			if (comtable[i].prefix == nil)
			{
				Comment = comtable[i].comlead;	/* default */
				break;
			}
			else if (strlcmp(prefix, comtable[i].prefix) == 0)
			{
				/* Found it */
				basename = prefix;
				Comment = comtable[i].comlead;
				break;
			}
		}

		/* Restore file name */
		*q = '.';
	}

	/*
	 * We now have the pure file name. Next, we must check the earlier
	 * part of the file name, to see whether it is an RCS file name or a
	 * work file name.
	 */

	if ((basename >= (*argv) + RCSPrefixLen)
	    && strnlcmp(RCSPrefix, basename - RCSPrefixLen, RCSPrefixLen) == 0)
	{
		/*
		 * We have an RCS filename. Look for the corresponding work
		 * file name. If it does not exist, use the 'pure' file name
		 * (ie the RCS file name without the part up to RCSPrefix)
		 */
		RCSfilename = *argv;
		strcpy(tempfilename, basename);
		workfilename = findpairfile(argc - 1, argv + 1, tempfilename, tempfilename);
	}
	else
	{
		/*
		 * We have a work file name. Look for the corresponding RCS
		 * file name. If it does not exist, it should be generated
		 * from the work file name by inserting RCSPrefix just after
		 * the directory part.
		 */
		workfilename = *argv;
		sp = workfilename;
		tp = tempfilename;
		while (sp < basename)
			*tp++ = *sp++;
		strcpy(tp, RCSPrefix);
		strcpy(tp + RCSPrefixLen, basename);
		RCSfilename = findpairfile(argc - 1, argv + 1, tp, tempfilename);
	}

	/*
	 * We now have the file names. The full RCS file name is in
	 * RCSfilename, and the full work file name is in workfilename.
	 */

	/* Now, we need to get the file type of the two files */
	getfilestat(workfilename, &workfilestat);
	getfilestat(RCSfilename, &RCSfilestat);

	/* If either file exists, but is a directory, ignore it */
	if (workfilestat.type == 2)
	{
		diagnose("Directory %s ignored", workfilename);
		return 0;
	}

	if (RCSfilestat.type == 2)
	{
		diagnose("Directory %s ignored", RCSfilename);
		return 0;
	}

	/* Now, try to open the RCS file */
	finptr = fopen(RCSfilename, "r");

	if (finptr != NULL)
	{
		Lexinit();
		getadmin();
		returncode = 1;
	}
	else
	{
		/* We could not open the RCS file */
		if (RCSfilestat.type != 0)
		{
			error("Can't open existing %s", RCSfilename);
			return 0;
		}
		if (mustread)
		{
			error("Can't find %s", RCSfilename);
			return 0;
		}
		else
		{
			/* initialize if not mustread */
			InitAdmin();
			returncode = -1;
		}
	}

	if (tostdout &&
	    !(RCSfilename == tempfilename || workfilename == tempfilename))
		/* The last term determines whether a pair of        */
		/* file names was given in the argument list        */
		warn("Option -p is set; ignoring output file %s", workfilename);

	return returncode;
}

/* Function: Get a file's catalogue information, and place it in a
 * "struct filestat" structure.
 */
void getfilestat(char *name, struct filestat * stat)
{
	_kernel_osfile_block blk;

	stat->type = _kernel_osfile(5, name, &blk);
	stat->load = blk.load;
	stat->exec = blk.exec;
	stat->length = blk.start;
	stat->attr = blk.end;
}

/* Function: Set a file's catalogue information, from data stored in a
 * "struct filestat" structure. Returns true on success, false on failure.
 */
int setfilestat(char *name, struct filestat * stat)
{
	_kernel_osfile_block blk;

	blk.load = stat->load;
	blk.exec = stat->exec;
	blk.end = stat->attr;
	return (_kernel_osfile(1, name, &blk) != _kernel_ERROR);
}

/* Function: returns a pointer to the full path name of the RCS file.
 * Calls getwd(), but only once. Removes leading "../" and "./".
 *
 * For now: simply returns RCSfilename.
 */
char *getfullRCSname(void)
{
	return RCSfilename;
#if 0
	static char pathbuf[NCPPN];
	static char namebuf[NCPPN];
	static int pathlength = 0;

	register char *realname, *lastpathchar;
	register int dotdotcounter, realpathlength;

	if (*RCSfilename == '/')
	{
		return (RCSfilename);
	}
	else
	{
		if (pathlength == 0)
		{		/* call curdir for the first time */
			if (getwd(pathbuf) == NULL)
				faterror("Can't build current directory path");
			pathlength = strlen(pathbuf);
			if (!((pathlength == 1) && (pathbuf[0] == '/')))
			{
				pathbuf[pathlength++] = '/';
				/*
				 * Check needed because some getwd
				 * implementations
				 */
				/* generate "/" for the root.                      */
			}
		}
		/* the following must be redone since RCSfilename may change */
		/* find how many ../ to remvove from RCSfilename */
		dotdotcounter = 0;
		realname = RCSfilename;
		while (realname[0] == '.' &&
		       (realname[1] == '/' || (realname[1] == '.' && realname[2] == '/')))
		{
			if (realname[1] == '/')
			{
				/* drop leading ./ */
				realname += 2;
			}
			else
			{
				/* drop leading ../ and remember */
				dotdotcounter++;
				realname += 3;
			}
		}
		/* now remove dotdotcounter trailing directories from pathbuf */
		lastpathchar = pathbuf + pathlength - 1;
		while (dotdotcounter > 0 && lastpathchar > pathbuf)
		{
			/* move pointer backwards over trailing directory */
			lastpathchar--;
			if (*lastpathchar == '/')
			{
				dotdotcounter--;
			}
		}
		if (dotdotcounter > 0)
		{
			error("Can't generate full path name for RCS file");
			return RCSfilename;
		}
		else
		{
			/* build full path name */
			realpathlength = lastpathchar - pathbuf + 1;
			VOID strncpy(namebuf, pathbuf, realpathlength);
			VOID strcpy(&namebuf[realpathlength], realname);
			return (namebuf);
		}
	}
#endif
}

/* Function: Checks write permission in directory of filename and returns
 * true if writable, false otherwise. Always true on the Archimedes.
 * (Later, worry about 77-entry limit).
 */
int trydiraccess(char *filename)
{
	USE(filename);
	return true;
}

#ifdef PAIRTEST
/* Test program for pairfilenames() and getfullRCSname() */

char *workfilename, *RCSfilename, *basename;
extern int quietflag;

int main(int argc, char *argv[])
{
	int result;
	int initflag, tostdout;
	quietflag = tostdout = initflag = false;
	cmdid = "pair";

	while (--argc, ++argv, argc >= 1 && ((*argv)[0] == '-'))
	{
		switch ((*argv)[1])
		{

		case 'p':
			tostdout = true;
			break;
		case 'i':
			initflag = true;
			break;
		case 'q':
			quietflag = true;
			break;
		default:
			error("unknown option: %s", *argv);
			break;
		}
	}

	do
	{
		RCSfilename = workfilename = nil;
		result = pairfilenames(argc, argv, !initflag, tostdout);
		if (result != 0)
		{
			diagnose("RCSfile: %s; working file: %s", RCSfilename, workfilename);
			diagnose("Full RCS file name: %s", getfullRCSname());
		}
		switch (result)
		{
		case 0:
			continue;	/* already paired file */

		case 1:
			if (initflag)
			{
				error("RCS file %s exists already", RCSfilename);
			}
			else
			{
				diagnose("RCS file %s exists", RCSfilename);
			}
			VOID fclose(finptr);
			break;

		case -1:
			diagnose("RCS file does not exist");
			break;
		}

	} while (++argv, --argc >= 1);

	return 0;
}
#endif
