/*
 *   Copyright (C) 1988-2006 Lucent Technologies Inc. and others.
 *   All Rights Reserved.
 */

/*	cscope - interactive C symbol cross-reference
 *
 *	main functions
 */

#include "global.h"
#include "version.h"	/* FILEVERSION and FIXVERSION */
#include "vp.h"		/* vpdirs and vpndirs */
#if defined(__BORLANDC__) || defined(FOR_WIN32)
#include <io.h>
#include <process.h>
#include <dos.h>
#endif
#include <curses.h>	/* stdscr and TRUE */
#include <errno.h>	/* errno */
#include <fcntl.h>	/* O_RDONLY */
#include <sys/types.h>	/* needed by stat.h */
#include <sys/stat.h>	/* stat */

#define	OPTSEPS	" \t"	/* CSCOPEOPTION separators */
#define	MINHOURS 4	/* minimum no activity timeout hours */

/* defaults for unset environment variables */
#if defined(__BORLANDC__) || defined(FOR_WIN32)
#define	EDITOR	"emacs"
#define	SHELL	"command"
#else
#define	EDITOR	"vi"
#define	SHELL	"sh"
#endif
#define TOOLS	"/opt/exp"
#ifndef	TMPDIR
#if defined(__BORLANDC__)
#define TMPDIR "c:\\temp"
#elif defined(FOR_WIN32)
#define TMPDIR "c:/temp"
#else
#define TMPDIR	"/usr/tmp"
#endif
#endif
#define TMPDIR1 "/var/tmp"
#define TMPDIR2 "/tmp"

/* note: these digraph character frequencies were calculated from possible 
   printable digraphs in the cross-reference for the C compiler */
char	dichar1[] = " teisaprnl(of)=c";	/* 16 most frequent first chars */
char	dichar2[] = " tnerpla";		/* 8 most frequent second chars 
					   using the above as first chars */
char	dicode1[256];		/* digraph first character code */
char	dicode2[256];		/* digraph second character code */

char	*editor, *home, *shell;	/* environment variables */
char	*tools;			/* exptools variable */
BOOL	compress = YES;		/* compress the characters in the crossref */
int	cscopedepth;		/* cscope invocation nesting depth */
char	currentdir[PATHLEN + 1];/* current directory */
BOOL	dbtruncated;		/* database symbols are truncated to 8 chars */
char	**dbvpdirs;		/* directories (including current) in database view path */
int	dbvpndirs;		/* number of directories in database view path */
int	dispcomponents = 1;	/* file path components to display */
#if CCS
BOOL	displayversion;		/* display the C Compilation System version */
#endif
BOOL	editallprompt = YES;	/* prompt between editing files */
BOOL	extendedmenu = NO;	/* extended menu support */
int	fileargc;		/* file argument count */
char	**fileargv;		/* file argument values */
int	fileversion;		/* cross-reference file version */
BOOL	incurses;		/* in curses */
INVCONTROL invcontrol;		/* inverted file control structure */
BOOL	invertedindex;		/* the database has an inverted index */
BOOL	isuptodate;		/* consider the crossref up-to-date */
BOOL	kernelmode;		/* don't use /usr/include - bad for kernels */
BOOL	linemode;		/* use line oriented user interface */
#ifdef MULTILINGUAL
char	*multilingual;		/* multi-lingual language */
#endif
char	*namefile;		/* file of file names */
char	*newinvname;		/* new inverted index file name */
char	*newinvpost;		/* new inverted index postings file name */
char	*newreffile;		/* new cross-reference file name */
FILE	*newrefs;		/* new cross-reference */
BOOL	noacttimeout;		/* no activity timeout occurred */
BOOL	ogs;			/* display OGS book and subsystem names */
FILE	*postings;		/* new inverted index postings */
char	*prependpath;		/* prepend path to file names */
BOOL	recurse_dir = NO;	/* recurse dirs when searching for src files */
BOOL	returnrequired;		/* RETURN required after selection number */
int	symrefs = -1;		/* cross-reference file */
BOOL	systemcalls = NO;	/* system calls ignored */
char	temp1[PATHLEN + 1];	/* temporary file name */
char	temp2[PATHLEN + 1];	/* temporary file name */
long	totalterms;		/* total inverted index terms */
BOOL	truncateto8;		/* truncate symbols to 8 characters */
char	*invname = INVNAME;	/* inverted index to the database */
char	*invpost = INVPOST;	/* inverted index postings */
char	*reffile = REFFILE;	/* cross-reference file path name */
char	*tmpdir;		/* temporary directory */

static	BOOL	buildonly;		/* only build the database */
static	BOOL	fileschanged;		/* assume some files changed */
static	unsigned noacttime;		/* no activity timeout in seconds */
static	BOOL	onesearch;		/* one search only in line mode */
static	char	*reflines;		/* symbol reference lines file */
static	long	traileroffset;		/* file trailer offset */
static	BOOL	unconditional;		/* unconditionally build database */

#ifdef __STDC__
int	main(int argc, char **argv);
void	options(int argc, char **argv);
void	printusage(void);
void	removeindex(void);
void	cannotindex(void);
void	cannotopen(char *file);
void	cannotwrite(char *file);
void	initcompress(void);
void	opendatabase(void);
void	closedatabase(void);
void	rebuild(void);
void	build(void);
int	compare(const void *s1, const void *s2);
char	*getoldfile(void);
void	putheader(char *dir);
void	putlist(char **names, int count);
BOOL	samelist(FILE *oldrefs, char **names, int count);
void	skiplist(FILE *oldrefs);
void	copydata(void);
void	copyinverted(void);
void	putinclude(char *s);
void	movefile(char *new, char *old);
void	entercurses(void);
void	exitcurses(void);
void	timedout(int sig);
void	myexit(int sig);
#if defined(__BORLANDC__) || defined(FOR_WIN32)
int	mkdir(const char *path);
typedef int mode_t;
#endif
#ifdef FOR_WIN32
static	int in_path(char *prog);
#endif
#else
BOOL	samelist();
char	*getoldfile();
int	compare();			/* needed for qsort call */
unsigned alarm();
void	copydata();
void	copyinverted();
void	initcompress();
void	movefile();
void	opendatabase();
void	options();
void	printusage();
void	putheader();
void	putinclude();
void	putlist();
void	skiplist();
void	timedout();
int	mkdir();
#ifdef FOR_WIN32
static	int in_path();
#endif
#endif

