8322 nl: misleading-indentation
[unleashed/tickless.git] / usr / src / tools / cscope-fast / main.c
blob26b244827ad3625726111716aafa177cdb5073d1
1 /*
2 * CDDL HEADER START
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.
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.
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]
20 * CDDL HEADER END
22 /* Copyright (c) 1988 AT&T */
23 /* All Rights Reserved */
27 * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
28 * Use is subject to license terms.
31 #pragma ident "%Z%%M% %I% %E% SMI"
34 * cscope - interactive C symbol cross-reference
36 * main functions
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 */
50 #define OPTSEPS " \t" /* CSCOPEOPTION separators */
51 #define MINHOURS 4 /* minimum no activity timeout hours */
53 /* defaults for unset environment variables */
54 #define EDITOR "vi"
55 #define SHELL "sh"
56 #define TMPDIR "/tmp"
59 * note: these digraph character frequencies were calculated from possible
60 * printable digraphs in the cross-reference for the C compiler
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 */
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 */
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 */
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);
135 main(int argc, char **argv)
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;
147 /* save the command name for messages */
148 argv0 = basename(argv[0]);
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>");
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;
162 /* the first source directory is the current directory */
163 sourcedir(".");
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) {
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);
186 /* set the environment options */
187 options(envc, envv);
189 /* set the command line options */
190 options(argc, argv);
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);
197 /* if running in the foreground */
198 if (signal(SIGINT, SIG_IGN) != SIG_IGN) {
200 /* cleanup on the interrupt and quit signals */
201 (void) signal(SIGINT, myexit);
202 (void) signal(SIGQUIT, myexit);
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) {
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) {
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);
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);
231 * get the crossref file version but skip the current
232 * directory
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);
240 if (fileversion >= 8) {
242 /* override these command line options */
243 compress = YES;
244 invertedindex = NO;
246 /* see if there are options in the database */
247 for (;;) {
248 /* no -q leaves multiple blanks */
249 while ((c = getc(oldrefs)) == ' ') {
252 if (c != '-') {
253 (void) ungetc(c, oldrefs);
254 break;
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;
272 initcompress();
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);
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);
289 * read the view path for use in converting relative paths to
290 * full paths
292 * note: don't overwrite vp[n]dirs because this can cause
293 * the wrong database index files to be found in the viewpath
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);
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);
313 dbvpdirs[i] = stralloc(path);
317 /* skip the source and include directory lists */
318 skiplist(oldrefs);
319 skiplist(oldrefs);
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);
328 /* get the source file list */
329 srcfiles = mymalloc(nsrcfiles * sizeof (char *));
330 if (fileversion >= 9) {
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);
339 s = mymalloc(oldnum);
340 (void) getc(oldrefs); /* skip the newline */
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);
349 /* change newlines to nulls */
350 for (i = 0; i < nsrcfiles; ++i) {
351 srcfiles[i] = s;
352 for (++s; *s != '\n'; ++s) {
355 *s = '\0';
356 ++s;
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) {
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;
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);
387 dispcomponents = atoi(s);
390 (void) fclose(names);
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);
400 srcfiles[i] = stralloc(path);
403 (void) fclose(oldrefs);
404 } else {
405 /* get source directories from the environment */
406 if ((s = getenv("SOURCEDIRS")) != NULL) {
407 sourcedir(s);
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);
418 /* get include directories from the environment */
419 if ((s = getenv("INCLUDEDIRS")) != NULL) {
420 includedir(s);
422 /* add /usr/include to the #include directory list */
423 includedir("/usr/include");
425 /* initialize the C keyword table */
426 initsymtab();
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);
441 /* build the cross-reference */
442 initcompress();
443 build();
444 if (buildonly == YES) {
445 exit(0);
448 opendatabase();
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
455 if (noacttime) {
456 (void) signal(SIGALRM, timedout);
457 (void) alarm(noacttime);
460 * if using the line oriented user interface so cscope can be a
461 * subprocess to emacs or samuel
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);
471 if (onesearch == YES) {
472 myexit(0);
474 for (;;) {
475 char buf[PATLEN + 2];
476 if (noacttime) {
477 (void) alarm(noacttime);
479 (void) printf(">> ");
480 (void) fflush(stdout);
481 if (fgets(buf, sizeof (buf), stdin) == NULL) {
482 myexit(0);
484 /* remove any trailing newline character */
485 if (*(s = buf + strlen(buf) - 1) == '\n') {
486 *s = '\0';
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);
506 break;
508 case 'c': /* toggle caseless mode */
509 case ctrl('C'):
510 if (caseless == NO) {
511 caseless = YES;
512 } else {
513 caseless = NO;
515 egrepcaseless(caseless);
516 break;
518 case 'r': /* rebuild database cscope style */
519 case ctrl('R'):
520 freefilelist();
521 makefilelist();
522 /* FALLTHROUGH */
524 case 'R': /* rebuild database samuel style */
525 rebuild();
526 (void) putchar('\n');
527 break;
529 case 'C': /* clear file names */
530 freefilelist();
531 (void) putchar('\n');
532 break;
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);
540 (void) putchar('\n');
541 break;
543 case 'P': /* print the path to the files */
544 if (prependpath != NULL) {
545 (void) puts(prependpath);
546 } else {
547 (void) puts(currentdir);
549 break;
551 case 'q': /* quit */
552 case ctrl('D'):
553 case ctrl('Z'):
554 myexit(0);
556 default:
557 (void) fprintf(stderr,
558 "cscope: unknown command '%s'\n", buf);
559 break;
562 /* NOTREACHED */
564 /* pause before clearing the screen if there have been error messages */
565 if (errorsfound == YES) {
566 errorsfound = NO;
567 askforreturn();
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 */
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 */
590 for (;;) {
591 if (noacttime) {
592 (void) alarm(noacttime);
594 atfield(); /* move to the input field */
596 /* exit if the quit command is entered */
597 if ((c = mygetch()) == EOF || c == ctrl('D') ||
598 c == ctrl('Z')) {
599 break;
601 /* execute the commmand, updating the display if necessary */
602 if (command(c) == YES) {
603 display();
606 /* cleanup and exit */
607 myexit(0);
608 /* NOTREACHED */
609 return (0);
612 static void
613 options(int argc, char **argv)
615 char path[PATHLEN + 1]; /* file path */
616 int c;
617 char *s;
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;
627 if (strlen(s) > PATLEN) {
628 (void) fprintf(stderr,
629 "cscope: pattern too long, cannot "
630 "be > %d characters\n", PATLEN);
631 exit(1);
633 (void) strcpy(pattern, s);
634 goto nextarg;
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;
704 if (*s == '\0') {
705 (void) fprintf(stderr,
706 "%s: -%c option: missing or empty "
707 "value\n", argv0, c);
708 goto usage;
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';
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;
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;
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;
772 noacttime = c * 3600;
773 break;
775 goto nextarg;
776 default:
777 (void) fprintf(stderr,
778 "%s: unknown option: -%c\n", argv0, *s);
779 usage:
780 printusage();
781 exit(1);
784 nextarg: continue;
786 lastarg:
787 /* save the file arguments */
788 fileargc = argc;
789 fileargv = argv;
792 static void
793 printusage(void)
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");
835 static void
836 removeindex(void)
838 (void) fprintf(stderr,
839 "cscope: removed files %s and %s\n", invname, invpost);
840 (void) unlink(invname);
841 (void) unlink(invpost);
844 static void
845 cannotindex(void)
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 */
858 void
859 cannotopen(char *file)
861 char msg[MSGLEN + 1];
863 (void) sprintf(msg, "Cannot open file %s", file);
864 putmsg(msg);
867 void
868 cannotwrite(char *file)
870 char msg[MSGLEN + 1];
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 */
878 /* set up the digraph character tables for text compression */
880 static void
881 initcompress(void)
883 int i;
885 if (compress == YES) {
886 for (i = 0; i < 16; ++i) {
887 dicode1[(unsigned)(dichar1[i])] = i * 8 + 1;
889 for (i = 0; i < 8; ++i) {
890 dicode2[(unsigned)(dichar2[i])] = i + 1;
895 /* open the database */
897 static void
898 opendatabase(void)
900 if ((symrefs = vpopen(reffile, O_RDONLY)) == -1) {
901 cannotopen(reffile);
902 myexit(1);
904 blocknumber = -1; /* force next seek to read the first block */
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;
914 /* close the database */
916 static void
917 closedatabase(void)
919 (void) close(symrefs);
920 if (invertedindex == YES) {
921 invclose(&invcontrol);
922 nsrcoffset = 0;
923 npostings = 0;
927 /* rebuild the database */
929 void
930 rebuild(void)
932 closedatabase();
933 build();
934 opendatabase();
936 /* revert to the initial display */
937 if (refsfound != NULL) {
938 (void) fclose(refsfound);
939 refsfound = NULL;
941 *lastfilepath = '\0'; /* last file may have new path */
944 /* build the cross-reference */
946 static void
947 build(void)
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 */
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
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));
975 /* sort the source file names (needed for rebuilding) */
976 qsort((char *)srcfiles, (unsigned)nsrcfiles, sizeof (char *), compare);
979 * if there is an old cross-reference and its current directory
980 * matches or this is an unconditional build
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)) {
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;
996 /* see if there are options in the database */
997 for (;;) {
998 while ((c = getc(oldrefs)) == ' ') {
1000 if (c != '-') {
1001 (void) ungetc(c, oldrefs);
1002 break;
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;
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;
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();
1034 goto outofdate;
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;
1045 /* if assuming that some files have changed */
1046 if (fileschanged == YES) {
1047 goto outofdate;
1049 /* see if the view path is the same */
1050 if (fileversion >= 13 &&
1051 samelist(oldrefs, vpdirs, vpndirs) == NO) {
1052 goto outofdate;
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;
1063 * see if the list of source files is the same and
1064 * none have been changed up to the included files
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;
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);
1079 (void) fclose(oldrefs);
1080 return;
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;
1090 /* reopen the old cross-reference file for fast scanning */
1091 if ((symrefs = vpopen(reffile, O_RDONLY)) == -1) {
1092 cannotopen(reffile);
1093 myexit(1);
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 */
1101 force:
1102 reftime = 0;
1103 oldfile = NULL;
1105 /* open the new cross-reference file */
1106 if ((newrefs = fopen(newreffile, "w")) == NULL) {
1107 cannotopen(newreffile);
1108 myexit(1);
1110 if (invertedindex == YES && (postings = fopen(temp1, "w")) == NULL) {
1111 cannotopen(temp1);
1112 cannotindex();
1114 (void) fprintf(stderr, "cscope: building symbol database\n");
1115 putheader(newdir);
1116 fileversion = FILEVERSION;
1117 if (buildonly == YES && !isatty(0)) {
1118 interactive = NO;
1119 } else {
1120 initprogress();
1122 /* output the leading tab expected by crossref() */
1123 dbputc('\t');
1126 * make passes through the source file list until the last level of
1127 * included files is processed
1129 firstfile = 0;
1130 lastfile = nsrcfiles;
1131 if (invertedindex == YES) {
1132 srcoffset = mymalloc((nsrcfiles + 1) * sizeof (long));
1134 for (;;) {
1136 /* get the next source file name */
1137 for (fileindex = firstfile; fileindex < lastfile; ++fileindex) {
1138 /* display the progress about every three seconds */
1139 if (interactive == YES && fileindex % 10 == 0) {
1140 if (copied == 0) {
1141 progress("%ld files built",
1142 (long)built, 0L);
1143 } else {
1144 progress("%ld files built, %ld "
1145 "files copied", (long)built,
1146 (long)copied);
1149 /* if the old file has been deleted get the next one */
1150 file = srcfiles[fileindex];
1151 while (oldfile != NULL && strcmp(file, oldfile) > 0) {
1152 oldfile = getoldfile();
1155 * if there isn't an old database or this is
1156 * a new file
1158 if (oldfile == NULL || strcmp(file, oldfile) < 0) {
1159 crossref(file);
1160 ++built;
1161 } else if (vpstat(file, &statstruct) == 0 &&
1162 statstruct.st_mtime > reftime) {
1163 /* if this file was modified */
1164 crossref(file);
1165 ++built;
1168 * skip its old crossref so modifying the last
1169 * source file does not cause all included files
1170 * to be built. Unfortunately a new file that
1171 * is alphabetically last will cause all
1172 * included files to be built, but this is
1173 * less likely
1175 oldfile = getoldfile();
1176 } else { /* copy its cross-reference */
1177 putfilename(file);
1178 if (invertedindex == YES) {
1179 copyinverted();
1180 } else {
1181 copydata();
1183 ++copied;
1184 oldfile = getoldfile();
1187 /* see if any included files were found */
1188 if (lastfile == nsrcfiles) {
1189 break;
1191 firstfile = lastfile;
1192 lastfile = nsrcfiles;
1193 if (invertedindex == YES) {
1194 srcoffset = myrealloc(srcoffset,
1195 (nsrcfiles + 1) * sizeof (long));
1197 /* sort the included file names */
1198 qsort((char *)&srcfiles[firstfile],
1199 (unsigned)(lastfile - firstfile), sizeof (char *), compare);
1201 /* add a null file name to the trailing tab */
1202 putfilename("");
1203 dbputc('\n');
1205 /* get the file trailer offset */
1207 traileroffset = dboffset;
1210 * output the view path and source and include directory and
1211 * file lists
1213 putlist(vpdirs, vpndirs);
1214 putlist(srcdirs, nsrcdirs);
1215 putlist(incdirs, nincdirs);
1216 putlist(srcfiles, nsrcfiles);
1217 if (fflush(newrefs) == EOF) {
1218 /* rewind doesn't check for write failure */
1219 cannotwrite(newreffile);
1220 /* NOTREACHED */
1222 /* create the inverted index if requested */
1223 if (invertedindex == YES) {
1224 char sortcommand[PATHLEN + 1];
1226 if (fflush(postings) == EOF) {
1227 cannotwrite(temp1);
1228 /* NOTREACHED */
1230 (void) fstat(fileno(postings), &statstruct);
1231 (void) fprintf(stderr,
1232 "cscope: building symbol index: temporary file size is "
1233 "%ld bytes\n", statstruct.st_size);
1234 (void) fclose(postings);
1236 * sort -T is broken until it is fixed we don't have too much choice
1239 * (void) sprintf(sortcommand, "sort -y -T %s %s", tmpdir, temp1);
1241 (void) sprintf(sortcommand, "LC_ALL=C sort %s", temp1);
1242 if ((postings = popen(sortcommand, "r")) == NULL) {
1243 (void) fprintf(stderr,
1244 "cscope: cannot open pipe to sort command\n");
1245 cannotindex();
1246 } else {
1247 if ((totalterms = invmake(newinvname, newinvpost,
1248 postings)) > 0) {
1249 movefile(newinvname, invname);
1250 movefile(newinvpost, invpost);
1251 } else {
1252 cannotindex();
1254 (void) pclose(postings);
1256 (void) unlink(temp1);
1257 (void) free(srcoffset);
1258 (void) fprintf(stderr,
1259 "cscope: index has %ld references to %ld symbols\n",
1260 npostings, totalterms);
1262 /* rewrite the header with the trailer offset and final option list */
1263 rewind(newrefs);
1264 putheader(newdir);
1265 (void) fclose(newrefs);
1267 /* close the old database file */
1268 if (symrefs >= 0) {
1269 (void) close(symrefs);
1271 if (oldrefs != NULL) {
1272 (void) fclose(oldrefs);
1274 /* replace it with the new database file */
1275 movefile(newreffile, reffile);
1278 /* string comparison function for qsort */
1280 static int
1281 compare(const void *s1, const void *s2)
1283 return (strcmp((char *)s1, (char *)s2));
1286 /* get the next file name in the old cross-reference */
1288 static char *
1289 getoldfile(void)
1291 static char file[PATHLEN + 1]; /* file name in old crossref */
1293 if (blockp != NULL) {
1294 do {
1295 if (*blockp == NEWFILE) {
1296 skiprefchar();
1297 getstring(file);
1298 if (file[0] != '\0') {
1299 /* if not end-of-crossref */
1300 return (file);
1302 return (NULL);
1304 } while (scanpast('\t') != NULL);
1306 return (NULL);
1310 * output the cscope version, current directory, database format options, and
1311 * the database trailer offset
1314 static void
1315 putheader(char *dir)
1317 dboffset = fprintf(newrefs, "cscope %d %s", FILEVERSION, dir);
1318 if (compress == NO) {
1319 dboffset += fprintf(newrefs, " -c");
1321 if (invertedindex == YES) {
1322 dboffset += fprintf(newrefs, " -q %.10ld", totalterms);
1323 } else {
1325 * leave space so if the header is overwritten without -q
1326 * because writing the inverted index failed, the header is
1327 * the same length
1329 dboffset += fprintf(newrefs, " ");
1331 if (truncatesyms == YES) {
1332 dboffset += fprintf(newrefs, " -T");
1334 dbfprintf(newrefs, " %.10ld\n", traileroffset);
1337 /* put the name list into the cross-reference file */
1339 static void
1340 putlist(char **names, int count)
1342 int i, size = 0;
1344 (void) fprintf(newrefs, "%d\n", count);
1345 if (names == srcfiles) {
1347 /* calculate the string space needed */
1348 for (i = 0; i < count; ++i) {
1349 size += strlen(names[i]) + 1;
1351 (void) fprintf(newrefs, "%d\n", size);
1353 for (i = 0; i < count; ++i) {
1354 if (fputs(names[i], newrefs) == EOF ||
1355 putc('\n', newrefs) == EOF) {
1356 cannotwrite(newreffile);
1357 /* NOTREACHED */
1362 /* see if the name list is the same in the cross-reference file */
1364 static BOOL
1365 samelist(FILE *oldrefs, char **names, int count)
1367 char oldname[PATHLEN + 1]; /* name in old cross-reference */
1368 int oldcount;
1369 int i;
1371 /* see if the number of names is the same */
1372 if (fscanf(oldrefs, "%d", &oldcount) != 1 ||
1373 oldcount != count) {
1374 return (NO);
1376 /* see if the name list is the same */
1377 for (i = 0; i < count; ++i) {
1378 if (fscanf(oldrefs, "%s", oldname) != 1 ||
1379 strnotequal(oldname, names[i])) {
1380 return (NO);
1383 return (YES);
1386 /* skip the list in the cross-reference file */
1388 static void
1389 skiplist(FILE *oldrefs)
1391 int i;
1393 if (fscanf(oldrefs, "%d", &i) != 1) {
1394 (void) fprintf(stderr,
1395 "cscope: cannot read list size from file %s\n", reffile);
1396 exit(1);
1398 while (--i >= 0) {
1399 if (fscanf(oldrefs, "%*s") != 0) {
1400 (void) fprintf(stderr,
1401 "cscope: cannot read list name from file %s\n",
1402 reffile);
1403 exit(1);
1408 /* copy this file's symbol data */
1410 static void
1411 copydata(void)
1413 char symbol[PATLEN + 1];
1414 char *cp;
1416 setmark('\t');
1417 cp = blockp;
1418 for (;;) {
1419 /* copy up to the next \t */
1420 do { /* innermost loop optimized to only one test */
1421 while (*cp != '\t') {
1422 dbputc(*cp++);
1424 } while (*++cp == '\0' && (cp = readblock()) != NULL);
1425 dbputc('\t'); /* copy the tab */
1427 /* get the next character */
1428 if (*(cp + 1) == '\0') {
1429 cp = readblock();
1431 /* exit if at the end of this file's data */
1432 if (cp == NULL || *cp == NEWFILE) {
1433 break;
1435 /* look for an #included file */
1436 if (*cp == INCLUDE) {
1437 blockp = cp;
1438 putinclude(symbol);
1439 putstring(symbol);
1440 setmark('\t');
1441 cp = blockp;
1444 blockp = cp;
1447 /* copy this file's symbol data and output the inverted index postings */
1449 static void
1450 copyinverted(void)
1452 char *cp;
1453 int c;
1454 int type; /* reference type (mark character) */
1455 char symbol[PATLEN + 1];
1457 /* note: this code was expanded in-line for speed */
1458 /* while (scanpast('\n') != NULL) { */
1459 /* other macros were replaced by code using cp instead of blockp */
1460 cp = blockp;
1461 for (;;) {
1462 setmark('\n');
1463 do { /* innermost loop optimized to only one test */
1464 while (*cp != '\n') {
1465 dbputc(*cp++);
1467 } while (*++cp == '\0' && (cp = readblock()) != NULL);
1468 dbputc('\n'); /* copy the newline */
1470 /* get the next character */
1471 if (*(cp + 1) == '\0') {
1472 cp = readblock();
1474 /* exit if at the end of this file's data */
1475 if (cp == NULL) {
1476 break;
1478 switch (*cp) {
1479 case '\n':
1480 lineoffset = dboffset + 1;
1481 continue;
1482 case '\t':
1483 dbputc('\t');
1484 blockp = cp;
1485 type = getrefchar();
1486 switch (type) {
1487 case NEWFILE: /* file name */
1488 return;
1489 case INCLUDE: /* #included file */
1490 putinclude(symbol);
1491 goto output;
1493 dbputc(type);
1494 skiprefchar();
1495 getstring(symbol);
1496 goto output;
1498 c = *cp;
1499 if (c & 0200) { /* digraph char? */
1500 c = dichar1[(c & 0177) / 8];
1502 /* if this is a symbol */
1503 if (isalpha(c) || c == '_') {
1504 blockp = cp;
1505 getstring(symbol);
1506 type = ' ';
1507 output:
1508 putposting(symbol, type);
1509 putstring(symbol);
1510 if (blockp == NULL) {
1511 return;
1513 cp = blockp;
1516 blockp = cp;
1519 /* process the #included file in the old database */
1521 static void
1522 putinclude(char *s)
1524 dbputc(INCLUDE);
1525 skiprefchar();
1526 getstring(s);
1527 incfile(s + 1, *s);
1530 /* replace the old file with the new file */
1532 static void
1533 movefile(char *new, char *old)
1535 (void) unlink(old);
1536 if (link(new, old) == -1) {
1537 (void) perror("cscope");
1538 (void) fprintf(stderr,
1539 "cscope: cannot link file %s to file %s\n", new, old);
1540 myexit(1);
1542 if (unlink(new) == -1) {
1543 (void) perror("cscope");
1544 (void) fprintf(stderr, "cscope: cannot unlink file %s\n", new);
1545 errorsfound = YES;
1549 /* enter curses mode */
1551 void
1552 entercurses(void)
1554 incurses = YES;
1555 (void) nonl(); /* don't translate an output \n to \n\r */
1556 (void) cbreak(); /* single character input */
1557 (void) noecho(); /* don't echo input characters */
1558 (void) clear(); /* clear the screen */
1559 initmouse(); /* initialize any mouse interface */
1560 drawscrollbar(topline, nextline, totallines);
1561 atfield();
1564 /* exit curses mode */
1566 void
1567 exitcurses(void)
1569 /* clear the bottom line */
1570 (void) move(LINES - 1, 0);
1571 (void) clrtoeol();
1572 (void) refresh();
1574 /* exit curses and restore the terminal modes */
1575 (void) endwin();
1576 incurses = NO;
1578 /* restore the mouse */
1579 cleanupmouse();
1580 (void) fflush(stdout);
1583 /* no activity timeout occurred */
1585 static void
1586 timedout(int sig)
1588 /* if there is a child process, don't exit until it does */
1589 if (childpid) {
1590 closedatabase();
1591 noacttimeout = YES;
1592 return;
1594 exitcurses();
1595 (void) fprintf(stderr, "cscope: no activity for %d hours--exiting\n",
1596 noacttime / 3600);
1597 myexit(sig);
1600 /* cleanup and exit */
1602 void
1603 myexit(int sig)
1605 /* deleted layer causes multiple signals */
1606 (void) signal(SIGHUP, SIG_IGN);
1607 /* remove any temporary files */
1608 if (temp1[0] != '\0') {
1609 (void) unlink(temp1);
1610 (void) unlink(temp2);
1612 /* restore the terminal to its original mode */
1613 if (incurses == YES) {
1614 exitcurses();
1617 /* dump core for debugging on the quit signal */
1618 if (sig == SIGQUIT) {
1619 (void) abort();
1621 exit(sig);