/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/* Copyright (c) 1988 AT&T */
/* All Rights Reserved */
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* cscope - interactive C symbol cross-reference
*
* main functions
*/
#include <curses.h> /* stdscr and TRUE */
#include <fcntl.h> /* O_RDONLY */
#include <sys/types.h> /* needed by stat.h */
#include <unistd.h> /* O_RDONLY */
#include <unistd.h> /* O_RDONLY */
#include <sys/stat.h> /* stat */
#include <libgen.h> /* O_RDONLY */
#include "global.h"
#include "version.h" /* FILEVERSION and FIXVERSION */
#include "vp.h" /* vpdirs and vpndirs */
#define OPTSEPS " \t" /* CSCOPEOPTION separators */
#define MINHOURS 4 /* minimum no activity timeout hours */
/* defaults for unset environment variables */
#define EDITOR "vi"
#define SHELL "sh"
#define TMPDIR "/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 */
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; /* # of directories in database view path */
int dispcomponents = 1; /* file path components to display */
BOOL editallprompt = YES; /* prompt between editing files */
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 linemode; /* use line oriented user interface */
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 returnrequired; /* RETURN required after selection number */
int symrefs = -1; /* cross-reference file */
char temp1[PATHLEN + 1]; /* temporary file name */
char temp2[PATHLEN + 1]; /* temporary file name */
long totalterms; /* total inverted index terms */
BOOL truncatesyms; /* truncate symbols to 8 characters */
static BOOL buildonly; /* only build the database */
static BOOL fileschanged; /* assume some files changed */
static char *invname = INVNAME; /* inverted index to the database */
static char *invpost = INVPOST; /* inverted index postings */
static unsigned noacttime; /* no activity timeout in seconds */
static BOOL onesearch; /* one search only in line mode */
static char *reffile = REFFILE; /* cross-reference file path name */
static char *reflines; /* symbol reference lines file */
static char *tmpdir; /* temporary directory */
static long traileroffset; /* file trailer offset */
static BOOL unconditional; /* unconditionally build database */
static void options(int argc, char **argv);
static void printusage(void);
static void removeindex(void);
static void cannotindex(void);
static void initcompress(void);
static void opendatabase(void);
static void closedatabase(void);
static void build(void);
static int compare(const void *s1, const void *s2);
static char *getoldfile(void);
static void putheader(char *dir);
static void putlist(char **names, int count);
static BOOL samelist(FILE *oldrefs, char **names, int count);
static void skiplist(FILE *oldrefs);
static void copydata(void);
static void copyinverted(void);
static void putinclude(char *s);
static void movefile(char *new, char *old);
static void timedout(int sig);
int
main(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 */
FILE *oldrefs; /* old cross-reference file */
char *s;
int c, i;
pid_t pid;
/* save the command name for messages */
argv0 = basename(argv[0]);
/* get the current directory for build() and line-oriented P command */
if (mygetwd(currentdir) == NULL) {
(void) fprintf(stderr,
"cscope: warning: cannot get current directory name\n");
(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 */
/* directories (including current) in database view path */
dbvpdirs = vpdirs;
/* the first source directory is the current directory */
sourcedir(".");
/* read the environment */
editor = mygetenv("EDITOR", EDITOR);
editor = mygetenv("VIEWER", editor); /* use viewer if set */
home = getenv("HOME");
shell = mygetenv("SHELL", SHELL);
tmpdir = mygetenv("TMPDIR", TMPDIR);
/* increment nesting depth */
cscopedepth = atoi(mygetenv("CSCOPEDEPTH", "0"));
(void) sprintf(path, "CSCOPEDEPTH=%d", ++cscopedepth);
(void) putenv(stralloc(path));
if ((s = getenv("CSCOPEOPTIONS")) != NULL) {
/* parse the environment option string */
envc = 1;
envv = mymalloc(sizeof (char *));
s = strtok(stralloc(s), OPTSEPS);
while (s != NULL) {
envv = myrealloc(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 */
pid = getpid();
(void) sprintf(temp1, "%s/cscope%d.1", tmpdir, (int)pid);
(void) sprintf(temp2, "%s/cscope%d.2", tmpdir, (int)pid);
/* if running in the foreground */
if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
/* cleanup on the interrupt and quit signals */
(void) signal(SIGINT, myexit);
(void) signal(SIGQUIT, myexit);
}
/* cleanup on the hangup signal */
(void) signal(SIGHUP, myexit);
/* if the database path is relative and it can't be created */
if (reffile[0] != '/' && 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,
"cscope: symbol database will be %s\n", reffile);
}
}
/* if the cross-reference is to be considered up-to-date */
if (isuptodate == YES) {
if ((oldrefs = vpfopen(reffile, "r")) == NULL) {
cannotopen(reffile);
exit(1);
}
/*
* get the crossref file version but skip the current
* directory
*/
if (fscanf(oldrefs, "cscope %d %*s", &fileversion) != 1) {
(void) fprintf(stderr,
"cscope: cannot read file version from file %s\n",
reffile);
exit(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;
case 'q': /* quick search */
invertedindex = YES;
(void) fscanf(oldrefs,
"%ld", &totalterms);
break;
case 'T':
/* truncate symbols to 8 characters */
dbtruncated = YES;
truncatesyms = YES;
break;
}
}
initcompress();
/* seek to the trailer */
if (fscanf(oldrefs, "%ld", &traileroffset) != 1) {
(void) fprintf(stderr,
"cscope: cannot read trailer offset from "
"file %s\n", reffile);
exit(1);
}
if (fseek(oldrefs, traileroffset, 0) != 0) {
(void) fprintf(stderr,
"cscope: cannot seek to trailer in "
"file %s\n", reffile);
exit(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,
"cscope: cannot read view path size from "
"file %s\n", reffile);
exit(1);
}
if (dbvpndirs > 0) {
dbvpdirs = mymalloc(
dbvpndirs * sizeof (char *));
for (i = 0; i < dbvpndirs; ++i) {
if (fscanf(oldrefs, "%s", path) != 1) {
(void) fprintf(stderr,
"cscope: cannot read view "
"path from file %s\n",
reffile);
exit(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,
"cscope: cannot read source file size from "
"file %s\n", reffile);
exit(1);
}
/* get the source file list */
srcfiles = mymalloc(nsrcfiles * sizeof (char *));
if (fileversion >= 9) {
/* allocate the string space */
if (fscanf(oldrefs, "%d", &oldnum) != 1) {
(void) fprintf(stderr,
"cscope: cannot read string space size "
"from file %s\n", reffile);
exit(1);
}
s = mymalloc(oldnum);
(void) getc(oldrefs); /* skip the newline */
/* read the strings */
if (fread(s, oldnum, 1, oldrefs) != 1) {
(void) fprintf(stderr,
"cscope: cannot read source file names "
"from file %s\n", reffile);
exit(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,
"cscope: -p option "
"in file %s: "
"missing or "
"invalid numeric "
"value\n",
namefile);
}
dispcomponents = atoi(s);
}
}
(void) fclose(names);
}
} else {
for (i = 0; i < nsrcfiles; ++i) {
if (fscanf(oldrefs, "%s", path) != 1) {
(void) fprintf(stderr,
"cscope: cannot read source file "
"name from file %s\n", reffile);
exit(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 = mymalloc(msrcfiles * sizeof (char *));
makefilelist();
if (nsrcfiles == 0) {
(void) fprintf(stderr,
"cscope: no source files found\n");
printusage();
exit(1);
}
/* get include directories from the environment */
if ((s = getenv("INCLUDEDIRS")) != NULL) {
includedir(s);
}
/* add /usr/include to the #include directory list */
includedir("/usr/include");
/* initialize the C keyword table */
initsymtab();
/* create the file name(s) used for a new cross-reference */
(void) strcpy(path, reffile);
s = basename(path);
*s = '\0';
(void) strcat(path, "n");
++s;
(void) strcpy(s, basename(reffile));
newreffile = stralloc(path);
(void) strcpy(s, basename(invname));
newinvname = stralloc(path);
(void) strcpy(s, basename(invpost));
newinvpost = stralloc(path);
/* build the cross-reference */
initcompress();
build();
if (buildonly == YES) {
exit(0);
}
}
opendatabase();
/*
* 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);
}
/*
* if using the line oriented user interface so cscope can be a
* subprocess to emacs or samuel
*/
if (linemode == YES) {
if (*pattern != '\0') { /* 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 (noacttime) {
(void) alarm(noacttime);
}
(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 */
field = *buf - '0';
(void) strcpy(pattern, buf + 1);
(void) search();
(void) printf("cscope: %d lines\n", totallines);
while ((c = getc(refsfound)) != EOF) {
(void) putchar(c);
}
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,
"cscope: unknown command '%s'\n", buf);
break;
}
}
/* NOTREACHED */
}
/* pause before clearing the screen if there have been error messages */
if (errorsfound == YES) {
errorsfound = NO;
askforreturn();
}
(void) signal(SIGINT, SIG_IGN); /* ignore interrupts */
(void) signal(SIGPIPE, SIG_IGN); /* | command can cause pipe signal */
/* initialize the curses display package */
(void) initscr(); /* initialize the screen */
setfield(); /* set the initial cursor position */
entercurses();
(void) keypad(stdscr, TRUE); /* enable the keypad */
dispinit(); /* initialize display parameters */
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 */
} else if (reflines != NULL) {
/* read any symbol reference lines file */
(void) readrefs(reflines);
display(); /* update the display */
}
for (;;) {
if (noacttime) {
(void) alarm(noacttime);
}
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 */
return (0);
}
static void
options(int argc, char **argv)
{
char path[PATHLEN + 1]; /* file path */
int c;
char *s;
while (--argc > 0 && (*++argv)[0] == '-') {
for (s = argv[0] + 1; *s != '\0'; s++) {
/* look for an input field number */
if (isdigit(*s)) {
field = *s - '0';
if (*++s == '\0' && --argc > 0) {
s = *++argv;
}
if (strlen(s) > PATLEN) {
(void) fprintf(stderr,
"cscope: pattern too long, cannot "
"be > %d characters\n", PATLEN);
exit(1);
}
(void) strcpy(pattern, s);
goto nextarg;
}
switch (*s) {
case '-': /* end of options */
--argc;
++argv;
goto lastarg;
case 'V': /* print the version number */
(void) fprintf(stderr,
"%s: version %d%s\n", argv0,
FILEVERSION, FIXVERSION);
exit(0);
/*NOTREACHED*/
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;
/* simulate egrep -i flag */
egrepcaseless(caseless);
break;
case 'd': /* consider crossref up-to-date */
isuptodate = YES;
break;
case 'e': /* suppress ^E prompt between files */
editallprompt = NO;
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 */
invertedindex = YES;
break;
case 'r': /* display as many lines as possible */
returnrequired = YES;
break;
case 'T': /* truncate symbols to 8 characters */
truncatesyms = 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 */
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 */
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);
/* System V has a 14 character limit */
s = basename(path);
if ((int)strlen(s) > 11) {
s[11] = '\0';
}
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;
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':
case 'S':
/* additional source directory */
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,
"cscope: minimum timeout "
"is %d hours\n", MINHOURS);
(void) sleep(3);
c = MINHOURS;
}
noacttime = c * 3600;
break;
}
goto nextarg;
default:
(void) fprintf(stderr,
"%s: unknown option: -%c\n", argv0, *s);
usage:
printusage();
exit(1);
}
}
nextarg: continue;
}
lastarg:
/* save the file arguments */
fileargc = argc;
fileargv = argv;
}
static void
printusage(void)
{
(void) fprintf(stderr,
"Usage: cscope [-bcdelLoqrtTuUV] [-f file] [-F file] [-i file] "
"[-I dir] [-s dir]\n");
(void) fprintf(stderr,
" [-p number] [-P path] [-[0-8] pattern] "
"[source files]\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,
"-d Do not update the database.\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");
/* BEGIN CSTYLED */
(void) fprintf(stderr,
" like the \"<\" command.\n");
/* END CSTYLED */
(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,
"-q Build an inverted index for quick symbol seaching.\n");
(void) fprintf(stderr,
"-s \"dir\" Look in \"dir\" for additional source files.\n");
}
static void
removeindex(void)
{
(void) fprintf(stderr,
"cscope: removed files %s and %s\n", invname, invpost);
(void) unlink(invname);
(void) unlink(invpost);
}
static void
cannotindex(void)
{
(void) fprintf(stderr,
"cscope: cannot create inverted index; ignoring -q option\n");
invertedindex = NO;
errorsfound = YES;
(void) fprintf(stderr,
"cscope: removed files %s and %s\n", newinvname, newinvpost);
(void) unlink(newinvname);
(void) unlink(newinvpost);
removeindex(); /* remove any existing index to prevent confusion */
}
void
cannotopen(char *file)
{
char msg[MSGLEN + 1];
(void) sprintf(msg, "Cannot open file %s", file);
putmsg(msg);
}
void
cannotwrite(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 */
static void
initcompress(void)
{
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 */
static void
opendatabase(void)
{
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) {
askforreturn(); /* so user sees message */
invertedindex = NO;
}
}
/* close the database */
static void
closedatabase(void)
{
(void) close(symrefs);
if (invertedindex == YES) {
invclose(&invcontrol);
nsrcoffset = 0;
npostings = 0;
}
}
/* rebuild the database */
void
rebuild(void)
{
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 */
static void
build(void)
{
int i;
FILE *oldrefs; /* old cross-reference file */
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 oldtruncatesyms = NO;
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;
case 'q': /* quick search */
oldinvertedindex = YES;
(void) fscanf(oldrefs,
"%ld", &totalterms);
break;
case 'T':
/* truncate symbols to 8 characters */
oldtruncatesyms = YES;
break;
}
}
/* check the old and new option settings */
if (oldcompress != compress ||
oldtruncatesyms != truncatesyms) {
(void) fprintf(stderr,
"cscope: -c or -T option mismatch between "
"command line and old symbol database\n");
goto force;
}
if (oldinvertedindex != invertedindex) {
(void) fprintf(stderr,
"cscope: -q option mismatch between "
"command line and old symbol database\n");
if (invertedindex == NO) {
removeindex();
}
goto outofdate;
}
/* seek to the trailer */
if (fscanf(oldrefs, "%ld", &traileroffset) != 1 ||
fseek(oldrefs, traileroffset, 0) == -1) {
(void) fprintf(stderr,
"cscope: incorrect symbol database file "
"format\n");
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 ||
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,
"cscope: converting to new symbol database file "
"format\n");
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, "cscope: building symbol database\n");
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 = 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;
} else if (vpstat(file, &statstruct) == 0 &&
statstruct.st_mtime > reftime) {
/* if this file was modified */
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 built, 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 = myrealloc(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 */
}
/* create the inverted index if requested */
if (invertedindex == YES) {
char sortcommand[PATHLEN + 1];
if (fflush(postings) == EOF) {
cannotwrite(temp1);
/* NOTREACHED */
}
(void) fstat(fileno(postings), &statstruct);
(void) fprintf(stderr,
"cscope: building symbol index: temporary file size is "
"%ld bytes\n", statstruct.st_size);
(void) fclose(postings);
/*
* sort -T is broken until it is fixed we don't have too much choice
*/
/*
* (void) sprintf(sortcommand, "sort -y -T %s %s", tmpdir, temp1);
*/
(void) sprintf(sortcommand, "LC_ALL=C sort %s", temp1);
if ((postings = popen(sortcommand, "r")) == NULL) {
(void) fprintf(stderr,
"cscope: cannot open pipe to sort command\n");
cannotindex();
} else {
if ((totalterms = invmake(newinvname, newinvpost,
postings)) > 0) {
movefile(newinvname, invname);
movefile(newinvpost, invpost);
} else {
cannotindex();
}
(void) pclose(postings);
}
(void) unlink(temp1);
(void) free(srcoffset);
(void) fprintf(stderr,
"cscope: index has %ld references to %ld symbols\n",
npostings, totalterms);
}
/* 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 */
static int
compare(const void *s1, const void *s2)
{
return (strcmp((char *)s1, (char *)s2));
}
/* get the next file name in the old cross-reference */
static char *
getoldfile(void)
{
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
*/
static void
putheader(char *dir)
{
dboffset = fprintf(newrefs, "cscope %d %s", FILEVERSION, dir);
if (compress == NO) {
dboffset += fprintf(newrefs, " -c");
}
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 (truncatesyms == YES) {
dboffset += fprintf(newrefs, " -T");
}
dbfprintf(newrefs, " %.10ld\n", traileroffset);
}
/* put the name list into the cross-reference file */
static void
putlist(char **names, int count)
{
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 */
static BOOL
samelist(FILE *oldrefs, char **names, int count)
{
char oldname[PATHLEN + 1]; /* name in old cross-reference */
int oldcount;
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 */
static void
skiplist(FILE *oldrefs)
{
int i;
if (fscanf(oldrefs, "%d", &i) != 1) {
(void) fprintf(stderr,
"cscope: cannot read list size from file %s\n", reffile);
exit(1);
}
while (--i >= 0) {
if (fscanf(oldrefs, "%*s") != 0) {
(void) fprintf(stderr,
"cscope: cannot read list name from file %s\n",
reffile);
exit(1);
}
}
}
/* copy this file's symbol data */
static void
copydata(void)
{
char symbol[PATLEN + 1];
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 */
static void
copyinverted(void)
{
char *cp;
int c;
int type; /* reference type (mark character) */
char symbol[PATLEN + 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 */
static void
putinclude(char *s)
{
dbputc(INCLUDE);
skiprefchar();
getstring(s);
incfile(s + 1, *s);
}
/* replace the old file with the new file */
static void
movefile(char *new, char *old)
{
(void) unlink(old);
if (link(new, old) == -1) {
(void) perror("cscope");
(void) fprintf(stderr,
"cscope: cannot link file %s to file %s\n", new, old);
myexit(1);
}
if (unlink(new) == -1) {
(void) perror("cscope");
(void) fprintf(stderr, "cscope: cannot unlink file %s\n", new);
errorsfound = YES;
}
}
/* enter curses mode */
void
entercurses(void)
{
incurses = YES;
(void) nonl(); /* don't translate an output \n to \n\r */
(void) cbreak(); /* single character input */
(void) noecho(); /* don't echo input characters */
(void) clear(); /* clear the screen */
initmouse(); /* initialize any mouse interface */
drawscrollbar(topline, nextline, totallines);
atfield();
}
/* exit curses mode */
void
exitcurses(void)
{
/* 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 */
static void
timedout(int sig)
{
/* if there is a child process, don't exit until it does */
if (childpid) {
closedatabase();
noacttimeout = YES;
return;
}
exitcurses();
(void) fprintf(stderr, "cscope: no activity for %d hours--exiting\n",
noacttime / 3600);
myexit(sig);
}
/* cleanup and exit */
void
myexit(int sig)
{
/* deleted layer causes multiple signals */
(void) signal(SIGHUP, SIG_IGN);
/* 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();
}
/* dump core for debugging on the quit signal */
if (sig == SIGQUIT) {
(void) abort();
}
exit(sig);
}