main(argc, argv)
int	argc;
char	**argv;
{
	int	envc;			/* environment argument count */
	char	**envv;			/* environment argument list */
	FILE	*names;			/* name file pointer */
	int	oldnum;			/* number in old cross-ref */
	char	path[PATHLEN + 1];	/* file path */
	register FILE	*oldrefs;	/* old cross-reference file */
	register char	*s;
	register int	c, i;
	pid_t	pid;
#if SVR2 && !UNIXPC && defined(FIXKEYPAD)
	void	fixkeypad();
#endif

#if cygwin
/*
 * the GNU libraries used by cygwin are so convoluted that I could not come
 * up with a static initialization for yyin and yyout that would compile, so
 * we'll just do it at run-time.
 */
	yyin = stdin;
#ifndef MULTILINGUAL
	yyout = stdout;
#endif
#endif

#if defined(__BORLANDC__) || defined(FOR_WIN32)

	/* open files in binary mode, that is, no CR/LF to LF translation */
	_fmode = O_BINARY;
#endif
	/* save the command name for messages */
	argv0 = mybasename(argv[0]);

	/* get the current directory for build() and line-oriented P command */
	if (mygetwd(currentdir) == NULL) {
		(void) fprintf(stderr, "%s: warning: cannot get current directory name\n",argv0);
		(void) strcpy(currentdir, "<unknown>");
	}
	/* initialize any view path; (saves time since currendir is known) */
	vpinit(currentdir);
	dbvpndirs = vpndirs;	/* number of directories in database view path */
	dbvpdirs = vpdirs;	/* directories (including current) in database view path */

	/* the first source directory is the current directory */
	sourcedir(".");
	
	/* read the environment */
#if defined(FOR_WIN32)
	editor = mygetenv("EDITOR", NULL);
	if (editor == NULL) {
		/* Check for command suffixes */
		if (in_path(EDITOR)) {
			editor = EDITOR;
		}
	}
#else
	editor = mygetenv("EDITOR", EDITOR);
#endif
	editor = mygetenv("VIEWER", editor);	/* use viewer if set */
	tools = mygetenv("TOOLS", TOOLS);
#if defined(__BORLANDC__)
	home = mygetenv("HOME","c:\\");
#elif defined(FOR_WIN32)
	home = mygetenv("HOME","c:/");
#else
	home = getenv("HOME");
#endif
#if defined(FOR_WIN32)
	shell = mygetenv("SHELL", NULL);
	if (shell == NULL) {
		/* Check for command suffixes */	
		if (in_path(SHELL)) {
			shell = SHELL;
		}
	}
#else
	shell = mygetenv("SHELL", SHELL);
#endif
	tmpdir = mygetenv("TMPDIR", TMPDIR);
#ifdef __BORLANDC__
	{
		char *ptmp = tmpdir;
		while(*ptmp) {
			if(*ptmp == '/') *ptmp = '\\';
			ptmp++;
		}
	}
#endif
#if defined(__BORLANDC__) || defined(FOR_WIN32)
	if (access(tmpdir, WRITE)) {	/* if directory doesn't exist, create it */
		mkdir(tmpdir);
	}
#else
	if (access(tmpdir, WRITE)) {
		/* Try /var/tmp as the symbolic link may not be there */
		tmpdir = TMPDIR1;
		if (access(tmpdir, WRITE)) {
			/* Try /tmp as an alternative */
			tmpdir = TMPDIR2;
			if (access(tmpdir, WRITE)) {
				/* Use $HOME instead */
				tmpdir = home;
			}
		}
	}
#endif
	temp1[0] = temp2[0] = '\0';

	cscopedepth = atoi(mygetenv("CSCOPEDEPTH", "0")); /* increment nesting depth */
	(void) sprintf(path, "CSCOPEDEPTH=%d", ++cscopedepth);
	(void) putenv(stralloc(path));
	if ((s = getenv("CSCOPEOPTIONS")) != NULL) {
		
		/* parse the environment option string */
		envc = 1;
		envv = (char **) mymalloc(sizeof(char *));
		s = strtok(stralloc(s), OPTSEPS);
		while(s != NULL) {
			envv = (char **) myrealloc((char *) envv, ++envc * sizeof(char *));
			envv[envc - 1] = stralloc(s);
			s = strtok((char *) NULL, OPTSEPS);
		}
		/* set the environment options */
		options(envc, envv);
	}
	/* set the command line options */
	options(argc, argv);

	/**********************************************************************
	 * Create the temporary file names. Since these are based upon the
	 * process id, which might rollover, handle the case of a clash.
	 * Increment the pid until we can create the files as needed. Be careful
	 * to avoid special PIDs (0, 1, daemons, and such). For portability,
	 * keep the PID to a positive 16-bit number.
	 **********************************************************************/
	pid = getpid();	/* best first choice for filename suffix */
	for(c = 0; c < 100; c++) {
		FILE *t1,*t2;
		int npid = pid & 0x7fff;	/* ensure a positive 16-bit number */
		if(c && ((npid + c) > 0x7fff)) {	/* leave room for other values */
			npid -= 100;
		}
		else if(npid < 100) {
			npid += 100;	/* don't clash with system processes and such */
		}

#ifdef __BORLANDC__
		(void) sprintf(temp1, "%s\\cscope%d.1", tmpdir, npid + c);
		(void) sprintf(temp2, "%s\\cscope%d.2", tmpdir, npid + c);
#else
		(void) sprintf(temp1, "%s/cscope%d.1",  tmpdir, npid + c);
		(void) sprintf(temp2, "%s/cscope%d.2",  tmpdir, npid + c);
#endif

		/* If we cannot open the file, it is unusable */
		t1 = fopen(temp1,"w");
		if(t1 == (FILE *)NULL) {
			temp1[0] = '\0';
			continue;	/* no good, loop until we get it */
		}

		/* First was OK, now check the second */
		t2 = fopen(temp2,"w");
		if(t2 == (FILE *)NULL) {
			fclose(t1);
			temp1[0] = temp2[0] = '\0';
			continue;	/* no good, loop until we get it */
		}

		/* Close files but leave them on the filesystem for later usage */
		fclose(t1);
		fclose(t2);
		break;
	}

	/* If we still don't have them after 100 tries, let the user know */
	if(!temp1[0]) {
		char msg[128];
		sprintf(msg,"%s/cscope%d.1",tmpdir,pid);
		cannotopen(msg);
	}

#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
	/* if running in the foreground */
	if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
		void myexit();	/* needed by ctrace */

		/* cleanup on the interrupt and quit signals */
		(void) signal(SIGINT, myexit);
		(void) signal(SIGQUIT, myexit);
	}

	/* cleanup on the hangup signal */
	(void) signal(SIGHUP, myexit);

	/* Log this call of cscope */
	(void)sprintf(path,"%s/bin/expcounter",tools);
	if (access(path,1) == 0) {
		(void)sprintf(path,"%s -p cscope -n %s",path,argv0);
		if (system(path) < 0)
			(void)fprintf(stderr,"%s: Cannot log this call, %d, but continuing anyway...\n",argv0,errno);
	}
