590 lines
17 KiB
C
590 lines
17 KiB
C
/*===========================================================================
|
|
Copyright (c) 1998-2000, The Santa Cruz Operation
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
*Redistributions of source code must retain the above copyright notice,
|
|
this list of conditions and the following disclaimer.
|
|
|
|
*Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
*Neither name of The Santa Cruz Operation nor the names of its contributors
|
|
may be used to endorse or promote products derived from this software
|
|
without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
|
|
IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT falseT LIMITED TO,
|
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT falseT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION)
|
|
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
DAMAGE.
|
|
=========================================================================*/
|
|
|
|
|
|
/* cscope - interactive C symbol cross-reference
|
|
*
|
|
* main functions
|
|
*/
|
|
|
|
#include "global.h"
|
|
|
|
#include "build.h"
|
|
#include "vp.h"
|
|
#include "version.h" /* FILEVERSION and FIXVERSION */
|
|
#include "scanner.h"
|
|
|
|
#include <stdlib.h> /* atoi */
|
|
#include <ncurses.h>
|
|
#include <sys/types.h> /* needed by stat.h */
|
|
#include <sys/stat.h> /* stat */
|
|
#include <signal.h>
|
|
#include <getopt.h>
|
|
|
|
/* defaults for unset environment variables */
|
|
#define EDITOR "vi"
|
|
#define HOME "/" /* no $HOME --> use root directory */
|
|
#define SHELL "sh"
|
|
#define LINEFLAG "+%s" /* default: used by vi and emacs */
|
|
#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, *shell, *lineflag; /* environment variables */
|
|
char *home; /* Home directory */
|
|
bool lineflagafterfile;
|
|
char *argv0; /* command name */
|
|
bool compress = true; /* compress the characters in the crossref */
|
|
bool dbtruncated; /* database symbols are truncated to 8 chars */
|
|
int dispcomponents = 1; /* file path components to display */
|
|
bool editallprompt = true; /* prompt between editing files */
|
|
unsigned int fileargc; /* file argument count */
|
|
char **fileargv; /* file argument values */
|
|
int fileversion; /* cross-reference file version */
|
|
bool incurses = false; /* in curses */
|
|
bool invertedindex; /* the database has an inverted index */
|
|
bool isuptodate; /* consider the crossref up-to-date */
|
|
bool kernelmode; /* don't use DFLT_INCDIR - bad for kernels */
|
|
bool linemode = false; /* use line oriented user interface */
|
|
bool verbosemode = false; /* print extra information on line mode */
|
|
bool recurse_dir = false; /* recurse dirs when searching for src files */
|
|
char *namefile; /* file of file names */
|
|
bool ogs = false; /* display OGS book and subsystem names */
|
|
char *prependpath; /* prepend path to file names */
|
|
FILE *refsfound; /* references found file */
|
|
char temp1[PATHLEN + 1]; /* temporary file name */
|
|
char temp2[PATHLEN + 1]; /* temporary file name */
|
|
char tempdirpv[PATHLEN + 1]; /* private temp directory */
|
|
long totalterms; /* total inverted index terms */
|
|
bool trun_syms; /* truncate symbols to 8 characters */
|
|
char tempstring[TEMPSTRING_LEN + 1]; /* use this as a buffer, instead of 'yytext',
|
|
* which had better be left alone */
|
|
char *tmpdir; /* temporary directory */
|
|
|
|
static char path[PATHLEN + 1]; /* file path */
|
|
|
|
/* Internal prototypes: */
|
|
static void skiplist(FILE *oldrefs);
|
|
static void initcompress(void);
|
|
static inline void readenv(void);
|
|
static inline void linemode_event_loop(void);
|
|
static inline void screenmode_event_loop(void);
|
|
|
|
|
|
|
|
static inline void siginit(void) {
|
|
/* if running in the foreground */
|
|
if(signal(SIGINT, SIG_IGN) != SIG_IGN) {
|
|
/* cleanup on the interrupt and quit signals */
|
|
signal(SIGINT, myexit);
|
|
signal(SIGQUIT, myexit);
|
|
}
|
|
/* cleanup on the hangup signal */
|
|
signal(SIGHUP, myexit);
|
|
|
|
/* ditto the TERM signal */
|
|
signal(SIGTERM, myexit);
|
|
|
|
/* ignore PIPE signal, so myexit() will have a chance to clean up in
|
|
* linemode, while in curses mode the "|" command can cause a pipe signal
|
|
* too
|
|
*/
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
//if(linemode == false) { signal(SIGWINCH, redisplay); }
|
|
}
|
|
|
|
void cannotopen(const char *const file) {
|
|
posterr("Cannot open file %s", file);
|
|
}
|
|
|
|
/* FIXME MTE - should use postfatal here */
|
|
void cannotwrite(const char *const file) {
|
|
char msg[MSGLEN + 1];
|
|
|
|
snprintf(msg, sizeof(msg), "Removed file %s because write failed", file);
|
|
|
|
myperror(msg); /* display the reason */
|
|
|
|
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 == true) {
|
|
for(i = 0; i < 16; ++i) {
|
|
dicode1[(unsigned char)(dichar1[i])] = i * 8 + 1;
|
|
}
|
|
for(i = 0; i < 8; ++i) {
|
|
dicode2[(unsigned char)(dichar2[i])] = i + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* skip the list in the cross-reference file */
|
|
|
|
static void skiplist(FILE *oldrefs) {
|
|
int i;
|
|
|
|
if(fscanf(oldrefs, "%d", &i) != 1) {
|
|
postfatal(PROGRAM_NAME ": cannot read list size from file %s\n", reffile);
|
|
/* NOTREACHED */
|
|
}
|
|
while(--i >= 0) {
|
|
if(fscanf(oldrefs, "%*s") != 0) {
|
|
postfatal(PROGRAM_NAME ": cannot read list name from file %s\n", reffile);
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* cleanup and exit */
|
|
void myexit(int sig) {
|
|
/* Close file before unlinking it. DOS absolutely needs it */
|
|
if(refsfound != NULL) { fclose(refsfound); }
|
|
|
|
/* remove any temporary files */
|
|
if(temp1[0] != '\0') {
|
|
unlink(temp1);
|
|
unlink(temp2);
|
|
rmdir(tempdirpv);
|
|
}
|
|
/* restore the terminal to its original mode */
|
|
if(incurses == true) { exitcurses(); }
|
|
/* dump core for debugging on the quit signal */
|
|
if(sig == SIGQUIT) { abort(); }
|
|
/* HBB 20000421: be nice: free allocated data */
|
|
freefilelist();
|
|
freeinclist();
|
|
freesrclist();
|
|
freecrossref();
|
|
free_newbuildfiles();
|
|
|
|
if(remove_symfile_onexit == true) {
|
|
unlink(reffile);
|
|
unlink(invname);
|
|
unlink(invpost);
|
|
}
|
|
|
|
exit(sig);
|
|
}
|
|
|
|
static inline void readenv(void) {
|
|
editor = mygetenv("EDITOR", EDITOR);
|
|
editor = mygetenv("VIEWER", editor); /* use viewer if set */
|
|
editor = mygetenv("CSCOPE_EDITOR", editor); /* has last word */
|
|
home = mygetenv("HOME", HOME);
|
|
shell = mygetenv("SHELL", SHELL);
|
|
lineflag = mygetenv("CSCOPE_LINEFLAG", LINEFLAG);
|
|
lineflagafterfile = getenv("CSCOPE_LINEFLAG_AFTER_FILE") ? 1 : 0;
|
|
tmpdir = mygetenv("TMPDIR", TMPDIR);
|
|
}
|
|
|
|
static inline void linemode_event_loop(void) {
|
|
int c;
|
|
|
|
if(*input_line != '\0') { /* do any optional search */
|
|
if(search(input_line) == true) {
|
|
/* print the total number of lines in
|
|
* verbose mode */
|
|
if(verbosemode == true) printf(PROGRAM_NAME ": %d lines\n", totallines);
|
|
|
|
while((c = getc(refsfound)) != EOF)
|
|
putchar(c);
|
|
}
|
|
}
|
|
if(onesearch == true) {
|
|
myexit(0);
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
for(char *s;;) {
|
|
char buf[PATLEN + 2];
|
|
|
|
printf(">> ");
|
|
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';
|
|
strcpy(input_line, buf + 1);
|
|
if(search(input_line) == false) {
|
|
printf("Unable to search database\n");
|
|
} else {
|
|
printf("cscope: %d lines\n", totallines);
|
|
while((c = getc(refsfound)) != EOF) {
|
|
putchar(c);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'c': /* toggle caseless mode */
|
|
case ctrl('C'):
|
|
if(caseless == false) {
|
|
caseless = true;
|
|
} else {
|
|
caseless = false;
|
|
}
|
|
egrepcaseless(caseless);
|
|
break;
|
|
|
|
case 'r': /* rebuild database cscope style */
|
|
case ctrl('R'):
|
|
freefilelist();
|
|
makefilelist();
|
|
/* FALLTHROUGH */
|
|
|
|
case 'R': /* rebuild database samuel style */
|
|
rebuild();
|
|
putchar('\n');
|
|
break;
|
|
|
|
case 'C': /* clear file names */
|
|
freefilelist();
|
|
putchar('\n');
|
|
break;
|
|
|
|
case 'F': /* add a file name */
|
|
strcpy(path, buf + 1);
|
|
if(infilelist(path) == false && (s = inviewpath(path)) != NULL) {
|
|
addsrcfile(s);
|
|
}
|
|
putchar('\n');
|
|
break;
|
|
|
|
case 'q': /* quit */
|
|
case ctrl('D'):
|
|
case ctrl('Z'):
|
|
myexit(0);
|
|
/* NOTREACHED */
|
|
break;
|
|
default:
|
|
fprintf(stderr, PROGRAM_NAME ": unknown command '%s'\n", buf);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void screenmode_event_loop(void) {
|
|
for(;;) {
|
|
display();
|
|
handle_input(wgetch(stdscr)); // NOTE: getch() does not return KEY_* codes
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
FILE *names; /* name file pointer */
|
|
int oldnum; /* number in old cross-ref */
|
|
FILE *oldrefs; /* old cross-reference file */
|
|
char *s;
|
|
unsigned int i;
|
|
pid_t pid;
|
|
struct stat stat_buf;
|
|
mode_t orig_umask;
|
|
|
|
yyin = stdin;
|
|
yyout = stdout;
|
|
/* save the command name for messages */
|
|
argv0 = argv[0];
|
|
|
|
/* set the options */
|
|
argv = parse_options(&argc, argv);
|
|
|
|
/* read the environment */
|
|
readenv();
|
|
|
|
/* XXX remove if/when clearerr() in dir.c does the right thing. */
|
|
if(namefile && strcmp(namefile, "-") == 0 && !buildonly) {
|
|
postfatal(PROGRAM_NAME ": Must use -b if file list comes from stdin\n");
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
/* make sure that tmpdir exists */
|
|
if(lstat(tmpdir, &stat_buf)) {
|
|
fprintf(stderr,
|
|
PROGRAM_NAME
|
|
": Temporary directory %s does not exist or cannot be accessed\n",
|
|
tmpdir);
|
|
fprintf(stderr,
|
|
PROGRAM_NAME
|
|
": Please create the directory or set the environment variable\n" PROGRAM_NAME
|
|
": TMPDIR to a valid directory\n");
|
|
myexit(1);
|
|
}
|
|
|
|
/* create the temporary file names */
|
|
orig_umask = umask(S_IRWXG | S_IRWXO);
|
|
pid = getpid();
|
|
snprintf(tempdirpv, sizeof(tempdirpv), "%s/" PROGRAM_NAME ".%d", tmpdir, pid);
|
|
if(mkdir(tempdirpv, S_IRWXU)) {
|
|
fprintf(stderr,
|
|
PROGRAM_NAME ": Could not create private temp dir %s\n",
|
|
tempdirpv);
|
|
myexit(1);
|
|
}
|
|
umask(orig_umask);
|
|
|
|
snprintf(temp1, sizeof(temp1), "%s/" PROGRAM_NAME ".1", tempdirpv);
|
|
snprintf(temp2, sizeof(temp2), "%s/" PROGRAM_NAME ".2", tempdirpv);
|
|
|
|
/* if the database path is relative and it can't be created */
|
|
if(reffile[0] != '/' && access(".", WRITE) != 0) {
|
|
|
|
/* put it in the home directory if the database may not be
|
|
* up-to-date or doesn't exist in the relative directory,
|
|
* so a database in the current directory will be
|
|
* used instead of failing to open a non-existant database in
|
|
* the home directory
|
|
*/
|
|
snprintf(path, sizeof(path), "%s/%s", home, reffile);
|
|
if(isuptodate == false || access(path, READ) == 0) {
|
|
reffile = strdup(path);
|
|
snprintf(path, sizeof(path), "%s/%s", home, invname);
|
|
invname = strdup(path);
|
|
snprintf(path, sizeof(path), "%s/%s", home, invpost);
|
|
invpost = strdup(path);
|
|
}
|
|
}
|
|
|
|
siginit();
|
|
|
|
if(linemode == false) {
|
|
dispinit(); /* initialize display parameters */
|
|
postmsg(""); /* clear any build progress message */
|
|
display(); /* display the version number and input fields */
|
|
}
|
|
|
|
|
|
/* if the cross-reference is to be considered up-to-date */
|
|
if(isuptodate == true) {
|
|
if((oldrefs = vpfopen(reffile, "rb")) == NULL) {
|
|
postfatal(PROGRAM_NAME ": cannot open file %s\n", reffile);
|
|
/* NOTREACHED */
|
|
}
|
|
/* get the crossref file version but skip the current directory */
|
|
if(fscanf(oldrefs, PROGRAM_NAME " %d %*s", &fileversion) != 1) {
|
|
postfatal(PROGRAM_NAME ": cannot read file version from file %s\n", reffile);
|
|
/* NOTREACHED */
|
|
}
|
|
if(fileversion >= 8) {
|
|
|
|
/* override these command line options */
|
|
compress = true;
|
|
invertedindex = false;
|
|
|
|
/* see if there are options in the database */
|
|
for(int c;;) {
|
|
getc(oldrefs); /* skip the blank */
|
|
if((c = getc(oldrefs)) != '-') {
|
|
ungetc(c, oldrefs);
|
|
break;
|
|
}
|
|
switch(getc(oldrefs)) {
|
|
case 'c': /* ASCII characters only */
|
|
compress = false;
|
|
break;
|
|
case 'q': /* quick search */
|
|
invertedindex = true;
|
|
fscanf(oldrefs, "%ld", &totalterms);
|
|
break;
|
|
case 'T': /* truncate symbols to 8 characters */
|
|
dbtruncated = true;
|
|
trun_syms = true;
|
|
break;
|
|
}
|
|
}
|
|
initcompress();
|
|
seek_to_trailer(oldrefs);
|
|
}
|
|
/* skip the source and include directory lists */
|
|
skiplist(oldrefs);
|
|
skiplist(oldrefs);
|
|
|
|
/* get the number of source files */
|
|
if(fscanf(oldrefs, "%lu", &nsrcfiles) != 1) {
|
|
postfatal(PROGRAM_NAME ": cannot read source file size from file %s\n",
|
|
reffile);
|
|
/* NOTREACHED */
|
|
}
|
|
/* get the source file list */
|
|
srcfiles = malloc(nsrcfiles * sizeof(*srcfiles));
|
|
if(fileversion >= 9) {
|
|
|
|
/* allocate the string space */
|
|
if(fscanf(oldrefs, "%d", &oldnum) != 1) {
|
|
postfatal(PROGRAM_NAME ": cannot read string space size from file %s\n",
|
|
reffile);
|
|
/* NOTREACHED */
|
|
}
|
|
s = malloc(oldnum);
|
|
getc(oldrefs); /* skip the newline */
|
|
|
|
/* read the strings */
|
|
if(fread(s, oldnum, 1, oldrefs) != 1) {
|
|
postfatal(PROGRAM_NAME ": cannot read source file names from file %s\n",
|
|
reffile);
|
|
/* NOTREACHED */
|
|
}
|
|
/* 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(fgets(path, sizeof(path), names) != NULL && *path == '-') {
|
|
i = path[1];
|
|
s = path + 2; /* for "-Ipath" */
|
|
if(*s == '\0') { /* if "-I path" */
|
|
fgets(path, sizeof(path), names);
|
|
s = path;
|
|
}
|
|
switch(i) {
|
|
case 'p': /* file path components to display */
|
|
if(*s < '0' || *s > '9') {
|
|
posterr(PROGRAM_NAME
|
|
": -p option in file %s: missing or invalid numeric value\n",
|
|
namefile);
|
|
}
|
|
dispcomponents = atoi(s);
|
|
}
|
|
}
|
|
fclose(names);
|
|
}
|
|
} else {
|
|
for(i = 0; i < nsrcfiles; ++i) {
|
|
if(!fgets(path, sizeof(path), oldrefs)) {
|
|
postfatal(PROGRAM_NAME
|
|
": cannot read source file name from file %s\n",
|
|
reffile);
|
|
/* NOTREACHED */
|
|
}
|
|
srcfiles[i] = strdup(path);
|
|
}
|
|
}
|
|
fclose(oldrefs);
|
|
} else {
|
|
/* save the file arguments */
|
|
fileargc = argc;
|
|
fileargv = argv;
|
|
|
|
/* get source directories from the environment */
|
|
if((s = getenv("SOURCEDIRS")) != NULL) { sourcedir(s); }
|
|
/* make the source file list */
|
|
srcfiles = malloc(msrcfiles * sizeof(*srcfiles));
|
|
makefilelist();
|
|
if(nsrcfiles == 0) {
|
|
postfatal(PROGRAM_NAME ": no source files found\n");
|
|
/* NOTREACHED */
|
|
}
|
|
/* get include directories from the environment */
|
|
if((s = getenv("INCLUDEDIRS")) != NULL) { includedir(s); }
|
|
/* add /usr/include to the #include directory list,
|
|
but not in kernelmode... kernels tend not to use it. */
|
|
if(kernelmode == false) {
|
|
if(NULL != (s = getenv("INCDIR"))) {
|
|
includedir(s);
|
|
} else {
|
|
includedir(DFLT_INCDIR);
|
|
}
|
|
}
|
|
|
|
/* initialize the C keyword table */
|
|
initsymtab();
|
|
|
|
/* Tell build.c about the filenames to create: */
|
|
setup_build_filenames(reffile);
|
|
|
|
/* build the cross-reference */
|
|
initcompress();
|
|
if(linemode == false || verbosemode == true) { /* display if verbose as well */
|
|
postmsg("Building cross-reference...");
|
|
}
|
|
build();
|
|
if(linemode == false) { postmsg(""); /* clear any build progress message */ }
|
|
if(buildonly == true) {
|
|
myexit(0);
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
opendatabase();
|
|
|
|
/* if using the line oriented user interface so cscope can be a
|
|
subprocess to emacs or samuel */
|
|
if(linemode == true) { linemode_event_loop(); }
|
|
/* pause before clearing the screen if there have been error messages */
|
|
if(errorsfound == true) {
|
|
errorsfound = false;
|
|
askforreturn();
|
|
}
|
|
/* do any optional search */
|
|
if(*input_line != '\0') {
|
|
// command(ctrl('Y')); /* search */ // XXX: fix
|
|
} else if(reflines != NULL) {
|
|
/* read any symbol reference lines file */
|
|
readrefs(reflines);
|
|
}
|
|
|
|
screenmode_event_loop();
|
|
/* cleanup and exit */
|
|
myexit(0);
|
|
/* NOTREACHED */
|
|
return 0; /* avoid warning... */
|
|
}
|