Home | History | Annotate | Download | only in cscope-fast
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License, Version 1.0 only
      6  * (the "License").  You may not use this file except in compliance
      7  * with the License.
      8  *
      9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     10  * or http://www.opensolaris.org/os/licensing.
     11  * See the License for the specific language governing permissions
     12  * and limitations under the License.
     13  *
     14  * When distributing Covered Code, include this CDDL HEADER in each
     15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     16  * If applicable, add the following below this CDDL HEADER, with the
     17  * fields enclosed by brackets "[]" replaced with your own identifying
     18  * information: Portions Copyright [yyyy] [name of copyright owner]
     19  *
     20  * CDDL HEADER END
     21  */
     22 /*	Copyright (c) 1988 AT&T	*/
     23 /*	  All Rights Reserved  	*/
     24 
     25 
     26 /*
     27  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
     28  * Use is subject to license terms.
     29  */
     30 
     31 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     32 
     33 /*
     34  *	cscope - interactive C symbol cross-reference
     35  *
     36  *	main functions
     37  */
     38 
     39 #include <curses.h>	/* stdscr and TRUE */
     40 #include <fcntl.h>	/* O_RDONLY */
     41 #include <sys/types.h>	/* needed by stat.h */
     42 #include <unistd.h>	/* O_RDONLY */
     43 #include <unistd.h>	/* O_RDONLY */
     44 #include <sys/stat.h>	/* stat */
     45 #include <libgen.h>	/* O_RDONLY */
     46 #include "global.h"
     47 #include "version.h"	/* FILEVERSION and FIXVERSION */
     48 #include "vp.h"		/* vpdirs and vpndirs */
     49 
     50 #define	OPTSEPS	" \t"	/* CSCOPEOPTION separators */
     51 #define	MINHOURS 4	/* minimum no activity timeout hours */
     52 
     53 /* defaults for unset environment variables */
     54 #define	EDITOR	"vi"
     55 #define	SHELL	"sh"
     56 #define	TMPDIR	"/tmp"
     57 
     58 /*
     59  * note: these digraph character frequencies were calculated from possible
     60  * printable digraphs in the cross-reference for the C compiler
     61  */
     62 char	dichar1[] = " teisaprnl(of)=c";	/* 16 most frequent first chars */
     63 char	dichar2[] = " tnerpla";		/* 8 most frequent second chars */
     64 					/* using the above as first chars */
     65 char	dicode1[256];		/* digraph first character code */
     66 char	dicode2[256];		/* digraph second character code */
     67 
     68 char	*editor, *home, *shell;	/* environment variables */
     69 BOOL	compress = YES;		/* compress the characters in the crossref */
     70 int	cscopedepth;		/* cscope invocation nesting depth */
     71 char	currentdir[PATHLEN + 1]; /* current directory */
     72 BOOL	dbtruncated;		/* database symbols are truncated to 8 chars */
     73 char	**dbvpdirs;		/* directories (including current) in */
     74 				/* database view path */
     75 int	dbvpndirs;		/* # of directories in database view path */
     76 int	dispcomponents = 1;	/* file path components to display */
     77 BOOL	editallprompt = YES;	/* prompt between editing files */
     78 int	fileargc;		/* file argument count */
     79 char	**fileargv;		/* file argument values */
     80 int	fileversion;		/* cross-reference file version */
     81 BOOL	incurses;		/* in curses */
     82 INVCONTROL invcontrol;		/* inverted file control structure */
     83 BOOL	invertedindex;		/* the database has an inverted index */
     84 BOOL	isuptodate;		/* consider the crossref up-to-date */
     85 BOOL	linemode;		/* use line oriented user interface */
     86 char	*namefile;		/* file of file names */
     87 char	*newinvname;		/* new inverted index file name */
     88 char	*newinvpost;		/* new inverted index postings file name */
     89 char	*newreffile;		/* new cross-reference file name */
     90 FILE	*newrefs;		/* new cross-reference */
     91 BOOL	noacttimeout;		/* no activity timeout occurred */
     92 BOOL	ogs;			/* display OGS book and subsystem names */
     93 FILE	*postings;		/* new inverted index postings */
     94 char	*prependpath;		/* prepend path to file names */
     95 BOOL	returnrequired;		/* RETURN required after selection number */
     96 int	symrefs = -1;		/* cross-reference file */
     97 char	temp1[PATHLEN + 1];	/* temporary file name */
     98 char	temp2[PATHLEN + 1];	/* temporary file name */
     99 long	totalterms;		/* total inverted index terms */
    100 BOOL	truncatesyms;		/* truncate symbols to 8 characters */
    101 
    102 static	BOOL	buildonly;		/* only build the database */
    103 static	BOOL	fileschanged;		/* assume some files changed */
    104 static	char	*invname = INVNAME;	/* inverted index to the database */
    105 static	char	*invpost = INVPOST;	/* inverted index postings */
    106 static	unsigned noacttime;		/* no activity timeout in seconds */
    107 static	BOOL	onesearch;		/* one search only in line mode */
    108 static	char	*reffile = REFFILE;	/* cross-reference file path name */
    109 static	char	*reflines;		/* symbol reference lines file */
    110 static	char	*tmpdir;		/* temporary directory */
    111 static	long	traileroffset;		/* file trailer offset */
    112 static	BOOL	unconditional;		/* unconditionally build database */
    113 
    114 static void options(int argc, char **argv);
    115 static void printusage(void);
    116 static void removeindex(void);
    117 static void cannotindex(void);
    118 static void initcompress(void);
    119 static void opendatabase(void);
    120 static void closedatabase(void);
    121 static void build(void);
    122 static int compare(const void *s1, const void *s2);
    123 static char *getoldfile(void);
    124 static void putheader(char *dir);
    125 static void putlist(char **names, int count);
    126 static BOOL samelist(FILE *oldrefs, char **names, int count);
    127 static void skiplist(FILE *oldrefs);
    128 static void copydata(void);
    129 static void copyinverted(void);
    130 static void putinclude(char *s);
    131 static void movefile(char *new, char *old);
    132 static void timedout(int sig);
    133 
    134 int
    135 main(int argc, char **argv)
    136 {
    137 	int	envc;			/* environment argument count */
    138 	char	**envv;			/* environment argument list */
    139 	FILE	*names;			/* name file pointer */
    140 	int	oldnum;			/* number in old cross-ref */
    141 	char	path[PATHLEN + 1];	/* file path */
    142 	FILE	*oldrefs;	/* old cross-reference file */
    143 	char	*s;
    144 	int	c, i;
    145 	pid_t	pid;
    146 
    147 	/* save the command name for messages */
    148 	argv0 = basename(argv[0]);
    149 
    150 	/* get the current directory for build() and line-oriented P command */
    151 	if (mygetwd(currentdir) == NULL) {
    152 		(void) fprintf(stderr,
    153 		    "cscope: warning: cannot get current directory name\n");
    154 		(void) strcpy(currentdir, "<unknown>");
    155 	}
    156 	/* initialize any view path; (saves time since currendir is known) */
    157 	vpinit(currentdir);
    158 	dbvpndirs = vpndirs; /* number of directories in database view path */
    159 	/* directories (including current) in database view path */
    160 	dbvpdirs = vpdirs;
    161 
    162 	/* the first source directory is the current directory */
    163 	sourcedir(".");
    164 
    165 	/* read the environment */
    166 	editor = mygetenv("EDITOR", EDITOR);
    167 	editor = mygetenv("VIEWER", editor);	/* use viewer if set */
    168 	home = getenv("HOME");
    169 	shell = mygetenv("SHELL", SHELL);
    170 	tmpdir = mygetenv("TMPDIR", TMPDIR);
    171 	/* increment nesting depth */
    172 	cscopedepth = atoi(mygetenv("CSCOPEDEPTH", "0"));
    173 	(void) sprintf(path, "CSCOPEDEPTH=%d", ++cscopedepth);
    174 	(void) putenv(stralloc(path));
    175 	if ((s = getenv("CSCOPEOPTIONS")) != NULL) {
    176 
    177 		/* parse the environment option string */
    178 		envc = 1;
    179 		envv = mymalloc(sizeof (char *));
    180 		s = strtok(stralloc(s), OPTSEPS);
    181 		while (s != NULL) {
    182 			envv = myrealloc(envv, ++envc * sizeof (char *));
    183 			envv[envc - 1] = stralloc(s);
    184 			s = strtok((char *)NULL, OPTSEPS);
    185 		}
    186 		/* set the environment options */
    187 		options(envc, envv);
    188 	}
    189 	/* set the command line options */
    190 	options(argc, argv);
    191 
    192 	/* create the temporary file names */
    193 	pid = getpid();
    194 	(void) sprintf(temp1, "%s/cscope%d.1", tmpdir, (int)pid);
    195 	(void) sprintf(temp2, "%s/cscope%d.2", tmpdir, (int)pid);
    196 
    197 	/* if running in the foreground */
    198 	if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
    199 
    200 		/* cleanup on the interrupt and quit signals */
    201 		(void) signal(SIGINT, myexit);
    202 		(void) signal(SIGQUIT, myexit);
    203 	}
    204 
    205 	/* cleanup on the hangup signal */
    206 	(void) signal(SIGHUP, myexit);
    207 	/* if the database path is relative and it can't be created */
    208 	if (reffile[0] != '/' && access(".", WRITE) != 0) {
    209 
    210 		/* if the database may not be up-to-date or can't be read */
    211 		(void) sprintf(path, "%s/%s", home, reffile);
    212 		if (isuptodate == NO || access(reffile, READ) != 0) {
    213 
    214 			/* put it in the home directory */
    215 			reffile = stralloc(path);
    216 			(void) sprintf(path, "%s/%s", home, invname);
    217 			invname = stralloc(path);
    218 			(void) sprintf(path, "%s/%s", home, invpost);
    219 			invpost = stralloc(path);
    220 			(void) fprintf(stderr,
    221 			    "cscope: symbol database will be %s\n", reffile);
    222 		}
    223 	}
    224 	/* if the cross-reference is to be considered up-to-date */
    225 	if (isuptodate == YES) {
    226 		if ((oldrefs = vpfopen(reffile, "r")) == NULL) {
    227 			cannotopen(reffile);
    228 			exit(1);
    229 		}
    230 		/*
    231 		 * get the crossref file version but skip the current
    232 		 * directory
    233 		 */
    234 		if (fscanf(oldrefs, "cscope %d %*s", &fileversion) != 1) {
    235 			(void) fprintf(stderr,
    236 			    "cscope: cannot read file version from file %s\n",
    237 			    reffile);
    238 			exit(1);
    239 		}
    240 		if (fileversion >= 8) {
    241 
    242 			/* override these command line options */
    243 			compress = YES;
    244 			invertedindex = NO;
    245 
    246 			/* see if there are options in the database */
    247 			for (;;) {
    248 				/* no -q leaves multiple blanks */
    249 				while ((c = getc(oldrefs)) == ' ') {
    250 					;
    251 				}
    252 				if (c != '-') {
    253 					(void) ungetc(c, oldrefs);
    254 					break;
    255 				}
    256 				switch (c = getc(oldrefs)) {
    257 				case 'c':	/* ASCII characters only */
    258 					compress = NO;
    259 					break;
    260 				case 'q':	/* quick search */
    261 					invertedindex = YES;
    262 					(void) fscanf(oldrefs,
    263 					    "%ld", &totalterms);
    264 					break;
    265 				case 'T':
    266 					/* truncate symbols to 8 characters */
    267 					dbtruncated = YES;
    268 					truncatesyms = YES;
    269 					break;
    270 				}
    271 			}
    272 			initcompress();
    273 
    274 			/* seek to the trailer */
    275 			if (fscanf(oldrefs, "%ld", &traileroffset) != 1) {
    276 				(void) fprintf(stderr,
    277 				    "cscope: cannot read trailer offset from "
    278 				    "file %s\n", reffile);
    279 				exit(1);
    280 			}
    281 			if (fseek(oldrefs, traileroffset, 0) != 0) {
    282 				(void) fprintf(stderr,
    283 				    "cscope: cannot seek to trailer in "
    284 				    "file %s\n", reffile);
    285 				exit(1);
    286 			}
    287 		}
    288 		/*
    289 		 * read the view path for use in converting relative paths to
    290 		 * full paths
    291 		 *
    292 		 * note: don't overwrite vp[n]dirs because this can cause
    293 		 * the wrong database index files to be found in the viewpath
    294 		 */
    295 		if (fileversion >= 13) {
    296 			if (fscanf(oldrefs, "%d", &dbvpndirs) != 1) {
    297 				(void) fprintf(stderr,
    298 				    "cscope: cannot read view path size from "
    299 				    "file %s\n", reffile);
    300 				exit(1);
    301 			}
    302 			if (dbvpndirs > 0) {
    303 				dbvpdirs = mymalloc(
    304 				    dbvpndirs * sizeof (char *));
    305 				for (i = 0; i < dbvpndirs; ++i) {
    306 					if (fscanf(oldrefs, "%s", path) != 1) {
    307 						(void) fprintf(stderr,
    308 						    "cscope: cannot read view "
    309 						    "path from file %s\n",
    310 						    reffile);
    311 						exit(1);
    312 					}
    313 					dbvpdirs[i] = stralloc(path);
    314 				}
    315 			}
    316 		}
    317 		/* skip the source and include directory lists */
    318 		skiplist(oldrefs);
    319 		skiplist(oldrefs);
    320 
    321 		/* get the number of source files */
    322 		if (fscanf(oldrefs, "%d", &nsrcfiles) != 1) {
    323 			(void) fprintf(stderr,
    324 			    "cscope: cannot read source file size from "
    325 			    "file %s\n", reffile);
    326 			exit(1);
    327 		}
    328 		/* get the source file list */
    329 		srcfiles = mymalloc(nsrcfiles * sizeof (char *));
    330 		if (fileversion >= 9) {
    331 
    332 			/* allocate the string space */
    333 			if (fscanf(oldrefs, "%d", &oldnum) != 1) {
    334 				(void) fprintf(stderr,
    335 				    "cscope: cannot read string space size "
    336 				    "from file %s\n", reffile);
    337 				exit(1);
    338 			}
    339 			s = mymalloc(oldnum);
    340 			(void) getc(oldrefs);	/* skip the newline */
    341 
    342 			/* read the strings */
    343 			if (fread(s, oldnum, 1, oldrefs) != 1) {
    344 				(void) fprintf(stderr,
    345 				    "cscope: cannot read source file names "
    346 				    "from file %s\n", reffile);
    347 				exit(1);
    348 			}
    349 			/* change newlines to nulls */
    350 			for (i = 0; i < nsrcfiles; ++i) {
    351 				srcfiles[i] = s;
    352 				for (++s; *s != '\n'; ++s) {
    353 					;
    354 				}
    355 				*s = '\0';
    356 				++s;
    357 			}
    358 			/* if there is a file of source file names */
    359 			if (namefile != NULL &&
    360 			    (names = vpfopen(namefile, "r")) != NULL ||
    361 			    (names = vpfopen(NAMEFILE, "r")) != NULL) {
    362 
    363 				/* read any -p option from it */
    364 				while (fscanf(names, "%s", path) == 1 &&
    365 				    *path == '-') {
    366 					i = path[1];
    367 					s = path + 2;	/* for "-Ipath" */
    368 					if (*s == '\0') {
    369 						/* if "-I path" */
    370 						(void) fscanf(names,
    371 						    "%s", path);
    372 						s = path;
    373 					}
    374 					switch (i) {
    375 					case 'p':
    376 						/* file path components */
    377 						/* to display */
    378 						if (*s < '0' || *s > '9') {
    379 							(void) fprintf(stderr,
    380 							    "cscope: -p option "
    381 							    "in file %s: "
    382 							    "missing or "
    383 							    "invalid numeric "
    384 							    "value\n",
    385 							    namefile);
    386 						}
    387 						dispcomponents = atoi(s);
    388 					}
    389 				}
    390 				(void) fclose(names);
    391 			}
    392 		} else {
    393 			for (i = 0; i < nsrcfiles; ++i) {
    394 				if (fscanf(oldrefs, "%s", path) != 1) {
    395 					(void) fprintf(stderr,
    396 					    "cscope: cannot read source file "
    397 					    "name from file %s\n", reffile);
    398 					exit(1);
    399 				}
    400 				srcfiles[i] = stralloc(path);
    401 			}
    402 		}
    403 		(void) fclose(oldrefs);
    404 	} else {
    405 		/* get source directories from the environment */
    406 		if ((s = getenv("SOURCEDIRS")) != NULL) {
    407 			sourcedir(s);
    408 		}
    409 		/* make the source file list */
    410 		srcfiles = mymalloc(msrcfiles * sizeof (char *));
    411 		makefilelist();
    412 		if (nsrcfiles == 0) {
    413 			(void) fprintf(stderr,
    414 			    "cscope: no source files found\n");
    415 			printusage();
    416 			exit(1);
    417 		}
    418 		/* get include directories from the environment */
    419 		if ((s = getenv("INCLUDEDIRS")) != NULL) {
    420 			includedir(s);
    421 		}
    422 		/* add /usr/include to the #include directory list */
    423 		includedir("/usr/include");
    424 
    425 		/* initialize the C keyword table */
    426 		initsymtab();
    427 
    428 		/* create the file name(s) used for a new cross-reference */
    429 		(void) strcpy(path, reffile);
    430 		s = basename(path);
    431 		*s = '\0';
    432 		(void) strcat(path, "n");
    433 		++s;
    434 		(void) strcpy(s, basename(reffile));
    435 		newreffile = stralloc(path);
    436 		(void) strcpy(s, basename(invname));
    437 		newinvname = stralloc(path);
    438 		(void) strcpy(s, basename(invpost));
    439 		newinvpost = stralloc(path);
    440 
    441 		/* build the cross-reference */
    442 		initcompress();
    443 		build();
    444 		if (buildonly == YES) {
    445 			exit(0);
    446 		}
    447 	}
    448 	opendatabase();
    449 
    450 	/*
    451 	 * removing a database will not release the disk space if a cscope
    452 	 * process has the file open, so a project may want unattended cscope
    453 	 * processes to exit overnight, including their subshells and editors
    454 	 */
    455 	if (noacttime) {
    456 		(void) signal(SIGALRM, timedout);
    457 		(void) alarm(noacttime);
    458 	}
    459 	/*
    460 	 * if using the line oriented user interface so cscope can be a
    461 	 * subprocess to emacs or samuel
    462 	 */
    463 	if (linemode == YES) {
    464 		if (*pattern != '\0') {		/* do any optional search */
    465 			if (search() == YES) {
    466 				while ((c = getc(refsfound)) != EOF) {
    467 					(void) putchar(c);
    468 				}
    469 			}
    470 		}
    471 		if (onesearch == YES) {
    472 			myexit(0);
    473 		}
    474 		for (;;) {
    475 			char buf[PATLEN + 2];
    476 			if (noacttime) {
    477 				(void) alarm(noacttime);
    478 			}
    479 			(void) printf(">> ");
    480 			(void) fflush(stdout);
    481 			if (fgets(buf, sizeof (buf), stdin) == NULL) {
    482 				myexit(0);
    483 			}
    484 			/* remove any trailing newline character */
    485 			if (*(s = buf + strlen(buf) - 1) == '\n') {
    486 				*s = '\0';
    487 			}
    488 			switch (*buf) {
    489 			case '0':
    490 			case '1':
    491 			case '2':
    492 			case '3':
    493 			case '4':
    494 			case '5':
    495 			case '6':
    496 			case '7':
    497 			case '8':
    498 			case '9':	/* samuel only */
    499 				field = *buf - '0';
    500 				(void) strcpy(pattern, buf + 1);
    501 				(void) search();
    502 				(void) printf("cscope: %d lines\n", totallines);
    503 				while ((c = getc(refsfound)) != EOF) {
    504 					(void) putchar(c);
    505 				}
    506 				break;
    507 
    508 			case 'c':	/* toggle caseless mode */
    509 			case ctrl('C'):
    510 				if (caseless == NO) {
    511 					caseless = YES;
    512 				} else {
    513 					caseless = NO;
    514 				}
    515 				egrepcaseless(caseless);
    516 				break;
    517 
    518 			case 'r':	/* rebuild database cscope style */
    519 			case ctrl('R'):
    520 				freefilelist();
    521 				makefilelist();
    522 				/* FALLTHROUGH */
    523 
    524 			case 'R':	/* rebuild database samuel style */
    525 				rebuild();
    526 				(void) putchar('\n');
    527 				break;
    528 
    529 			case 'C':	/* clear file names */
    530 				freefilelist();
    531 				(void) putchar('\n');
    532 				break;
    533 
    534 			case 'F':	/* add a file name */
    535 				(void) strcpy(path, buf + 1);
    536 				if (infilelist(path) == NO &&
    537 				    vpaccess(path, READ) == 0) {
    538 					addsrcfile(path);
    539 				}
    540 				(void) putchar('\n');
    541 				break;
    542 
    543 			case 'P':	/* print the path to the files */
    544 				if (prependpath != NULL) {
    545 					(void) puts(prependpath);
    546 				} else {
    547 					(void) puts(currentdir);
    548 				}
    549 				break;
    550 
    551 			case 'q':	/* quit */
    552 			case ctrl('D'):
    553 			case ctrl('Z'):
    554 				myexit(0);
    555 
    556 			default:
    557 				(void) fprintf(stderr,
    558 				    "cscope: unknown command '%s'\n", buf);
    559 				break;
    560 			}
    561 		}
    562 		/* NOTREACHED */
    563 	}
    564 	/* pause before clearing the screen if there have been error messages */
    565 	if (errorsfound == YES) {
    566 		errorsfound = NO;
    567 		askforreturn();
    568 	}
    569 	(void) signal(SIGINT, SIG_IGN);	/* ignore interrupts */
    570 	(void) signal(SIGPIPE, SIG_IGN); /* | command can cause pipe signal */
    571 	/* initialize the curses display package */
    572 	(void) initscr();	/* initialize the screen */
    573 	setfield();	/* set the initial cursor position */
    574 	entercurses();
    575 	(void) keypad(stdscr, TRUE);	/* enable the keypad */
    576 	dispinit();	/* initialize display parameters */
    577 	putmsg("");	/* clear any build progress message */
    578 	display();	/* display the version number and input fields */
    579 
    580 	/* do any optional search */
    581 	if (*pattern != '\0') {
    582 		atfield();		/* move to the input field */
    583 		(void) command(ctrl('A'));	/* search */
    584 		display();		/* update the display */
    585 	} else if (reflines != NULL) {
    586 		/* read any symbol reference lines file */
    587 		(void) readrefs(reflines);
    588 		display();		/* update the display */
    589 	}
    590 	for (;;) {
    591 		if (noacttime) {
    592 			(void) alarm(noacttime);
    593 		}
    594 		atfield();	/* move to the input field */
    595 
    596 		/* exit if the quit command is entered */
    597 		if ((c = mygetch()) == EOF || c == ctrl('D') ||
    598 		    c == ctrl('Z')) {
    599 			break;
    600 		}
    601 		/* execute the commmand, updating the display if necessary */
    602 		if (command(c) == YES) {
    603 			display();
    604 		}
    605 	}
    606 	/* cleanup and exit */
    607 	myexit(0);
    608 	/* NOTREACHED */
    609 	return (0);
    610 }
    611 
    612 static void
    613 options(int argc, char **argv)
    614 {
    615 	char	path[PATHLEN + 1];	/* file path */
    616 	int	c;
    617 	char	*s;
    618 
    619 	while (--argc > 0 && (*++argv)[0] == '-') {
    620 		for (s = argv[0] + 1; *s != '\0'; s++) {
    621 			/* look for an input field number */
    622 			if (isdigit(*s)) {
    623 				field = *s - '0';
    624 				if (*++s == '\0' && --argc > 0) {
    625 					s = *++argv;
    626 				}
    627 				if (strlen(s) > PATLEN) {
    628 					(void) fprintf(stderr,
    629 					    "cscope: pattern too long, cannot "
    630 					    "be > %d characters\n", PATLEN);
    631 					exit(1);
    632 				}
    633 				(void) strcpy(pattern, s);
    634 				goto nextarg;
    635 			}
    636 			switch (*s) {
    637 			case '-':	/* end of options */
    638 				--argc;
    639 				++argv;
    640 				goto lastarg;
    641 			case 'V':	/* print the version number */
    642 				(void) fprintf(stderr,
    643 				    "%s: version %d%s\n", argv0,
    644 				    FILEVERSION, FIXVERSION);
    645 				exit(0);
    646 				/*NOTREACHED*/
    647 			case 'b':	/* only build the cross-reference */
    648 				buildonly = YES;
    649 				break;
    650 			case 'c':	/* ASCII characters only in crossref */
    651 				compress = NO;
    652 				break;
    653 			case 'C':
    654 				/* turn on caseless mode for symbol searches */
    655 				caseless = YES;
    656 				/* simulate egrep -i flag */
    657 				egrepcaseless(caseless);
    658 				break;
    659 			case 'd':	/* consider crossref up-to-date */
    660 				isuptodate = YES;
    661 				break;
    662 			case 'e':	/* suppress ^E prompt between files */
    663 				editallprompt = NO;
    664 				break;
    665 			case 'L':
    666 				onesearch = YES;
    667 				/* FALLTHROUGH */
    668 			case 'l':
    669 				linemode = YES;
    670 				break;
    671 			case 'o':
    672 				/* display OGS book and subsystem names */
    673 				ogs = YES;
    674 				break;
    675 			case 'q':	/* quick search */
    676 				invertedindex = YES;
    677 				break;
    678 			case 'r':	/* display as many lines as possible */
    679 				returnrequired = YES;
    680 				break;
    681 			case 'T':	/* truncate symbols to 8 characters */
    682 				truncatesyms = YES;
    683 				break;
    684 			case 'u':
    685 				/* unconditionally build the cross-reference */
    686 				unconditional = YES;
    687 				break;
    688 			case 'U':	/* assume some files have changed */
    689 				fileschanged = YES;
    690 				break;
    691 			case 'f':	/* alternate cross-reference file */
    692 			case 'F':	/* symbol reference lines file */
    693 			case 'i':	/* file containing file names */
    694 			case 'I':	/* #include file directory */
    695 			case 'p':	/* file path components to display */
    696 			case 'P':	/* prepend path to file names */
    697 			case 's':	/* additional source file directory */
    698 			case 'S':
    699 			case 't':	/* no activity timeout in hours */
    700 				c = *s;
    701 				if (*++s == '\0' && --argc > 0) {
    702 					s = *++argv;
    703 				}
    704 				if (*s == '\0') {
    705 					(void) fprintf(stderr,
    706 					    "%s: -%c option: missing or empty "
    707 					    "value\n", argv0, c);
    708 					goto usage;
    709 				}
    710 				switch (c) {
    711 				case 'f':
    712 					/* alternate cross-reference file */
    713 					reffile = s;
    714 					(void) strcpy(path, s);
    715 					/* System V has a 14 character limit */
    716 					s = basename(path);
    717 					if ((int)strlen(s) > 11) {
    718 						s[11] = '\0';
    719 					}
    720 					s = path + strlen(path);
    721 					(void) strcpy(s, ".in");
    722 					invname = stralloc(path);
    723 					(void) strcpy(s, ".po");
    724 					invpost = stralloc(path);
    725 					break;
    726 				case 'F':
    727 					/* symbol reference lines file */
    728 					reflines = s;
    729 					break;
    730 				case 'i':	/* file containing file names */
    731 					namefile = s;
    732 					break;
    733 				case 'I':	/* #include file directory */
    734 					includedir(s);
    735 					break;
    736 				case 'p':
    737 					/* file path components to display */
    738 					if (*s < '0' || *s > '9') {
    739 						(void) fprintf(stderr,
    740 						    "%s: -p option: missing "
    741 						    "or invalid numeric "
    742 						    "value\n", argv0);
    743 						goto usage;
    744 					}
    745 					dispcomponents = atoi(s);
    746 					break;
    747 				case 'P':	/* prepend path to file names */
    748 					prependpath = s;
    749 					break;
    750 				case 's':
    751 				case 'S':
    752 					/* additional source directory */
    753 					sourcedir(s);
    754 					break;
    755 				case 't':
    756 					/* no activity timeout in hours */
    757 					if (*s < '1' || *s > '9') {
    758 						(void) fprintf(stderr,
    759 						    "%s: -t option: missing or "
    760 						    "invalid numeric value\n",
    761 						    argv0);
    762 						goto usage;
    763 					}
    764 					c = atoi(s);
    765 					if (c < MINHOURS) {
    766 						(void) fprintf(stderr,
    767 						    "cscope: minimum timeout "
    768 						    "is %d hours\n", MINHOURS);
    769 						(void) sleep(3);
    770 						c = MINHOURS;
    771 					}
    772 					noacttime = c * 3600;
    773 					break;
    774 				}
    775 				goto nextarg;
    776 			default:
    777 				(void) fprintf(stderr,
    778 				    "%s: unknown option: -%c\n", argv0, *s);
    779 			usage:
    780 				printusage();
    781 				exit(1);
    782 			}
    783 		}
    784 nextarg:	continue;
    785 	}
    786 lastarg:
    787 	/* save the file arguments */
    788 	fileargc = argc;
    789 	fileargv = argv;
    790 }
    791 
    792 static void
    793 printusage(void)
    794 {
    795 	(void) fprintf(stderr,
    796 	    "Usage:  cscope [-bcdelLoqrtTuUV] [-f file] [-F file] [-i file] "
    797 	    "[-I dir] [-s dir]\n");
    798 	(void) fprintf(stderr,
    799 	    "               [-p number] [-P path] [-[0-8] pattern] "
    800 	    "[source files]\n");
    801 	(void) fprintf(stderr,
    802 	    "-b		Build the database only.\n");
    803 	(void) fprintf(stderr,
    804 	    "-c		Use only ASCII characters in the database file, "
    805 	    "that is,\n");
    806 	(void) fprintf(stderr,
    807 	    "		do not compress the data.\n");
    808 	(void) fprintf(stderr,
    809 	    "-d		Do not update the database.\n");
    810 	(void) fprintf(stderr,
    811 	    "-f \"file\"	Use \"file\" as the database file name "
    812 	    "instead of\n");
    813 	(void) fprintf(stderr,
    814 	    "		the default (cscope.out).\n");
    815 	(void) fprintf(stderr,
    816 	    "-F \"file\"	Read symbol reference lines from file, just\n");
    817 /* BEGIN CSTYLED */
    818 	(void) fprintf(stderr,
    819 	    "		like the \"<\" command.\n");
    820 /* END CSTYLED */
    821 	(void) fprintf(stderr,
    822 	    "-i \"file\"	Read any -I, -p, -q, and -T options and the\n");
    823 	(void) fprintf(stderr,
    824 	    "		list of source files from \"file\" instead of the \n");
    825 	(void) fprintf(stderr,
    826 	    "		default (cscope.files).\n");
    827 	(void) fprintf(stderr,
    828 	    "-I \"dir\"	Look in \"dir\" for #include files.\n");
    829 	(void) fprintf(stderr,
    830 	    "-q		Build an inverted index for quick symbol seaching.\n");
    831 	(void) fprintf(stderr,
    832 	    "-s \"dir\"	Look in \"dir\" for additional source files.\n");
    833 }
    834 
    835 static void
    836 removeindex(void)
    837 {
    838 	(void) fprintf(stderr,
    839 	    "cscope: removed files %s and %s\n", invname, invpost);
    840 	(void) unlink(invname);
    841 	(void) unlink(invpost);
    842 }
    843 
    844 static void
    845 cannotindex(void)
    846 {
    847 	(void) fprintf(stderr,
    848 	    "cscope: cannot create inverted index; ignoring -q option\n");
    849 	invertedindex = NO;
    850 	errorsfound = YES;
    851 	(void) fprintf(stderr,
    852 	    "cscope: removed files %s and %s\n", newinvname, newinvpost);
    853 	(void) unlink(newinvname);
    854 	(void) unlink(newinvpost);
    855 	removeindex();	/* remove any existing index to prevent confusion */
    856 }
    857 
    858 void
    859 cannotopen(char *file)
    860 {
    861 	char	msg[MSGLEN + 1];
    862 
    863 	(void) sprintf(msg, "Cannot open file %s", file);
    864 	putmsg(msg);
    865 }
    866 
    867 void
    868 cannotwrite(char *file)
    869 {
    870 	char	msg[MSGLEN + 1];
    871 
    872 	(void) sprintf(msg, "Removed file %s because write failed", file);
    873 	myperror(msg);	/* display the reason */
    874 	(void) unlink(file);
    875 	myexit(1);	/* calls exit(2), which closes files */
    876 }
    877 
    878 /* set up the digraph character tables for text compression */
    879 
    880 static void
    881 initcompress(void)
    882 {
    883 	int	i;
    884 
    885 	if (compress == YES) {
    886 		for (i = 0; i < 16; ++i) {
    887 			dicode1[(unsigned)(dichar1[i])] = i * 8 + 1;
    888 		}
    889 		for (i = 0; i < 8; ++i) {
    890 			dicode2[(unsigned)(dichar2[i])] = i + 1;
    891 		}
    892 	}
    893 }
    894 
    895 /* open the database */
    896 
    897 static void
    898 opendatabase(void)
    899 {
    900 	if ((symrefs = vpopen(reffile, O_RDONLY)) == -1) {
    901 		cannotopen(reffile);
    902 		myexit(1);
    903 	}
    904 	blocknumber = -1;	/* force next seek to read the first block */
    905 
    906 	/* open any inverted index */
    907 	if (invertedindex == YES &&
    908 	    invopen(&invcontrol, invname, invpost, INVAVAIL) == -1) {
    909 		askforreturn();		/* so user sees message */
    910 		invertedindex = NO;
    911 	}
    912 }
    913 
    914 /* close the database */
    915 
    916 static void
    917 closedatabase(void)
    918 {
    919 	(void) close(symrefs);
    920 	if (invertedindex == YES) {
    921 		invclose(&invcontrol);
    922 		nsrcoffset = 0;
    923 		npostings = 0;
    924 	}
    925 }
    926 
    927 /* rebuild the database */
    928 
    929 void
    930 rebuild(void)
    931 {
    932 	closedatabase();
    933 	build();
    934 	opendatabase();
    935 
    936 	/* revert to the initial display */
    937 	if (refsfound != NULL) {
    938 		(void) fclose(refsfound);
    939 		refsfound = NULL;
    940 	}
    941 	*lastfilepath = '\0';	/* last file may have new path */
    942 }
    943 
    944 /* build the cross-reference */
    945 
    946 static void
    947 build(void)
    948 {
    949 	int	i;
    950 	FILE	*oldrefs;	/* old cross-reference file */
    951 	time_t	reftime;	/* old crossref modification time */
    952 	char	*file;			/* current file */
    953 	char	*oldfile;		/* file in old cross-reference */
    954 	char	newdir[PATHLEN + 1];	/* directory in new cross-reference */
    955 	char	olddir[PATHLEN + 1];	/* directory in old cross-reference */
    956 	char	oldname[PATHLEN + 1];	/* name in old cross-reference */
    957 	int	oldnum;			/* number in old cross-ref */
    958 	struct	stat statstruct;	/* file status */
    959 	int	firstfile;		/* first source file in pass */
    960 	int	lastfile;		/* last source file in pass */
    961 	int	built = 0;		/* built crossref for these files */
    962 	int	copied = 0;		/* copied crossref for these files */
    963 	BOOL	interactive = YES;	/* output progress messages */
    964 
    965 	/*
    966 	 * normalize the current directory relative to the home directory so
    967 	 * the cross-reference is not rebuilt when the user's login is moved
    968 	 */
    969 	(void) strcpy(newdir, currentdir);
    970 	if (strcmp(currentdir, home) == 0) {
    971 		(void) strcpy(newdir, "$HOME");
    972 	} else if (strncmp(currentdir, home, strlen(home)) == 0) {
    973 		(void) sprintf(newdir, "$HOME%s", currentdir + strlen(home));
    974 	}
    975 	/* sort the source file names (needed for rebuilding) */
    976 	qsort((char *)srcfiles, (unsigned)nsrcfiles, sizeof (char *), compare);
    977 
    978 	/*
    979 	 * if there is an old cross-reference and its current directory
    980 	 * matches or this is an unconditional build
    981 	 */
    982 	if ((oldrefs = vpfopen(reffile, "r")) != NULL && unconditional == NO &&
    983 	    fscanf(oldrefs, "cscope %d %s", &fileversion, olddir) == 2 &&
    984 	    (strcmp(olddir, currentdir) == 0 || /* remain compatible */
    985 	    strcmp(olddir, newdir) == 0)) {
    986 
    987 		/* get the cross-reference file's modification time */
    988 		(void) fstat(fileno(oldrefs), &statstruct);
    989 		reftime = statstruct.st_mtime;
    990 		if (fileversion >= 8) {
    991 			BOOL	oldcompress = YES;
    992 			BOOL	oldinvertedindex = NO;
    993 			BOOL	oldtruncatesyms = NO;
    994 			int	c;
    995 
    996 			/* see if there are options in the database */
    997 			for (;;) {
    998 				while ((c = getc(oldrefs)) == ' ') {
    999 				}
   1000 				if (c != '-') {
   1001 					(void) ungetc(c, oldrefs);
   1002 					break;
   1003 				}
   1004 				switch (c = getc(oldrefs)) {
   1005 				case 'c':	/* ASCII characters only */
   1006 					oldcompress = NO;
   1007 					break;
   1008 				case 'q':	/* quick search */
   1009 					oldinvertedindex = YES;
   1010 					(void) fscanf(oldrefs,
   1011 					    "%ld", &totalterms);
   1012 					break;
   1013 				case 'T':
   1014 					/* truncate symbols to 8 characters */
   1015 					oldtruncatesyms = YES;
   1016 					break;
   1017 				}
   1018 			}
   1019 			/* check the old and new option settings */
   1020 			if (oldcompress != compress ||
   1021 			    oldtruncatesyms != truncatesyms) {
   1022 				(void) fprintf(stderr,
   1023 				    "cscope: -c or -T option mismatch between "
   1024 				    "command line and old symbol database\n");
   1025 				goto force;
   1026 			}
   1027 			if (oldinvertedindex != invertedindex) {
   1028 				(void) fprintf(stderr,
   1029 				    "cscope: -q option mismatch between "
   1030 				    "command line and old symbol database\n");
   1031 				if (invertedindex == NO) {
   1032 					removeindex();
   1033 				}
   1034 				goto outofdate;
   1035 			}
   1036 			/* seek to the trailer */
   1037 			if (fscanf(oldrefs, "%ld", &traileroffset) != 1 ||
   1038 			    fseek(oldrefs, traileroffset, 0) == -1) {
   1039 				(void) fprintf(stderr,
   1040 				    "cscope: incorrect symbol database file "
   1041 				    "format\n");
   1042 				goto force;
   1043 			}
   1044 		}
   1045 		/* if assuming that some files have changed */
   1046 		if (fileschanged == YES) {
   1047 			goto outofdate;
   1048 		}
   1049 		/* see if the view path is the same */
   1050 		if (fileversion >= 13 &&
   1051 		    samelist(oldrefs, vpdirs, vpndirs) == NO) {
   1052 			goto outofdate;
   1053 		}
   1054 		/* see if the directory lists are the same */
   1055 		if (samelist(oldrefs, srcdirs, nsrcdirs) == NO ||
   1056 		    samelist(oldrefs, incdirs, nincdirs) == NO ||
   1057 		    fscanf(oldrefs, "%d", &oldnum) != 1 ||
   1058 		    fileversion >= 9 && fscanf(oldrefs, "%*s") != 0) {
   1059 			/* skip the string space size */
   1060 			goto outofdate;
   1061 		}
   1062 		/*
   1063 		 * see if the list of source files is the same and
   1064 		 * none have been changed up to the included files
   1065 		 */
   1066 		for (i = 0; i < nsrcfiles; ++i) {
   1067 			if (fscanf(oldrefs, "%s", oldname) != 1 ||
   1068 			    strnotequal(oldname, srcfiles[i]) ||
   1069 			    vpstat(srcfiles[i], &statstruct) != 0 ||
   1070 			    statstruct.st_mtime > reftime) {
   1071 				goto outofdate;
   1072 			}
   1073 		}
   1074 		/* the old cross-reference is up-to-date */
   1075 		/* so get the list of included files */
   1076 		while (i++ < oldnum && fscanf(oldrefs, "%s", oldname) == 1) {
   1077 			addsrcfile(oldname);
   1078 		}
   1079 		(void) fclose(oldrefs);
   1080 		return;
   1081 
   1082 outofdate:
   1083 		/* if the database format has changed, rebuild it all */
   1084 		if (fileversion != FILEVERSION) {
   1085 			(void) fprintf(stderr,
   1086 			    "cscope: converting to new symbol database file "
   1087 			    "format\n");
   1088 			goto force;
   1089 		}
   1090 		/* reopen the old cross-reference file for fast scanning */
   1091 		if ((symrefs = vpopen(reffile, O_RDONLY)) == -1) {
   1092 			cannotopen(reffile);
   1093 			myexit(1);
   1094 		}
   1095 		/* get the first file name in the old cross-reference */
   1096 		blocknumber = -1;
   1097 		(void) readblock();	/* read the first cross-ref block */
   1098 		(void) scanpast('\t');	/* skip the header */
   1099 		oldfile = getoldfile();
   1100 	} else {	/* force cross-referencing of all the source files