#endif

	/* Determine if the database is compressed, and uncompress if necessary. */
	uncompressdb();

	/* if the database path is relative and it can't be created */
	if (reffile[0] != '/' &&
#if defined(__BORLANDC__) || defined(FOR_WIN32)

	    /* check for leading slash or leading drive name (drives are
	       always of the form "x:" where x is a single letter) */
	    reffile[0] != '\\' && reffile[1] != ':' &&
#endif
	    access(".", WRITE) != 0) {

		/* if the database may not be up-to-date or can't be read */
		(void) sprintf(path, "%s/%s", home, reffile);
		if (isuptodate == NO || access(reffile, READ) != 0) {

			/* put it in the home directory */
			reffile = stralloc(path);
			(void) sprintf(path, "%s/%s", home, invname);
			invname = stralloc(path);
			(void) sprintf(path, "%s/%s", home, invpost);
			invpost = stralloc(path);
			(void) fprintf(stderr, "%s: symbol database will be %s\n",argv0,
			    reffile);
		}
	}
	/* if the cross-reference is to be considered up-to-date */
	if (isuptodate == YES) {
		if ((oldrefs = vpfopen(reffile, "r")) == NULL) {
			cannotopen(reffile);
			myexit(1);
		}
		/* get the crossref file version but skip the current directory */
		if (fscanf(oldrefs, "cscope %d %*s", &fileversion) != 1) {
			(void) fprintf(stderr, "%s: cannot read file version from file %s\n", argv0, reffile);
			myexit(1);
		}
		/* do not allow upwards compatibility */
		if (fileversion > FILEVERSION) {
			(void) fprintf(stderr, "%s: cannot use new symbol database file format\n",argv0);
			myexit(1);
		}
		if (fileversion >= 8) {

			/* override these command line options */
			compress = YES;
			invertedindex = NO;

			/* see if there are options in the database */
			for (;;) {
				/* no -q leaves multiple blanks */
				while ((c = getc(oldrefs)) == ' ') {
					;
				}
				if (c != '-') {
					(void) ungetc(c, oldrefs);
					break;
				}
				switch (c = getc(oldrefs)) {
				case 'c':	/* ASCII characters only */
					compress = NO;
					break;
#ifdef MULTILINGUAL
				case 'm':	/* multi-lingual */
					/* skip blanks */
					while ((c = getc(oldrefs)) == ' ') {
						;
					}
					if (c == '-') {
						(void) ungetc(c, oldrefs);
						break;
					}
					if (fileversion < 14) {
						break;
					}
					switch (c) {
					case 'c':
					case 'j':
					case 's':
						multilingual = mymalloc(2);
						multilingual[0] = c;
						multilingual[1] = '\0';
						break;
					}
					break;
#endif
				case 'q':	/* quick search */
					invertedindex = YES;
					(void) fscanf(oldrefs, "%ld", &totalterms);
					break;
				case 'T':	/* truncate symbols to 8 characters */
					dbtruncated = YES;
					truncateto8 = YES;
					break;
				}
			}
			initcompress();

			/* seek to the trailer */
			if (fscanf(oldrefs, "%ld", &traileroffset) != 1) {
				(void) fprintf(stderr, "%s: cannot read trailer offset from file %s\n", argv0, reffile);
				myexit(1);
			}
			if (fseek(oldrefs, traileroffset, 0) != 0) {
				(void) fprintf(stderr, "%s: cannot seek to trailer in file %s\n", argv0, reffile);
				myexit(1);
			}
		}
		/* read the view path for use in converting relative paths to full paths */
		/* note: don't overwrite vp[n]dirs because this can cause the wrong
		   database index files to be found in the viewpath */
		if (fileversion >= 13) {
			if (fscanf(oldrefs, "%d", &dbvpndirs) != 1) {
				(void) fprintf(stderr, "%s: cannot read view path size from file %s\n", argv0, reffile);
				myexit(1);
			}
			if (dbvpndirs > 0) {
				dbvpdirs = (char **) mymalloc(dbvpndirs * sizeof(char *));
				for (i = 0; i < dbvpndirs; ++i) {
					if (fscanf(oldrefs, "%s", path) != 1) {
						(void) fprintf(stderr, "%s: cannot read view path from file %s\n", argv0, reffile);
						myexit(1);
					}
					dbvpdirs[i] = stralloc(path);
				}
			}
		}
		/* skip the source and include directory lists */
		skiplist(oldrefs);
		skiplist(oldrefs);

		/* get the number of source files */
		if (fscanf(oldrefs, "%d", &nsrcfiles) != 1) {
			(void) fprintf(stderr, "%s: cannot read source file size from file %s\n", argv0, reffile);
			myexit(1);
		}
		/* get the source file list */
		srcfiles = (char **) mymalloc(nsrcfiles * sizeof(char *));
		if (fileversion >= 9) {

			/* allocate the string space */
			if (fscanf(oldrefs, "%d", &oldnum) != 1) {
				(void) fprintf(stderr, "%s: cannot read string space size from file %s\n", argv0, reffile);
				myexit(1);
			}
			s = mymalloc((unsigned) oldnum);
			(void) getc(oldrefs);	/* skip the newline */
			
			/* read the strings */
			if (fread(s, oldnum, 1, oldrefs) != 1) {
				(void) fprintf(stderr, "%s: cannot read source file names from file %s\n", argv0, reffile);
				myexit(1);
			}
			/* change newlines to nulls */
			for (i = 0; i < nsrcfiles; ++i) {
				srcfiles[i] = s;
				for (++s; *s != '\n'; ++s) {
					;
				}
				*s = '\0';
				++s;
			}
			/* if there is a file of source file names */
			if (namefile != NULL && (names = vpfopen(namefile, "r")) != NULL ||
			    (names = vpfopen(NAMEFILE, "r")) != NULL) {
	
				/* read any -p option from it */
				while (fscanf(names, "%s", path) == 1 && *path == '-') {
					i = path[1];
					s = path + 2;		/* for "-Ipath" */
					if (*s == '\0') {	/* if "-I path" */
						(void) fscanf(names, "%s", path);
						s = path;
					}
					switch (i) {
					case 'p':	/* file path components to display */
						if (*s < '0' || *s > '9') {
							(void) fprintf(stderr, "%s: -p option in file %s: missing or invalid numeric value\n", argv0,
								namefile);
						}
						dispcomponents = atoi(s);
					}
				}
				(void) fclose(names);
			}
		}
		else {
			for (i = 0; i < nsrcfiles; ++i) {
				if (fscanf(oldrefs, "%s", path) != 1) {
					(void) fprintf(stderr, "%s: cannot read source file name from file %s\n", argv0, reffile);
					myexit(1);
				}
				srcfiles[i] = stralloc(path);
			}
		}
		(void) fclose(oldrefs);
	}
	else {
		/* get source directories from the environment */
		if ((s = getenv("SOURCEDIRS")) != NULL) {
			sourcedir(s);
		}
		/* make the source file list */
		srcfiles = (char **) mymalloc(msrcfiles * sizeof(char *));
		makefilelist();
		if (nsrcfiles == 0) {
			(void) fprintf(stderr, "%s: no source files found\n",argv0);
			printusage();
			myexit(1);
		}
		/* get include directories from the environment */
		if ((s = getenv("INCLUDEDIRS")) != NULL) {
			includedir(s);
		}
		/* ignore /usr/include if in kernel mode */
		if (kernelmode == NO) {
			/* add /usr/include to the #include directory list */
			includedir("/usr/include");

#ifdef __BORLANDC__

			/* also add in a likely MS-DOS source for include files */
			includedir("D:/BORLANDC/INCLUDE");
			includedir("C:/BORLANDC/INCLUDE");
			includedir("D:/BC/INCLUDE");
			includedir("C:/BC/INCLUDE");
			includedir("D:/BC45/INCLUDE");
			includedir("C:/BC45/INCLUDE");
#endif
		}

#ifdef MULTILINGUAL
		/* initialize the keyword tables */
#else
		/* initialize the C keyword table */
#endif
		initsymtab();

		/* create the file name(s) used for a new cross-reference */
		(void) strcpy(path, reffile);
		s = mybasename(path);
		*s = '\0';
		(void) strcat(path, "n");
		++s;
		(void) strcpy(s, mybasename(reffile));
		newreffile = stralloc(path);
		(void) strcpy(s, mybasename(invname));
		newinvname = stralloc(path);
		(void) strcpy(s, mybasename(invpost));
		newinvpost = stralloc(path);

		/* build the cross-reference */
		initcompress();
		build();
		if (buildonly == YES) {
			myexit(0);
		}
	}
	opendatabase();
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)

	/* removing a database will not release the disk space if a cscope process
	   has the file open, so a project may want unattended cscope processes to
	   exit overnight, including their subshells and editors */
	if (noacttime) {
		(void) signal(SIGALRM, timedout);
		(void) alarm(noacttime);
	}
#endif
	/* if using the line oriented user interface so cscope can be a 
	   subprocess to emacs or samuel */
	if (linemode == YES) {
		initfield();
		if (*pattern != '\1') {		/* do any optional search */
			if (search() == YES) {
				while ((c = getc(refsfound)) != EOF) {
					(void) putchar(c);
				}
			}
		}
		if (onesearch == YES) {
			myexit(0);
		}
		for (;;) {
			char buf[PATLEN + 2];
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
			if (noacttime) {
				(void) alarm(noacttime);
			}
#endif
			(void) printf(">> ");
			(void) fflush(stdout);
			if (fgets(buf, sizeof(buf), stdin) == NULL) {
				myexit(0);
			}
			/* remove any trailing newline character */
			if (*(s = buf + strlen(buf) - 1) == '\n') {
				*s = '\0';
			}
			switch (*buf) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':	/* samuel only */
			case ':':	/* cdb only */
				field = *buf - '0';
				(void) strcpy(pattern, buf + 1);
				(void) search();
				(void) printf("%s: %d lines\n", argv0, totallines);
				while ((c = getc(refsfound)) != EOF) {
					(void) putchar(c);
				}
				break;

			case 'a':	/* set the directory pattern for 'egrep' */
			case '^':	/* cdb only */
				if(strlen(buf) > 1) {
					if (*buf == 'a') askegrep = YES;
					else if (*buf == '^') filterfiles = YES;
					strcpy(subdirpat, buf + 1);
				}
				else {
					if (*buf == 'a') askegrep = NO;
					else if (*buf == '^') filterfiles = NO;
					subdirpat[0] = '\0';	/* force search of all directories */
				}
				(void) putchar('\n');
				break;

			case 'c':	/* toggle caseless mode */
			case ctrl('C'):
				if (caseless == NO) {
					caseless = YES;
				}
				else {
					caseless = NO;
				}
				egrepcaseless(caseless);
				break;

			case 'r':	/* rebuild database cscope style */
			case ctrl('R'):
				freefilelist();
				makefilelist();
				/* FALLTHROUGH */

			case 'R':	/* rebuild database samuel style */
				rebuild();
				(void) putchar('\n');
				break;

			case 'C':	/* clear file names */
				freefilelist();
				(void) putchar('\n');
				break;

			case 'F':	/* add a file name */
				(void) strcpy(path, buf + 1);
				if (infilelist(path) == NO &&
				    vpaccess(path, READ) == 0) {
					addsrcfile(path);
				}
				(void) putchar('\n');
				break;

			case 'P':	/* print the path to the files */
				if (prependpath != NULL) {
					(void) puts(prependpath);
				}
				else {
					(void) puts(currentdir);
				}
				break;

			case 'q':	/* quit */
			case ctrl('D'):
			case ctrl('Z'):
				myexit(0);

			default:
				(void) fprintf(stderr, "%s: unknown command '%s'\n", argv0, buf);
				break;
			}
		}
		/* NOTREACHED */
	}
	/* pause before clearing the screen if there have been error messages */
	if (errorsfound == YES) {
		errorsfound = NO;
		askforreturn();
	}
