1 /*===========================================================================
2 Copyright (c) 1998-2000, The Santa Cruz Operation
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
8 *Redistributions of source code must retain the above copyright notice,
9 this list of conditions and the following disclaimer.
11 *Redistributions in binary form must reproduce the above copyright notice,
12 this list of conditions and the following disclaimer in the documentation
13 and/or other materials provided with the distribution.
15 *Neither name of The Santa Cruz Operation nor the names of its contributors
16 may be used to endorse or promote products derived from this software
17 without specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
20 IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
31 =========================================================================*/
34 /* cscope - interactive C symbol cross-reference
43 #include "version.h" /* FILEVERSION and FIXVERSION */
47 #include <stdlib.h> /* atoi */
48 #if defined(USE_NCURSES) && !defined(RENAMED_NCURSES)
53 #include <sys/types.h> /* needed by stat.h */
54 #include <sys/stat.h> /* stat */
56 #ifdef HAVE_GETOPT_LONG
60 /* defaults for unset environment variables */
62 #define HOME "/" /* no $HOME --> use root directory */
64 #define LINEFLAG "+%s" /* default: used by vi and emacs */
67 #define DFLT_INCDIR "/usr/include"
70 static char const rcsid[] = "$Id: main.c,v 1.55 2011/07/04 13:41:17 nhorman Exp $";
72 /* note: these digraph character frequencies were calculated from possible
73 printable digraphs in the cross-reference for the C compiler */
74 char dichar1[] = " teisaprnl(of)=c"; /* 16 most frequent first chars */
75 char dichar2[] = " tnerpla"; /* 8 most frequent second chars
76 using the above as first chars */
77 char dicode1[256]; /* digraph first character code */
78 char dicode2[256]; /* digraph second character code */
80 char *editor, *shell, *lineflag; /* environment variables */
81 char *home; /* Home directory */
82 BOOL lineflagafterfile;
83 char *argv0; /* command name */
84 BOOL compress = YES; /* compress the characters in the crossref */
85 BOOL dbtruncated; /* database symbols are truncated to 8 chars */
86 int dispcomponents = 1; /* file path components to display */
88 BOOL displayversion; /* display the C Compilation System version */
90 BOOL editallprompt = YES; /* prompt between editing files */
91 unsigned int fileargc; /* file argument count */
92 char **fileargv; /* file argument values */
93 int fileversion; /* cross-reference file version */
94 BOOL incurses = NO; /* in curses */
95 BOOL invertedindex; /* the database has an inverted index */
96 BOOL isuptodate; /* consider the crossref up-to-date */
97 BOOL kernelmode; /* don't use DFLT_INCDIR - bad for kernels */
98 BOOL linemode = NO; /* use line oriented user interface */
99 BOOL verbosemode = NO; /* print extra information on line mode */
100 BOOL recurse_dir = NO; /* recurse dirs when searching for src files */
101 char *namefile; /* file of file names */
102 BOOL ogs; /* display OGS book and subsystem names */
103 char *prependpath; /* prepend path to file names */
104 FILE *refsfound; /* references found file */
105 char temp1[PATHLEN + 1]; /* temporary file name */
106 char temp2[PATHLEN + 1]; /* temporary file name */
107 char tempdirpv[PATHLEN + 1]; /* private temp directory */
108 long totalterms; /* total inverted index terms */
109 BOOL trun_syms; /* truncate symbols to 8 characters */
110 char tempstring[TEMPSTRING_LEN + 1]; /* use this as a buffer, instead of 'yytext',
111 * which had better be left alone */
112 char *tmpdir; /* temporary directory */
114 static BOOL onesearch; /* one search only in line mode */
115 static char *reflines; /* symbol reference lines file */
117 /* Internal prototypes: */
118 static void initcompress(void);
119 static void longusage(void);
120 static void skiplist(FILE *oldrefs);
121 static void usage(void);
123 #ifdef HAVE_FIXKEYPAD
127 #if defined(KEY_RESIZE) && !defined(__DJGPP__)
129 sigwinch_handler(int sig, siginfo_t *info, void *unused)
139 #ifdef HAVE_GETOPT_LONG
140 struct option lopts[] = {
141 {"help", 0, NULL, 'h'},
142 {"version", 0, NULL, 'V'},
146 char ** parse_options(int *argc, char **argv)
150 char path[PATHLEN + 1]; /* file path */
155 while ((opt = getopt_long(argcc, argv,
156 "hVbcCdeF:f:I:i:kLl0:1:2:3:4:5:6:7:8:9:P:p:qRs:TUuv",
157 lopts, &longind)) != -1) {
174 /* The input fields numbers for line mode operation */
176 if (strlen(optarg) > PATHLEN) {
178 cscope: pattern too long, cannot be > \
179 %d characters\n", PATLEN);
181 strcpy(Pattern, optarg);
183 case 'b': /* only build the cross-reference */
187 case 'c': /* ASCII characters only in crossref */
190 case 'C': /* turn on caseless mode for symbol searches */
192 egrepcaseless(caseless); /* simulate egrep -i flag */
194 case 'd': /* consider crossref up-to-date */
197 case 'e': /* suppress ^E prompt between files */
204 case 'k': /* ignore DFLT_INCDIR */
217 fprintf(stderr, "%s: version %d%s\n", argv0,
218 FILEVERSION, FIXVERSION);
221 case 'q': /* quick search */
224 case 'T': /* truncate symbols to 8 characters */
227 case 'u': /* unconditionally build the cross-reference */
230 case 'U': /* assume some files have changed */
236 case 'f': /* alternate cross-reference file */
238 if (strlen(reffile) > sizeof(path) - 3) {
240 cscope: reffile too long, cannot \
241 be > %d characters\n", sizeof(path) - 3);
244 strcpy(path, reffile);
246 s = path + strlen(path);
248 invname = my_strdup(path);
250 invpost = my_strdup(path);
253 case 'F': /* symbol reference lines file */
256 case 'i': /* file containing file names */
259 case 'I': /* #include file directory */
262 case 'p': /* file path components to display */
263 dispcomponents = atoi(optarg);
265 case 'P': /* prepend path to file names */
266 prependpath = optarg;
268 case 's': /* additional source file directory */
274 * This adjusts argv so that we only see the remaining
275 * args. Its ugly, but we need to do it so that the rest
276 * of the main routine doesn't get all confused
278 *argc = *argc - optind;
279 return &argv[optind];
284 main(int argc, char **argv)
286 FILE *names; /* name file pointer */
287 int oldnum; /* number in old cross-ref */
288 char path[PATHLEN + 1]; /* file path */
289 FILE *oldrefs; /* old cross-reference file */
294 struct stat stat_buf;
295 #if defined(KEY_RESIZE) && !defined(__DJGPP__)
296 struct sigaction winch_action;
302 /* save the command name for messages */
305 /* set the options */
306 #ifdef HAVE_GETOPT_LONG
307 argv = parse_options(&argc, argv);
309 while (--argc > 0 && (*++argv)[0] == '-') {
310 /* HBB 20030814: add GNU-style --help and --version options */
311 if (strequal(argv[0], "--help")
312 || strequal(argv[0], "-h")) {
316 if (strequal(argv[0], "--version")
317 || strequal(argv[0], "-V")) {
319 displayversion = YES;
321 fprintf(stderr, "%s: version %d%s\n", argv0,
322 FILEVERSION, FIXVERSION);
327 for (s = argv[0] + 1; *s != '\0'; s++) {
329 /* look for an input field number */
330 if (isdigit((unsigned char) *s)) {
335 if (*++s == '\0' && --argc > 0) {
338 if (strlen(s) > PATLEN) {
340 cscope: pattern too long, cannot be > %d characters\n", PATLEN);
347 case '-': /* end of options */
351 case 'b': /* only build the cross-reference */
355 case 'c': /* ASCII characters only in crossref */
358 case 'C': /* turn on caseless mode for symbol searches */
360 egrepcaseless(caseless); /* simulate egrep -i flag */
362 case 'd': /* consider crossref up-to-date */
365 case 'e': /* suppress ^E prompt between files */
368 case 'k': /* ignore DFLT_INCDIR */
380 case 'o': /* display OGS book and subsystem names */
383 case 'q': /* quick search */
386 case 'T': /* truncate symbols to 8 characters */
389 case 'u': /* unconditionally build the cross-reference */
392 case 'U': /* assume some files have changed */
398 case 'f': /* alternate cross-reference file */
399 case 'F': /* symbol reference lines file */
400 case 'i': /* file containing file names */
401 case 'I': /* #include file directory */
402 case 'p': /* file path components to display */
403 case 'P': /* prepend path to file names */
404 case 's': /* additional source file directory */
407 if (*++s == '\0' && --argc > 0) {
411 fprintf(stderr, "%s: -%c option: missing or empty value\n",
416 case 'f': /* alternate cross-reference file */
418 if (strlen(reffile) > sizeof(path) - 3) {
420 cscope: reffile too long, cannot be > %d characters\n", sizeof(path) - 3);
424 #ifdef SHORT_NAMES_ONLY
425 /* System V has a 14 character limit */
426 s = mybasename(path);
427 if (strlen(s) > 11) {
431 s = path + strlen(path);
433 invname = my_strdup(path);
435 invpost = my_strdup(path);
437 case 'F': /* symbol reference lines file */
440 case 'i': /* file containing file names */
443 case 'I': /* #include file directory */
446 case 'p': /* file path components to display */
447 if (*s < '0' || *s > '9' ) {
449 %s: -p option: missing or invalid numeric value\n",
453 dispcomponents = atoi(s);
455 case 'P': /* prepend path to file names */
458 case 's': /* additional source directory */
465 fprintf(stderr, "%s: unknown option: -%c\n", argv0,
469 fprintf(stderr, "Try the -h option for more information.\n");
471 } /* switch(option letter) */
479 /* read the environment */
480 editor = mygetenv("EDITOR", EDITOR);
481 editor = mygetenv("VIEWER", editor); /* use viewer if set */
482 editor = mygetenv("CSCOPE_EDITOR", editor); /* has last word */
483 home = mygetenv("HOME", HOME);
484 shell = mygetenv("SHELL", SHELL);
485 lineflag = mygetenv("CSCOPE_LINEFLAG", LINEFLAG);
486 lineflagafterfile = getenv("CSCOPE_LINEFLAG_AFTER_FILE") ? 1 : 0;
487 tmpdir = mygetenv("TMPDIR", TMPDIR);
489 /* XXX remove if/when clearerr() in dir.c does the right thing. */
490 if (namefile && strcmp(namefile, "-") == 0 && !buildonly) {
491 postfatal("cscope: Must use -b if file list comes from stdin\n");
495 /* make sure that tmpdir exists */
496 if (lstat (tmpdir, &stat_buf)) {
498 cscope: Temporary directory %s does not exist or cannot be accessed\n",
501 cscope: Please create the directory or set the environment variable\n\
502 cscope: TMPDIR to a valid directory\n");
506 /* create the temporary file names */
507 orig_umask = umask(S_IRWXG|S_IRWXO);
509 snprintf(tempdirpv, sizeof(tempdirpv), "%s/cscope.%d", tmpdir, pid);
510 if(mkdir(tempdirpv,S_IRWXU)) {
512 cscope: Could not create private temp dir %s\n",
518 snprintf(temp1, sizeof(temp1), "%s/cscope.1", tempdirpv);
519 snprintf(temp2, sizeof(temp2), "%s/cscope.2", tempdirpv);
521 /* if running in the foreground */
522 if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
523 /* cleanup on the interrupt and quit signals */
524 signal(SIGINT, myexit);
525 signal(SIGQUIT, myexit);
527 /* cleanup on the hangup signal */
528 signal(SIGHUP, myexit);
530 /* ditto the TERM signal */
531 signal(SIGTERM, myexit);
533 /* ignore PIPE signal, so myexit() will have a chance to clean up in
534 * linemode, while in curses mode the "|" command can cause a pipe signal
537 signal(SIGPIPE, SIG_IGN);
539 /* if the database path is relative and it can't be created */
540 if (reffile[0] != '/' && access(".", WRITE) != 0) {
542 /* put it in the home directory if the database may not be
543 * up-to-date or doesn't exist in the relative directory,
544 * so a database in the current directory will be
545 * used instead of failing to open a non-existant database in
548 snprintf(path, sizeof(path), "%s/%s", home, reffile);
549 if (isuptodate == NO || access(path, READ) == 0) {
550 reffile = my_strdup(path);
551 snprintf(path, sizeof(path), "%s/%s", home, invname);
552 invname = my_strdup(path);
553 snprintf(path, sizeof(path), "%s/%s", home, invpost);
554 invpost = my_strdup(path);
558 if (linemode == NO) {
559 signal(SIGINT, SIG_IGN); /* ignore interrupts */
561 #if defined(KEY_RESIZE) && !defined(__DJGPP__)
562 winch_action.sa_sigaction = sigwinch_handler;
563 sigemptyset(&winch_action.sa_mask);
564 winch_action.sa_flags = SA_SIGINFO;
565 sigaction(SIGWINCH,&winch_action,NULL);
568 /* initialize the curses display package */
569 initscr(); /* initialize the screen */
572 keypad(stdscr, TRUE); /* enable the keypad */
573 # ifdef HAVE_FIXKEYPAD
574 fixkeypad(); /* fix for getch() intermittently returning garbage */
576 #endif /* TERMINFO */
578 standend(); /* turn off reverse video */
580 dispinit(); /* initialize display parameters */
581 setfield(); /* set the initial cursor position */
582 clearmsg(); /* clear any build progress message */
583 display(); /* display the version number and input fields */
587 /* if the cross-reference is to be considered up-to-date */
588 if (isuptodate == YES) {
589 if ((oldrefs = vpfopen(reffile, "rb")) == NULL) {
590 postfatal("cscope: cannot open file %s\n", reffile);
593 /* get the crossref file version but skip the current directory */
594 if (fscanf(oldrefs, "cscope %d %*s", &fileversion) != 1) {
595 postfatal("cscope: cannot read file version from file %s\n",
599 if (fileversion >= 8) {
601 /* override these command line options */
605 /* see if there are options in the database */
607 getc(oldrefs); /* skip the blank */
608 if ((c = getc(oldrefs)) != '-') {
612 switch (getc(oldrefs)) {
613 case 'c': /* ASCII characters only */
616 case 'q': /* quick search */
618 fscanf(oldrefs, "%ld", &totalterms);
620 case 'T': /* truncate symbols to 8 characters */
627 seek_to_trailer(oldrefs);
629 /* skip the source and include directory lists */
633 /* get the number of source files */
634 if (fscanf(oldrefs, "%lu", &nsrcfiles) != 1) {
636 cscope: cannot read source file size from file %s\n", reffile);
639 /* get the source file list */
640 srcfiles = mymalloc(nsrcfiles * sizeof(char *));
641 if (fileversion >= 9) {
643 /* allocate the string space */
644 if (fscanf(oldrefs, "%d", &oldnum) != 1) {
646 cscope: cannot read string space size from file %s\n", reffile);
649 s = mymalloc(oldnum);
650 getc(oldrefs); /* skip the newline */
652 /* read the strings */
653 if (fread(s, oldnum, 1, oldrefs) != 1) {
655 cscope: cannot read source file names from file %s\n", reffile);
658 /* change newlines to nulls */
659 for (i = 0; i < nsrcfiles; ++i) {
661 for (++s; *s != '\n'; ++s) {
667 /* if there is a file of source file names */
668 if ((namefile != NULL && (names = vpfopen(namefile, "r")) != NULL)
669 || (names = vpfopen(NAMEFILE, "r")) != NULL) {
671 /* read any -p option from it */
672 while (fgets(path, sizeof(path), names) != NULL && *path == '-') {
674 s = path + 2; /* for "-Ipath" */
675 if (*s == '\0') { /* if "-I path" */
676 fgets(path, sizeof(path), names);
680 case 'p': /* file path components to display */
681 if (*s < '0' || *s > '9') {
682 posterr("cscope: -p option in file %s: missing or invalid numeric value\n", namefile);
685 dispcomponents = atoi(s);
691 for (i = 0; i < nsrcfiles; ++i) {
692 if (!fgets(path, sizeof(path), oldrefs) ) {
694 cscope: cannot read source file name from file %s\n",
698 srcfiles[i] = my_strdup(path);
703 /* save the file arguments */
707 /* get source directories from the environment */
708 if ((s = getenv("SOURCEDIRS")) != NULL) {
711 /* make the source file list */
712 srcfiles = mymalloc(msrcfiles * sizeof(char *));
714 if (nsrcfiles == 0) {
715 postfatal("cscope: no source files found\n");
718 /* get include directories from the environment */
719 if ((s = getenv("INCLUDEDIRS")) != NULL) {
722 /* add /usr/include to the #include directory list,
723 but not in kernelmode... kernels tend not to use it. */
724 if (kernelmode == NO) {
725 if (NULL != (s = getenv("INCDIR"))) {
728 includedir(DFLT_INCDIR);
732 /* initialize the C keyword table */
735 /* Tell build.c about the filenames to create: */
736 setup_build_filenames(reffile);
738 /* build the cross-reference */
740 if (linemode == NO || verbosemode == YES) /* display if verbose as well */
741 postmsg("Building cross-reference...");
744 clearmsg(); /* clear any build progress message */
745 if (buildonly == YES) {
751 /* if using the line oriented user interface so cscope can be a
752 subprocess to emacs or samuel */
753 if (linemode == YES) {
754 if (*Pattern != '\0') { /* do any optional search */
755 if (search() == YES) {
756 /* print the total number of lines in
758 if (verbosemode == YES)
759 printf("cscope: %d lines\n",
762 while ((c = getc(refsfound)) != EOF)
766 if (onesearch == YES)
770 char buf[PATLEN + 2];
774 if (fgets(buf, sizeof(buf), stdin) == NULL) {
777 /* remove any trailing newline character */
778 if (*(s = buf + strlen(buf) - 1) == '\n') {
791 case '9': /* samuel only */
793 strcpy(Pattern, buf + 1);
795 printf("cscope: %d lines\n", totallines);
796 while ((c = getc(refsfound)) != EOF) {
801 case 'c': /* toggle caseless mode */
803 if (caseless == NO) {
808 egrepcaseless(caseless);
811 case 'r': /* rebuild database cscope style */
817 case 'R': /* rebuild database samuel style */
822 case 'C': /* clear file names */
827 case 'F': /* add a file name */
828 strcpy(path, buf + 1);
829 if (infilelist(path) == NO &&
830 (s = inviewpath(path)) != NULL) {
842 fprintf(stderr, "cscope: unknown command '%s'\n", buf);
848 /* pause before clearing the screen if there have been error messages */
849 if (errorsfound == YES) {
853 /* do any optional search */
854 if (*Pattern != '\0') {
855 atfield(); /* move to the input field */
856 command(ctrl('Y')); /* search */
857 } else if (reflines != NULL) {
858 /* read any symbol reference lines file */
861 display(); /* update the display */
865 atfield(); /* move to the input field */
867 /* exit if the quit command is entered */
868 if ((c = mygetch()) == EOF || c == ctrl('D')) {
871 if (c == ctrl('Z')) {
879 /* execute the commmand, updating the display if necessary */
880 if (command(c) == YES) {
885 move(displine[curdispline], 0);
889 /* cleanup and exit */
892 return 0; /* avoid warning... */
896 cannotopen(char *file)
898 posterr("Cannot open file %s", file);
901 /* FIXME MTE - should use postfatal here */
903 cannotwrite(char *file)
905 char msg[MSGLEN + 1];
907 snprintf(msg, sizeof(msg), "Removed file %s because write failed", file);
909 myperror(msg); /* display the reason */
912 myexit(1); /* calls exit(2), which closes files */
916 /* set up the digraph character tables for text compression */
922 if (compress == YES) {
923 for (i = 0; i < 16; ++i) {
924 dicode1[(unsigned char) (dichar1[i])] = i * 8 + 1;
926 for (i = 0; i < 8; ++i) {
927 dicode2[(unsigned char) (dichar2[i])] = i + 1;
932 /* skip the list in the cross-reference file */
935 skiplist(FILE *oldrefs)
939 if (fscanf(oldrefs, "%d", &i) != 1) {
940 postfatal("cscope: cannot read list size from file %s\n", reffile);
944 if (fscanf(oldrefs, "%*s") != 0) {
945 postfatal("cscope: cannot read list name from file %s\n", reffile);
952 /* enter curses mode */
957 #ifndef __MSDOS__ /* HBB 20010313 */
958 nonl(); /* don't translate an output \n to \n\r */
960 raw(); /* single character input */
961 noecho(); /* don't echo input characters */
962 clear(); /* clear the screen */
963 mouseinit(); /* initialize any mouse interface */
964 drawscrollbar(topline, nextline);
968 /* exit curses mode */
972 /* clear the bottom line */
977 /* exit curses and restore the terminal modes */
981 /* restore the mouse */
987 /* normal usage message */
991 fprintf(stderr, "Usage: cscope [-bcCdehklLqRTuUvV] [-f file] [-F file] [-i file] [-I dir] [-s dir]\n");
992 fprintf(stderr, " [-p number] [-P path] [-[0-8] pattern] [source files]\n");
996 /* long usage message */
1003 -b Build the cross-reference only.\n\
1004 -C Ignore letter case when searching.\n\
1005 -c Use only ASCII characters in the cross-ref file (don't compress).\n\
1006 -d Do not update the cross-reference.\n\
1007 -e Suppress the <Ctrl>-e command prompt between files.\n\
1008 -F symfile Read symbol reference lines from symfile.\n\
1009 -f reffile Use reffile as cross-ref file name instead of %s.\n",
1012 -h This help screen.\n\
1013 -I incdir Look in incdir for any #include files.\n\
1014 -i namefile Browse through files listed in namefile, instead of %s\n",
1017 -k Kernel Mode - don't use %s for #include files.\n",
1020 -L Do a single search with line-oriented output.\n\
1021 -l Line-oriented interface.\n\
1022 -num pattern Go to input field num (counting from 0) and find pattern.\n\
1023 -P path Prepend path to relative file names in pre-built cross-ref file.\n\
1024 -p n Display the last n file path components.\n\
1025 -q Build an inverted index for quick symbol searching.\n\
1026 -R Recurse directories for files.\n\
1027 -s dir Look in dir for additional source files.\n\
1028 -T Use only the first eight characters to match against C symbols.\n\
1029 -U Check file time stamps.\n\
1030 -u Unconditionally build the cross-reference file.\n\
1031 -v Be more verbose in line mode.\n\
1032 -V Print the version number.\n\
1034 Please see the manpage for more information.\n",
1038 /* cleanup and exit */
1043 /* HBB 20010313; close file before unlinking it. Unix may not care
1044 * about that, but DOS absolutely needs it */
1045 if (refsfound != NULL)
1048 /* remove any temporary files */
1049 if (temp1[0] != '\0') {
1054 /* restore the terminal to its original mode */
1055 if (incurses == YES) {
1058 /* dump core for debugging on the quit signal */
1059 if (sig == SIGQUIT) {
1062 /* HBB 20000421: be nice: free allocated data */
1067 free_newbuildfiles();