#ifndef __BORLANDC__
	(void) signal(SIGINT, SIG_IGN);	/* ignore interrupts */
#ifdef FOR_WIN32
	(void) signal(SIGBREAK, SIG_IGN);	/* ignore interrupts */
	(void) signal(SIGTERM, SIG_IGN);	/* ignore interrupts */
#endif
#ifndef FOR_WIN32
	(void) signal(SIGPIPE, SIG_IGN);/* | command can cause pipe signal */
#endif
#endif
	/* initialize the curses display package */
	(void) initscr();	/* initialize the screen */
	entercurses();
#if TERMINFO
	(void) keypad(stdscr, TRUE);	/* enable the keypad */
#if SVR2 && !UNIXPC && defined(FIXKEYPAD)
	fixkeypad();	/* fix for getch() intermittently returning garbage */
#endif
#endif
#if UNIXPC
	standend();	/* turn off reverse video */
#endif
	dispinit();	/* initialize display parameters */
	initfield();	/* initialize the fields array */
	setfield();	/* set the initial cursor position */
	putmsg("");	/* clear any build progress message */
	display();	/* display the version number and input fields */

	/* do any optional search */
	if (*pattern != '\0') {
		atfield();		/* move to the input field */
		(void) command(ctrl('A'));	/* search */
		display();		/* update the display */
	}
	/* read any symbol reference lines file */
	else if (reflines != NULL) {
		(void) readrefs(reflines);
		display();		/* update the display */
	}
	for (;;) {
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
		if (noacttime) {
			(void) alarm(noacttime);
		}
#endif
		atfield();	/* move to the input field */
		
		/* exit if the quit command is entered */
		if ((c = mygetch()) == EOF || c == ctrl('D') || c == ctrl('Z')) {
			break;
		}
		/* execute the commmand, updating the display if necessary */
		if (command(c) == YES) {
			display();
		}
	}
	/* cleanup and exit */
	myexit(0);
	/* NOTREACHED */
}

void
options(argc, argv)
int	argc;
char	**argv;
{
	char	path[PATHLEN + 1];	/* file path */
	int	c;
	register char	*s;

#ifdef __BORLANDC__
	if (strcmp(argv[0], "/?") == 0) {
		printusage();
		myexit(1);
	}
#endif
	while (--argc > 0 && (*++argv)[0] == '-') {
		for (s = argv[0] + 1; *s != '\0'; s++) {
			
			/* look for an input field number */
			if (isdigit(*s) || *s == ':') {
				field = *s - '0';
				if (*++s == '\0' && --argc > 0) {
					s = *++argv;
				}
				if (strlen(s) > PATLEN) {
					(void) fprintf(stderr, "%s: pattern too long, cannot be > %d characters\n", argv0, PATLEN);
					myexit(1);
				}
				(void) strcpy(pattern, s);
				goto nextarg;
			}
			switch (*s) {
			case '-':	/* end of options */
				--argc;
				++argv;
				goto lastarg;
			case 'V':	/* print the version number */
#if CCS
				displayversion = YES;
				break;
#else
				(void) fprintf(stderr, "%s: version %d%s\n", argv0,
					FILEVERSION, FIXVERSION);
				myexit(0);
#endif
			case 'a':	/* ask for file pattern in "Find this egrep pattern:" */
				askegrep = YES;
				break;
			case 'b':	/* only build the cross-reference */
				buildonly = YES;
				break;
			case 'c':	/* ASCII characters only in crossref */
				compress = NO;
				break;
			case 'C':	/* turn on caseless mode for symbol searches */
				caseless = YES;
				egrepcaseless(caseless);	/* simulate egrep -i flag */
				break;
			case 'd':	/* consider crossref up-to-date */
				isuptodate = YES;
				break;
			case 'e':	/* suppress ^E prompt between files */
				editallprompt = NO;
				break;
			case 'k':	/* ignore /usr/include */
				kernelmode = YES;
				break;
			case 'L':
				onesearch = YES;
				/* FALLTHROUGH */
			case 'l':
				linemode = YES;
				break;
			case 'o':	/* display OGS book and subsystem names */
				ogs = YES;
				break;
			case 'q':	/* quick search */
#if defined(__BORLANDC__) || defined(FOR_WIN32)
				(void) fprintf(stderr, "%s: -q option not supported under MS-DOS\n", argv0);
#else
				invertedindex = YES;
#endif
				break;
			case 'r':	/* display as many lines as possible */
				returnrequired = YES;
				break;
			case 'R':	/* recurse directories for files */
				recurse_dir = YES;
				break;
			case 'T':	/* truncate symbols to 8 characters */
				truncateto8 = YES;
				break;
			case 'u':	/* unconditionally build the cross-reference */
				unconditional = YES;
				break;
			case 'U':	/* assume some files have changed */
				fileschanged = YES;
				break;
			case 'f':	/* alternate cross-reference file */
			case 'F':	/* symbol reference lines file */
			case 'i':	/* file containing file names */
			case 'I':	/* #include file directory */
#ifdef MULTILINGUAL
			case 'm':	/* multi-lingual language */
#endif
			case 'p':	/* file path components to display */
			case 'P':	/* prepend path to file names */
			case 's':	/* additional source file directory */
			case 'S':
			case 't':	/* no activity timeout in hours */
			case 'x':	/* extended menu support */
				c = *s;
				if (*++s == '\0' && --argc > 0) {
					s = *++argv;
				}
				if (*s == '\0') {
					(void) fprintf(stderr, "%s: -%c option: missing or empty value\n", 
						argv0, c);
					goto usage;
				}
				switch (c) {
				case 'f':	/* alternate cross-reference file */
					reffile = s;
					(void) strcpy(path, s);
#if 0  /* !BSD || sun */	/* suns can access Amdahl databases */
					/* System V has a 14 character limit */
					s = mybasename(path);
					if (strlen(s) > 11) {
						s[11] = '\0';
					}
#endif
					s = path + strlen(path);
					(void) strcpy(s, ".in");
					invname = stralloc(path);
					(void) strcpy(s, ".po");
					invpost = stralloc(path);
					break;
				case 'F':	/* symbol reference lines file */
					reflines = s;
					break;
				case 'i':	/* file containing file names */
					namefile = s;
					break;
				case 'I':	/* #include file directory */
					includedir(s);
					break;
#ifdef MULTILINGUAL
				case 'm':	/* multi-lingual language */
					multilingual = s;
					switch (*s) {
					case 'c':
					case 'j':
					case 's':
						break;
					default:
						(void) fprintf(stderr, "%s: -m option: unknown language %s\n",
							argv0,s);
						goto usage;
					}
					break;
#endif
				case 'p':	/* file path components to display */
					if (*s < '0' || *s > '9' ) {
						(void) fprintf(stderr, "%s: -p option: missing or invalid numeric value\n", 
							argv0);
						goto usage;
					}
					dispcomponents = atoi(s);
					break;
				case 'P':	/* prepend path to file names */
					prependpath = s;
					break;
				case 's':	/* additional source directory */
				case 'S':
					sourcedir(s);
					break;
				case 't':	/* no activity timeout in hours */
					if (*s < '1' || *s > '9' ) {
						(void) fprintf(stderr, "%s: -t option: missing or invalid numeric value\n", 
							argv0);
						goto usage;
					}
					c = atoi(s);
					if (c < MINHOURS) {
						(void) fprintf(stderr, "%s: minimum timeout is %d hours\n", argv0, MINHOURS);
						(void) sleep(3);
						c = MINHOURS;
					}
					noacttime = c * 3600;
					break;
				case 'x':	/* extended menu support */
					while (*s != '\0') {
						switch (*s) {
						case 'c':
							extendedmenu = YES;
							break;
						case 's':
							systemcalls = YES;
							break;
						default:
							(void) fprintf(stderr, "%s: -x option: unknown extension %s\n",
								argv0,s);
							goto usage;
						}
						++s;
					}
					break;
				}
				goto nextarg;
			default:
				(void) fprintf(stderr, "%s: unknown option: -%c\n", argv0, 
					*s);
			usage:
				printusage();
				myexit(1);
			}
		}
nextarg:	;
	}
lastarg:

	if((askegrep == YES) && (linemode == YES)) {
		fprintf(stderr,
			"Options '-a' and -l/-L are mutually exclusive -- turning off -a\n");
		askegrep = NO;
	}

	if (linemode == NO) filterfiles = NO;

	/* save the file arguments */
	fileargc = argc;
	fileargv = argv;
}

void
printusage()
{
	(void) fprintf(stderr, "Usage:  %s [-abcCdeklLoqrRtTuUV] [-f file] [-F file] [-i file] [-I dir]\n",argv0);
#ifdef MULTILINGUAL
	(void) fprintf(stderr, "               [-m lang] [-p number] [-P path] [-s dir] [-x ext] [-[0-8] pattern]\n");
#else
	(void) fprintf(stderr, "               [-p number] [-P path] [-s dir] [-x ext] [-[0-8] pattern]\n");
#endif
	(void) fprintf(stderr, "               [source files]\n");
 	(void) fprintf(stderr,
"-a		Ask for subset of files to search in \"Find this egrep pattern:\"\n");
 	(void) fprintf(stderr,
"-b		Build the database only.\n");
 	(void) fprintf(stderr,
"-c		Use only ASCII characters in the database file, that is,\n");
 	(void) fprintf(stderr,
"		do not compress the data.\n");
 	(void) fprintf(stderr,
"-C		Ignore letter case when searching.\n");
 	(void) fprintf(stderr,
"-d		Do not update the database.\n");
 	(void) fprintf(stderr,
"-e		Suppress the <Ctrl>-E command prompt between files.\n");
 	(void) fprintf(stderr,
"-f \"file\"	Use \"file\" as the database file name instead of\n");
 	(void) fprintf(stderr,
"		the default (cscope.out).\n");
 	(void) fprintf(stderr,
"-F \"file\"	Read symbol reference lines from file, just\n");
 	(void) fprintf(stderr,
"		like the \"<\" command.\n");
 	(void) fprintf(stderr,
"-i \"file\"	Read any -I, -p, -q, and -T options and the\n");
 	(void) fprintf(stderr,
"		list of source files from \"file\" instead of the \n");
 	(void) fprintf(stderr,
"		default (cscope.files).\n");
 	(void) fprintf(stderr,
"-I \"dir\"	Look in \"dir\" for #include files.\n");
 	(void) fprintf(stderr,
"-k		Kernel Mode - don't use /usr/include for #include files.\n");
 	(void) fprintf(stderr,
"-l		Line-oriented interface.\n");
 	(void) fprintf(stderr,
"-L		Do a single search with line-oriented output.\n");
#ifdef MULTILINGUAL
 	(void) fprintf(stderr,
"-m \"lang\"	Use lang for multi-lingual cscope.\n");
#endif
 	(void) fprintf(stderr,
"-num \"pattern\"	Go to input field num (counting from 0) and find pattern.\n");
 	(void) fprintf(stderr,
"-p n		Display the last n file path components.\n");
 	(void) fprintf(stderr,
"-P \"path\"	Prepend path to relative file names in pre-build databases.\n");
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
 	(void) fprintf(stderr,
"-q		Build an inverted index for quick symbol seaching.\n");
#endif
 	(void) fprintf(stderr,
"-r		Display as many lines as possible (return required).\n");
 	(void) fprintf(stderr,
"-R		Recurse directories for files.\n");
 	(void) fprintf(stderr,
"-s \"dir\"	Look in \"dir\" for additional source files.\n");
 	(void) fprintf(stderr,
"-T		Use only the first eight characters to match against symbols.\n");
 	(void) fprintf(stderr,
"-u		Unconditionally build the database.\n");
 	(void) fprintf(stderr,
"-U		Check file time stamps.\n");
 	(void) fprintf(stderr,
"-V		Print the version number.\n");
 	(void) fprintf(stderr,
"-x \"ext\"	Use extended menu with option \"ext\".\n");
}

void
removeindex()
{
	(void) fprintf(stderr, "%s: removed files %s and %s\n", argv0, invname, invpost);
	(void) unlink(invname);
	(void) unlink(invpost);
}

void
cannotindex()
{
	(void) fprintf(stderr, "%s: cannot create inverted index; ignoring -q option\n", argv0);
	invertedindex = NO;
	errorsfound = YES;
	(void) fprintf(stderr, "%s: removed files %s and %s\n", argv0, newinvname, newinvpost);
	(void) unlink(newinvname);
	(void) unlink(newinvpost);
	removeindex();	/* remove any existing index to prevent confusion */
}

void
cannotopen(file)
char	*file;
{
	char	msg[MSGLEN + 1];

	(void) sprintf(msg, "Cannot open file %s", file);
	putmsg(msg);
}

void
cannotwrite(file)
char	*file;
{
	char	msg[MSGLEN + 1];

	(void) sprintf(msg, "Removed file %s because write failed", file);
	myperror(msg);	/* display the reason */
	(void) unlink(file);
	myexit(1);	/* calls exit(2), which closes files */
}

/* set up the digraph character tables for text compression */

void
initcompress()
{
	register int	i;
	
	if (compress == YES) {
		for (i = 0; i < 16; ++i) {
			dicode1[(unsigned) (dichar1[i])] = i * 8 + 1;
		}
		for (i = 0; i < 8; ++i) {
			dicode2[(unsigned) (dichar2[i])] = i + 1;
		}
	}
}

/* open the database */

void
opendatabase()
{
	if ((symrefs = vpopen(reffile, O_RDONLY)) == -1) {
		cannotopen(reffile);
		myexit(1);
	}
	blocknumber = -1;	/* force next seek to read the first block */
	
	/* open any inverted index */
	if (invertedindex == YES &&
	    invopen(&invcontrol, invname, invpost, INVAVAIL) == -1) {
		
		/* allow use of -df .../cscope.out */
		if (strcmp(mybasename(invname), "cscope.out.in") == 0) {
			char	*s;

			s = mybasename(invname);
			*s = '\0';
			(void) strcat(s, "cscope.in.out");
			s = mybasename(invpost);
			*s = '\0';
			(void) strcat(s, "cscope.po.out");
			if (invopen(&invcontrol, invname, invpost, INVAVAIL) == -1) {
				askforreturn();		/* so user sees message */
				invertedindex = NO;
			}
		}
		else {
			askforreturn();		/* so user sees message */
			invertedindex = NO;
		}
	}
}

/* close the database */

void
closedatabase()
{
	(void) close(symrefs);
	if (invertedindex == YES) {
		invclose(&invcontrol);
		nsrcoffset = 0;
		npostings = 0;
	}
}

/* rebuild the database */

void
rebuild()
{
	closedatabase();
	build();
	opendatabase();

	/* revert to the initial display */
	if (refsfound != NULL) {
		(void) fclose(refsfound);
		refsfound = NULL;
	}
	*lastfilepath = '\0';	/* last file may have new path */
}

/* build the cross-reference */

void
build()
{
	register int	i;
	register FILE	*oldrefs;	/* old cross-reference file */
	register time_t	reftime;	/* old crossref modification time */
	char	*file;			/* current file */
	char	*oldfile;		/* file in old cross-reference */
	char	newdir[PATHLEN + 1];	/* directory in new cross-reference */
	char	olddir[PATHLEN + 1];	/* directory in old cross-reference */
	char	oldname[PATHLEN + 1];	/* name in old cross-reference */
	int	oldnum;			/* number in old cross-ref */
	struct	stat statstruct;	/* file status */
	int	firstfile;		/* first source file in pass */
	int	lastfile;		/* last source file in pass */
	int	built = 0;		/* built crossref for these files */
	int	copied = 0;		/* copied crossref for these files */
	BOOL	interactive = YES;	/* output progress messages */

	/* normalize the current directory relative to the home directory so
	   the cross-reference is not rebuilt when the user's login is moved */
	(void) strcpy(newdir, currentdir);
	if (strcmp(currentdir, home) == 0) {
		(void) strcpy(newdir, "$HOME");
	}
	else if (strncmp(currentdir, home, strlen(home)) == 0) {
		(void) sprintf(newdir, "$HOME%s", currentdir + strlen(home));
	}
	/* sort the source file names (needed for rebuilding) */
	qsort((char *) srcfiles, (unsigned) nsrcfiles, sizeof(char *), compare);

	/* if there is an old cross-reference and its current directory matches */
	/* or this is an unconditional build */
	if ((oldrefs = vpfopen(reffile, "r")) != NULL && unconditional == NO &&
	    fscanf(oldrefs, "cscope %d %s", &fileversion, olddir) == 2 &&
	    (strcmp(olddir, currentdir) == 0 || /* remain compatible */
	     strcmp(olddir, newdir) == 0)) {

		/* get the cross-reference file's modification time */
		(void) fstat(fileno(oldrefs), &statstruct);
		reftime = statstruct.st_mtime;
		if (fileversion >= 8) {
			BOOL	oldcompress = YES;
			BOOL	oldinvertedindex = NO;
			BOOL	oldtruncateto8 = NO;
#ifdef MULTILINGUAL
			char	*oldmultilingual = NULL;
#endif
			int	c;

			/* see if there are options in the database */
			for (;;) {
				while((c = getc(oldrefs)) == ' ') {
					;
				}
				if (c != '-') {
					(void) ungetc(c, oldrefs);
					break;
				}
				switch (c = getc(oldrefs)) {
				case 'c':	/* ASCII characters only */
					oldcompress = NO;
					break;
#ifdef MULTILINGUAL
				case 'm':	/* multi-lingual */
					/* skip blanks */
					while ((c = getc(oldrefs)) == ' ') {
						;
					}
					if (c == '-') {
						(void) ungetc(c, oldrefs);
						break;
					}
					if (fileversion < 14) {
						break;
					}
					switch (c) {
					case 'c':
					case 'j':
					case 's':
						oldmultilingual = mymalloc(2);
						oldmultilingual[0] = c;
						oldmultilingual[1] = '\0';
						break;
					}
					break;
#endif
				case 'q':	/* quick search */
					oldinvertedindex = YES;
					(void) fscanf(oldrefs, "%ld", &totalterms);
					break;
				case 'T':	/* truncate symbols to 8 characters */
					oldtruncateto8 = YES;
					break;
				}
			}
			/* check the old and new option settings */
			if (oldcompress != compress || oldtruncateto8 != truncateto8) {
				(void) fprintf(stderr, "%s: -c or -T option mismatch between command line and old symbol database\n", argv0);
				goto force;
			}
#ifdef MULTILINGUAL
			if ((oldmultilingual != NULL && multilingual == NULL) ||
			    (oldmultilingual == NULL && multilingual != NULL) ||
			    (oldmultilingual != NULL && multilingual != NULL &&
			     oldmultilingual[0] != multilingual[0])) {
				(void) fprintf(stderr, "%s: -m option mismatch between command line and old symbol database\n", argv0);
				goto force;
			}
#endif
			if (oldinvertedindex != invertedindex) {
				(void) fprintf(stderr, "%s: -q option mismatch between command line and old symbol database\n", argv0);
				if (invertedindex == NO) {
					removeindex();
				}
				goto outofdate;
			}
			/* seek to the trailer */
			if (fscanf(oldrefs, "%ld", &traileroffset) != 1 ||
			    fseek(oldrefs, traileroffset, 0) == -1) {
				(void) fprintf(stderr, "%s: incorrect symbol database file format\n", argv0);
				goto force;
			}
		}
		/* if assuming that some files have changed */
		if (fileschanged == YES) {
			goto outofdate;
		}
		/* see if the view path is the same */
		if (fileversion >= 13 && samelist(oldrefs, vpdirs, vpndirs) == NO) {
			goto outofdate;
		}
		/* see if the directory lists are the same */
		if (samelist(oldrefs, srcdirs, nsrcdirs) == NO ||
		    samelist(oldrefs, incdirs, nincdirs) == NO ||
		    fscanf(oldrefs, "%d", &oldnum) != 1 ||	/* get the old number of files */
		    fileversion >= 9 && fscanf(oldrefs, "%*s") != 0) {	/* skip the string space size */
			goto outofdate;
		}
		/* see if the list of source files is the same and
		   none have been changed up to the included files */
		for (i = 0; i < nsrcfiles; ++i) {
			if (fscanf(oldrefs, "%s", oldname) != 1 ||
			    strnotequal(oldname, srcfiles[i]) ||
			    vpstat(srcfiles[i], &statstruct) != 0 ||
			    statstruct.st_mtime > reftime) {
				goto outofdate;
			}
		}
		/* the old cross-reference is up-to-date */
		/* so get the list of included files */
		while (i++ < oldnum && fscanf(oldrefs, "%s", oldname) == 1) {
			addsrcfile(oldname);
		}
		(void) fclose(oldrefs);
		return;
		
	outofdate:
		/* if the database format has changed, rebuild it all */
		if (fileversion != FILEVERSION) {
			(void) fprintf(stderr, "%s: converting to new symbol database file format\n", argv0);
			goto force;
		}
		/* reopen the old cross-reference file for fast scanning */
		if ((symrefs = vpopen(reffile, O_RDONLY)) == -1) {
			cannotopen(reffile);
			myexit(1);
		}
		/* get the first file name in the old cross-reference */
		blocknumber = -1;
		(void) readblock();	/* read the first cross-ref block */
		(void) scanpast('\t');	/* skip the header */
		oldfile = getoldfile();
	}
	else {	/* force cross-referencing of all the source files */
	force:	reftime = 0;
		oldfile = NULL;
	}
	/* open the new cross-reference file */
	if ((newrefs = fopen(newreffile, "w")) == NULL) {
		cannotopen(newreffile);
		myexit(1);
	}
	if (invertedindex == YES && (postings = fopen(temp1, "w")) == NULL) {
		cannotopen(temp1);
		cannotindex();
	}
	(void) fprintf(stderr, "%s: building symbol database\n", argv0);
	putheader(newdir);
	fileversion = FILEVERSION;
	if (buildonly == YES && !isatty(0)) {
		interactive = NO;
	}
	else {
		initprogress();
	}
	/* output the leading tab expected by crossref() */
	dbputc('\t');

	/* make passes through the source file list until the last level of
	   included files is processed */
	firstfile = 0;
	lastfile = nsrcfiles;
	if (invertedindex == YES) {
		srcoffset = (long *) mymalloc((nsrcfiles + 1) * sizeof(long));
	}
	for (;;) {

		/* get the next source file name */
		for (fileindex = firstfile; fileindex < lastfile; ++fileindex) {
			
			/* display the progress about every three seconds */
			if (interactive == YES && fileindex % 10 == 0) {
				if (copied == 0) {
					progress("%ld files built", (long) built, 0L);
				}
				else {
					progress("%ld files built, %ld files copied", 
					    (long) built, (long) copied);
				}
			}
			/* if the old file has been deleted get the next one */
			file = srcfiles[fileindex];
			while (oldfile != NULL && strcmp(file, oldfile) > 0) {
				oldfile = getoldfile();
			}
			/* if there isn't an old database or this is a new file */
			if (oldfile == NULL || strcmp(file, oldfile) < 0) {
				crossref(file);
				++built;
			}
			/* if this file was modified */
			else if (vpstat(file, &statstruct) == 0 &&
			    statstruct.st_mtime > reftime) {
				crossref(file);
				++built;
				
				/* skip its old crossref so modifying the last
				   source file does not cause all included files
				   to be built.  Unfortunately a new file that is
				   alphabetically last will cause all included
				   files to be build, but this is less likely */
				oldfile = getoldfile();
			}
			else {	/* copy its cross-reference */
				putfilename(file);
				if (invertedindex == YES) {
					copyinverted();
				}
				else {
					copydata();
				}
				++copied;
				oldfile = getoldfile();
			}
		}
		/* see if any included files were found */
		if (lastfile == nsrcfiles) {
			break;
		}
		firstfile = lastfile;
		lastfile = nsrcfiles;
		if (invertedindex == YES) {
			srcoffset = (long *) myrealloc((char *) srcoffset,
			    (nsrcfiles + 1) * sizeof(long));
		}
		/* sort the included file names */
		qsort((char *) &srcfiles[firstfile], (unsigned) (lastfile - 
			firstfile), sizeof(char *), compare);
	}
	/* add a null file name to the trailing tab */
	putfilename("");
	dbputc('\n');
	
	/* get the file trailer offset */

	traileroffset = dboffset;

	/* output the view path and source and include directory and file lists */
	putlist(vpdirs, vpndirs);
	putlist(srcdirs, nsrcdirs);
	putlist(incdirs, nincdirs);
	putlist(srcfiles, nsrcfiles);
	if (fflush(newrefs) == EOF) {	/* rewind doesn't check for write failure */
		cannotwrite(newreffile);
		/* NOTREACHED */
	}

#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
	/* Create the inverted index if requested.
	 *
	 * Note: some systems care about the LC_COLLATE environment variable
	 * when doing a sort.  So, read the old value (if any) then set it to
	 * something that should work.  After the sort, restore the original
	 * situation.
	 */
	if (invertedindex == YES) {
		char	*LC_COLLATE = (char *)getenv("LC_COLLATE"); /* Solaris and Linux */
		char	sortcommand[PATHLEN + 1];

		if (fflush(postings) == EOF) {
			cannotwrite(temp1);
			/* NOTREACHED */
		}
		(void) fstat(fileno(postings), &statstruct);
		(void) fprintf(stderr, "%s: building symbol index: temporary file size is %ld bytes\n",
		    argv0,statstruct.st_size);
		(void) fclose(postings);

		/**********************************************************************
		 * On Solaris 2.6, 2.7, and 2.8 and recent versions of linux (and probably
		 * other machines) the sort command uses the LC_COLLATE environment
		 * variable. Unfortunately, this breaks the sort for cscope. So, unset
		 * this variable, run the sort, and reset it to the original value (if any).
		 * We need to restore the original value because there will be child
		 * shells to cscope and they may depend on the value of LC_COLLATE for
		 * other purposes.
		 **********************************************************************/
		/* force a reasonable collating order */
		(void)putenv("LC_COLLATE=C");

		(void) sprintf(sortcommand, "sort -y -T %s %s", tmpdir, temp1);
		if ((postings = popen(sortcommand, "r")) == NULL) {
			(void) fprintf(stderr, "%s: cannot open pipe to sort command\n",argv0);
			cannotindex();
		}
		else {
			if ((totalterms = invmake(newinvname, newinvpost, postings)) > 0) {
				movefile(newinvname, invname);
				movefile(newinvpost, invpost);
			}
			else {
				cannotindex();
			}
			(void) pclose(postings);
		}

		/* Restore the previous LC_COLLATE situation.  The three cases
		 * are:
		 * 1) was not in the environment
		 * 2) was set to "nothing"
		 * 3) was set to "something"
		 */
		if(!LC_COLLATE) {
			/* case 1
			 * putenv of a variable without an '=' causes that
			 * variable to be removed from the environment.
			*/
			putenv("LC_COLLATE");
		}
		else {
	 		/* case 2 and 3
			 * putenv of a variable with an '=' causes that
			 * variable to be modified in the environment.
			 */
			static char lc_collate[128];	/* NEVER pass an automatic to putenv() */
			strcpy(lc_collate,"LC_COLLATE=");
			if(strlen(LC_COLLATE)) strcat(lc_collate,LC_COLLATE);
			(void)putenv(lc_collate);
		}

		(void) unlink(temp1);
		(void) free((char *) srcoffset);
		(void) fprintf(stderr,
		    "%s: index has %ld references to %ld symbols\n",
		    argv0, npostings, totalterms);
	}
#endif

	/* rewrite the header with the trailer offset and final option list */
	rewind(newrefs);
	putheader(newdir);
	(void) fclose(newrefs);

	/* close the old database file */
	if (symrefs >= 0) {
		(void) close(symrefs);
	}
	if (oldrefs != NULL) {
		(void) fclose(oldrefs);
	}
	/* replace it with the new database file */
	movefile(newreffile, reffile);
}
	
/* string comparison function for qsort */

#if __BORLANDC__ || ilinux || VOID_PROTOTYPE
int
compare(const void *s1, const void *s2)
{
	return(strcmp((char *) s1, (char *) s2));
}
#else
int
compare(s1, s2)
char	**s1;
char	**s2;
{
	return(strcmp(*s1, *s2));
}
#endif

/* get the next file name in the old cross-reference */

char *
getoldfile()
{
	static	char	file[PATHLEN + 1];	/* file name in old crossref */

	if (blockp != NULL) {
		do {
			if (*blockp == NEWFILE) {
				skiprefchar();
				getstring(file);
				if (file[0] != '\0') {	/* if not end-of-crossref */
					return(file);
				}
				return(NULL);
			}
		} while (scanpast('\t') != NULL);
	}
	return(NULL);
}

/* output the cscope version, current directory, database format options, and
   the database trailer offset */

void
putheader(dir)
char	*dir;
{
	dboffset = fprintf(newrefs, "cscope %d %s", FILEVERSION, dir);
	if (compress == NO) {
		dboffset += fprintf(newrefs, " -c");
	}
#ifdef MULTILINGUAL
	if (multilingual != NULL) {
		dboffset += fprintf(newrefs, " -m %c",multilingual[0]);
	}
#endif
	if (invertedindex == YES) {
		dboffset += fprintf(newrefs, " -q %.10ld", totalterms);
	}
	else {	/* leave space so if the header is overwritten without -q
		   because writing the inverted index failed, the header is the
		   same length */
		dboffset += fprintf(newrefs, "              ");
	}
	if (truncateto8 == YES) {
		dboffset += fprintf(newrefs, " -T");
	}
	dbfprintf(newrefs, " %.10ld\n", traileroffset);
}

/* put the name list into the cross-reference file */

void
putlist(names, count)
register char	**names;
register int	count;
{
	register int	i, size = 0;
	
	(void) fprintf(newrefs, "%d\n", count);
	if (names == srcfiles) {

		/* calculate the string space needed */
		for (i = 0; i < count; ++i) {
			size += strlen(names[i]) + 1;
		}
		(void) fprintf(newrefs, "%d\n", size);
	}
	for (i = 0; i < count; ++i) {
		if (fputs(names[i], newrefs) == EOF ||
		    putc('\n', newrefs) == EOF) {
			cannotwrite(newreffile);
			/* NOTREACHED */
		}
	}
}

/* see if the name list is the same in the cross-reference file */

BOOL
samelist(oldrefs, names, count)
register FILE	*oldrefs;
register char	**names;
register int	count;
{
	char	oldname[PATHLEN + 1];	/* name in old cross-reference */
	int	oldcount;
	register int	i;

	/* see if the number of names is the same */
	if (fscanf(oldrefs, "%d", &oldcount) != 1 ||
	    oldcount != count) {
		return(NO);
	}
	/* see if the name list is the same */
	for (i = 0; i < count; ++i) {
		if (fscanf(oldrefs, "%s", oldname) != 1 ||
		    strnotequal(oldname, names[i])) {
			return(NO);
		}
	}
	return(YES);
}

/* skip the list in the cross-reference file */

void
skiplist(oldrefs)
register FILE	*oldrefs;
{
	int	i;
	
	if (fscanf(oldrefs, "%d", &i) != 1) {
		(void) fprintf(stderr, "%s: cannot read list size from file %s\n", argv0, reffile);
		myexit(1);
	}
	while (--i >= 0) {
		if (fscanf(oldrefs, "%*s") != 0) {
			(void) fprintf(stderr, "%s: cannot read list name from file %s\n", argv0, reffile);
			myexit(1);
		}
	}
}

/* copy this file's symbol data */

void
copydata()
{
	char	symbol[BUFSIZ + 1];
	register char	*cp;

	setmark('\t');
	cp = blockp;
	for (;;) {
		/* copy up to the next \t */
		do {	/* innermost loop optimized to only one test */
			while (*cp != '\t') {
				dbputc(*cp++);
			}
		} while (*++cp == '\0' && (cp = readblock()) != NULL);
		dbputc('\t');	/* copy the tab */
		
		/* get the next character */
		if (*(cp + 1) == '\0') {
			cp = readblock();
		}
		/* exit if at the end of this file's data */
		if (cp == NULL || *cp == NEWFILE) {
			break;
		}
		/* look for an #included file */
		if (*cp == INCLUDE) {
			blockp = cp;
			putinclude(symbol);
			putstring(symbol);
			setmark('\t');
			cp = blockp;
		}
	}
	blockp = cp;
}

/* copy this file's symbol data and output the inverted index postings */

void
copyinverted()
{
	register char	*cp;
	register char	c;
	register int	type;	/* reference type (mark character) */
	char	symbol[BUFSIZ + 1];

	/* note: this code was expanded in-line for speed */
	/* while (scanpast('\n') != NULL) { */
	/* other macros were replaced by code using cp instead of blockp */
	cp = blockp;
	for (;;) {
		setmark('\n');
		do {	/* innermost loop optimized to only one test */
			while (*cp != '\n') {
				dbputc(*cp++);
			}
		} while (*++cp == '\0' && (cp = readblock()) != NULL);
		dbputc('\n');	/* copy the newline */
		
		/* get the next character */
		if (*(cp + 1) == '\0') {
			cp = readblock();
		}
		/* exit if at the end of this file's data */
		if (cp == NULL) {
			break;
		}
		switch (*cp) {
		case '\n':
			lineoffset = dboffset + 1;
			continue;
		case '\t':
			dbputc('\t');
			blockp = cp;
			type = getrefchar();
			switch (type) {
			case NEWFILE:		/* file name */
				return;
			case INCLUDE:		/* #included file */
				putinclude(symbol);
				goto output;
			}
			dbputc(type);
			skiprefchar();
			getstring(symbol);
			goto output;
		}
		c = *cp;
		if (c & 0200) {	/* digraph char? */
			c = dichar1[(c & 0177) / 8];
		}
		/* if this is a symbol */
		if (isalpha(c) || c == '_') {
			blockp = cp;
			getstring(symbol);
			type = ' ';
		output:
			putposting(symbol, type);
			putstring(symbol);
			if (blockp == NULL) {
				return;
			}
			cp = blockp;
		}
	}
	blockp = cp;
}

/* process the #included file in the old database */

void
putinclude(s)
register char	*s;
{
	dbputc(INCLUDE);
	skiprefchar();
	getstring(s);
	incfile(s + 1, *s);
}

/* replace the old file with the new file */

void
movefile(new, old)
char	*new;
char	*old;
{
#if defined(__BORLANDC__) || defined(FOR_WIN32)
	char	buffer[128];
	char    *nextchar;

	(void) unlink(old);
	(void) sprintf(buffer, "ren %s %s", new, old);
	for(nextchar = buffer; *nextchar; nextchar++) {
		if(*nextchar == '/') *nextchar = '\\';
	}
	(void) system(buffer);
	(void) unlink(new);
#else
	(void) unlink(old);
	if (link(new, old) == -1) {
		(void) perror("cscope");
		(void) fprintf(stderr, "%s: cannot link file %s to file %s\n",
			argv0, new, old);
		myexit(1);
	}
	if (unlink(new) == -1) {
		(void) perror("cscope");
		(void) fprintf(stderr, "%s: cannot unlink file %s\n", argv0, new);
		errorsfound = YES;
	}
#endif
}

/* enter curses mode */

void
entercurses()
{
	incurses = YES;
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
	(void) nonl();		/* don't translate an output \n to \n\r */
#endif
	(void) cbreak();	/* single character input */
	(void) noecho();	/* don't echo input characters */
	(void) clear();		/* clear the screen */
#if defined(__BORLANDC__)
/* 	attrset(B_BLUE | F_RED); /* not compatible with PD Curses */
	erase();
/* 	attrset(B_BLUE | F_RED | F_BLUE | F_GREEN); */
#endif
	initmouse();		/* initialize any mouse interface */
	drawscrollbar(topline, nextline, totallines);
}

/* exit curses mode */

void
exitcurses()
{
	/* clear the bottom line */
	(void) move(LINES - 1, 0);
	(void) clrtoeol();
	(void) refresh();

	/* exit curses and restore the terminal modes */
	(void) endwin();
	incurses = NO;

	/* restore the mouse */
	cleanupmouse();
	(void) fflush(stdout);
}

/* no activity timeout occurred */

void
timedout(sig)
int	sig;
{
	/* if there is a child process, don't exit until it does */
	if (childpid) {
		closedatabase();
		noacttimeout = YES;
		return;
	}
	exitcurses();
	(void) fprintf(stderr, "%s: no activity for %d hours--exiting\n",
	    argv0, noacttime / 3600);
	myexit(sig);
}

/* cleanup and exit */

void
myexit(sig)
int	sig;
{
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
	(void) signal(SIGHUP, SIG_IGN);	/* deleted layer causes multiple signals */
#endif
	/* remove any temporary files */
	if (temp1[0] != '\0') {
		(void) unlink(temp1);
		(void) unlink(temp2);
	}
	/* restore the terminal to its original mode */
	if (incurses == YES) {
		exitcurses();
	}
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)

	/* dump core for debugging on the quit signal */
	if (sig == SIGQUIT) {
		(void) abort();
	}
#endif
#if DMALLOC
	dmalloc_shutdown();
#endif
	exit(sig);
}


#ifdef FOR_WIN32
/* figure out whether the given program is in the PATH */
static int in_path (
    char *prog
){
    char *path;
    char *dir;
    char fname[256];

    /* get the value of PATH (that we can modify) */
    if ((path = getenv ("PATH")) == NULL) {
	return 0;
    }
    if ((path = strdup(path)) == NULL) {
	return 0;
    }

    /* look through each entry in the PATH to see if the program exists there */
    dir = strtok(path, ";");
    while (dir != NULL) {
	/* does the program exist? */
	sprintf (fname, "%s/%s", dir, prog);
	if (access (fname, 0) == 0) {
	    free (path);
	    return 1;
	}

	/* maybe it has an exe extension? */
	sprintf (fname, "%s/%s.exe", dir, prog);
	if (access (fname, 0) == 0) {
	    free (path);
	    return 1;
	}

	/* maybe it has a com extension? */
	sprintf (fname, "%s/%s.com", dir, prog);
	if (access (fname, 0) == 0) {
	    free (path);
	    return 1;
	}

	/* try the next one */
	dir = strtok (NULL, ";");
    }

    /* program doesn't seem to be in the path */
    free (path);
    return 0;
}
#endif
