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
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]
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
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 */
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 */
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 */
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) */
158 dbvpndirs
= vpndirs
; /* number of directories in database view path */
159 /* directories (including current) in database view path */
162 /* the first source directory is the current directory */
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 */
179 envv
= mymalloc(sizeof (char *));
180 s
= strtok(stralloc(s
), OPTSEPS
);
182 envv
= myrealloc(envv
, ++envc
* sizeof (char *));
183 envv
[envc
- 1] = stralloc(s
);
184 s
= strtok((char *)NULL
, OPTSEPS
);
186 /* set the environment options */
189 /* set the command line options */
192 /* create the temporary file names */
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
) {
231 * get the crossref file version but skip the current
234 if (fscanf(oldrefs
, "cscope %d %*s", &fileversion
) != 1) {
235 (void) fprintf(stderr
,
236 "cscope: cannot read file version from file %s\n",
240 if (fileversion
>= 8) {
242 /* override these command line options */
246 /* see if there are options in the database */
248 /* no -q leaves multiple blanks */
249 while ((c
= getc(oldrefs
)) == ' ') {
253 (void) ungetc(c
, oldrefs
);
256 switch (c
= getc(oldrefs
)) {
257 case 'c': /* ASCII characters only */
260 case 'q': /* quick search */
262 (void) fscanf(oldrefs
,
266 /* truncate symbols to 8 characters */
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
);
281 if (fseek(oldrefs
, traileroffset
, 0) != 0) {
282 (void) fprintf(stderr
,
283 "cscope: cannot seek to trailer in "
284 "file %s\n", reffile
);
289 * read the view path for use in converting relative paths to
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
);
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",
313 dbvpdirs
[i
] = stralloc(path
);
317 /* skip the source and include directory lists */
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
);
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
);
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
);
349 /* change newlines to nulls */
350 for (i
= 0; i
< nsrcfiles
; ++i
) {
352 for (++s
; *s
!= '\n'; ++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 &&
367 s
= path
+ 2; /* for "-Ipath" */
376 /* file path components */
378 if (*s
< '0' || *s
> '9') {
379 (void) fprintf(stderr
,
387 dispcomponents
= atoi(s
);
390 (void) fclose(names
);
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
);
400 srcfiles
[i
] = stralloc(path
);
403 (void) fclose(oldrefs
);
405 /* get source directories from the environment */
406 if ((s
= getenv("SOURCEDIRS")) != NULL
) {
409 /* make the source file list */
410 srcfiles
= mymalloc(msrcfiles
* sizeof (char *));
412 if (nsrcfiles
== 0) {
413 (void) fprintf(stderr
,
414 "cscope: no source files found\n");
418 /* get include directories from the environment */
419 if ((s
= getenv("INCLUDEDIRS")) != NULL
) {
422 /* add /usr/include to the #include directory list */
423 includedir("/usr/include");
425 /* initialize the C keyword table */
428 /* create the file name(s) used for a new cross-reference */
429 (void) strcpy(path
, reffile
);
432 (void) strcat(path
, "n");
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 */
444 if (buildonly
== YES
) {
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
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
) {
471 if (onesearch
== YES
) {
475 char buf
[PATLEN
+ 2];
477 (void) alarm(noacttime
);
479 (void) printf(">> ");
480 (void) fflush(stdout
);
481 if (fgets(buf
, sizeof (buf
), stdin
) == NULL
) {
484 /* remove any trailing newline character */
485 if (*(s
= buf
+ strlen(buf
) - 1) == '\n') {
498 case '9': /* samuel only */
500 (void) strcpy(pattern
, buf
+ 1);
502 (void) printf("cscope: %d lines\n", totallines
);
503 while ((c
= getc(refsfound
)) != EOF
) {
508 case 'c': /* toggle caseless mode */
510 if (caseless
== NO
) {
515 egrepcaseless(caseless
);
518 case 'r': /* rebuild database cscope style */
524 case 'R': /* rebuild database samuel style */
526 (void) putchar('\n');
529 case 'C': /* clear file names */
531 (void) putchar('\n');
534 case 'F': /* add a file name */
535 (void) strcpy(path
, buf
+ 1);
536 if (infilelist(path
) == NO
&&
537 vpaccess(path
, READ
) == 0) {
540 (void) putchar('\n');
543 case 'P': /* print the path to the files */
544 if (prependpath
!= NULL
) {
545 (void) puts(prependpath
);
547 (void) puts(currentdir
);
557 (void) fprintf(stderr
,
558 "cscope: unknown command '%s'\n", buf
);
564 /* pause before clearing the screen if there have been error messages */
565 if (errorsfound
== YES
) {
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 */
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 */
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') ||
601 /* execute the commmand, updating the display if necessary */
602 if (command(c
) == YES
) {
606 /* cleanup and exit */
613 options(int argc
, char **argv
)
615 char path
[PATHLEN
+ 1]; /* file path */
619 while (--argc
> 0 && (*++argv
)[0] == '-') {
620 for (s
= argv
[0] + 1; *s
!= '\0'; s
++) {
621 /* look for an input field number */
624 if (*++s
== '\0' && --argc
> 0) {
627 if (strlen(s
) > PATLEN
) {
628 (void) fprintf(stderr
,
629 "cscope: pattern too long, cannot "
630 "be > %d characters\n", PATLEN
);
633 (void) strcpy(pattern
, s
);
637 case '-': /* end of options */
641 case 'V': /* print the version number */
642 (void) fprintf(stderr
,
643 "%s: version %d%s\n", argv0
,
644 FILEVERSION
, FIXVERSION
);
647 case 'b': /* only build the cross-reference */
650 case 'c': /* ASCII characters only in crossref */
654 /* turn on caseless mode for symbol searches */
656 /* simulate egrep -i flag */
657 egrepcaseless(caseless
);
659 case 'd': /* consider crossref up-to-date */
662 case 'e': /* suppress ^E prompt between files */
672 /* display OGS book and subsystem names */
675 case 'q': /* quick search */
678 case 'r': /* display as many lines as possible */
679 returnrequired
= YES
;
681 case 'T': /* truncate symbols to 8 characters */
685 /* unconditionally build the cross-reference */
688 case 'U': /* assume some files have changed */
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 */
699 case 't': /* no activity timeout in hours */
701 if (*++s
== '\0' && --argc
> 0) {
705 (void) fprintf(stderr
,
706 "%s: -%c option: missing or empty "
707 "value\n", argv0
, c
);
712 /* alternate cross-reference file */
714 (void) strcpy(path
, s
);
715 /* System V has a 14 character limit */
717 if ((int)strlen(s
) > 11) {
720 s
= path
+ strlen(path
);
721 (void) strcpy(s
, ".in");
722 invname
= stralloc(path
);
723 (void) strcpy(s
, ".po");
724 invpost
= stralloc(path
);
727 /* symbol reference lines file */
730 case 'i': /* file containing file names */
733 case 'I': /* #include file directory */
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 "
745 dispcomponents
= atoi(s
);
747 case 'P': /* prepend path to file names */
752 /* additional source directory */
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",
766 (void) fprintf(stderr
,
767 "cscope: minimum timeout "
768 "is %d hours\n", MINHOURS
);
772 noacttime
= c
* 3600;
777 (void) fprintf(stderr
,
778 "%s: unknown option: -%c\n", argv0
, *s
);
787 /* save the file arguments */
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] "
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, "
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 "
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");
818 (void) fprintf(stderr
,
819 " like the \"<\" command.\n");
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");
838 (void) fprintf(stderr
,
839 "cscope: removed files %s and %s\n", invname
, invpost
);
840 (void) unlink(invname
);
841 (void) unlink(invpost
);
847 (void) fprintf(stderr
,
848 "cscope: cannot create inverted index; ignoring -q option\n");
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 */
859 cannotopen(char *file
)
861 char msg
[MSGLEN
+ 1];
863 (void) sprintf(msg
, "Cannot open file %s", file
);
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 */
875 myexit(1); /* calls exit(2), which closes files */
878 /* set up the digraph character tables for text compression */
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 */
900 if ((symrefs
= vpopen(reffile
, O_RDONLY
)) == -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 */
914 /* close the database */
919 (void) close(symrefs
);
920 if (invertedindex
== YES
) {
921 invclose(&invcontrol
);
927 /* rebuild the database */
936 /* revert to the initial display */
937 if (refsfound
!= NULL
) {
938 (void) fclose(refsfound
);
941 *lastfilepath
= '\0'; /* last file may have new path */
944 /* build the cross-reference */
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
;
996 /* see if there are options in the database */
998 while ((c
= getc(oldrefs
)) == ' ') {
1001 (void) ungetc(c
, oldrefs
);
1004 switch (c
= getc(oldrefs
)) {
1005 case 'c': /* ASCII characters only */
1008 case 'q': /* quick search */
1009 oldinvertedindex
= YES
;
1010 (void) fscanf(oldrefs
,
1011 "%ld", &totalterms
);
1014 /* truncate symbols to 8 characters */
1015 oldtruncatesyms
= YES
;
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");
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
) {
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 "
1045 /* if assuming that some files have changed */
1046 if (fileschanged
== YES
) {
1049 /* see if the view path is the same */
1050 if (fileversion
>= 13 &&
1051 samelist(oldrefs
, vpdirs
, vpndirs
) == NO
) {
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 */
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
) {
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
);
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 "
1090 /* reopen the old cross-reference file for fast scanning */
1091 if ((symrefs
= vpopen(reffile
, O_RDONLY
)) == -1) {
1092 cannotopen(reffile
);
1095 /* get the first file name in the old cross-reference */
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 */
1105 /* open the new cross-reference file */
1106 if ((newrefs
= fopen(newreffile
, "w")) == NULL
) {
1107 cannotopen(newreffile
);
1110 if (invertedindex
== YES
&& (postings
= fopen(temp1
, "w")) == NULL
) {
1114 (void) fprintf(stderr
, "cscope: building symbol database\n");
1116 fileversion
= FILEVERSION
;
1117 if (buildonly
== YES
&& !isatty(0)) {
1122 /* output the leading tab expected by crossref() */
1126 * make passes through the source file list until the last level of
1127 * included files is processed
1130 lastfile
= nsrcfiles
;
1131 if (invertedindex
== YES
) {
1132 srcoffset
= mymalloc((nsrcfiles
+ 1) * sizeof (long));
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) {
1141 progress("%ld files built",
1144 progress("%ld files built, %ld "
1145 "files copied", (long)built
,
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
1158 if (oldfile
== NULL
|| strcmp(file
, oldfile
) < 0) {
1161 } else if (vpstat(file
, &statstruct
) == 0 &&
1162 statstruct
.st_mtime
> reftime
) {
1163 /* if this file was modified */
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
1175 oldfile
= getoldfile();
1176 } else { /* copy its cross-reference */
1178 if (invertedindex
== YES
) {
1184 oldfile
= getoldfile();
1187 /* see if any included files were found */
1188 if (lastfile
== nsrcfiles
) {
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 */
1205 /* get the file trailer offset */
1207 traileroffset
= dboffset
;
1210 * output the view path and source and include directory and
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
);
1222 /* create the inverted index if requested */
1223 if (invertedindex
== YES
) {
1224 char sortcommand
[PATHLEN
+ 1];
1226 if (fflush(postings
) == EOF
) {
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");
1247 if ((totalterms
= invmake(newinvname
, newinvpost
,
1249 movefile(newinvname
, invname
);
1250 movefile(newinvpost
, invpost
);
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 */
1265 (void) fclose(newrefs
);
1267 /* close the old database file */
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 */
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 */
1291 static char file
[PATHLEN
+ 1]; /* file name in old crossref */
1293 if (blockp
!= NULL
) {
1295 if (*blockp
== NEWFILE
) {
1298 if (file
[0] != '\0') {
1299 /* if not end-of-crossref */
1304 } while (scanpast('\t') != NULL
);
1310 * output the cscope version, current directory, database format options, and
1311 * the database trailer offset
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
);
1325 * leave space so if the header is overwritten without -q
1326 * because writing the inverted index failed, the header is
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 */
1340 putlist(char **names
, int count
)
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
);
1362 /* see if the name list is the same in the cross-reference file */
1365 samelist(FILE *oldrefs
, char **names
, int count
)
1367 char oldname
[PATHLEN
+ 1]; /* name in old cross-reference */
1371 /* see if the number of names is the same */
1372 if (fscanf(oldrefs
, "%d", &oldcount
) != 1 ||
1373 oldcount
!= count
) {
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
])) {
1386 /* skip the list in the cross-reference file */
1389 skiplist(FILE *oldrefs
)
1393 if (fscanf(oldrefs
, "%d", &i
) != 1) {
1394 (void) fprintf(stderr
,
1395 "cscope: cannot read list size from file %s\n", reffile
);
1399 if (fscanf(oldrefs
, "%*s") != 0) {
1400 (void) fprintf(stderr
,
1401 "cscope: cannot read list name from file %s\n",
1408 /* copy this file's symbol data */
1413 char symbol
[PATLEN
+ 1];
1419 /* copy up to the next \t */
1420 do { /* innermost loop optimized to only one test */
1421 while (*cp
!= '\t') {
1424 } while (*++cp
== '\0' && (cp
= readblock()) != NULL
);
1425 dbputc('\t'); /* copy the tab */
1427 /* get the next character */
1428 if (*(cp
+ 1) == '\0') {
1431 /* exit if at the end of this file's data */
1432 if (cp
== NULL
|| *cp
== NEWFILE
) {
1435 /* look for an #included file */
1436 if (*cp
== INCLUDE
) {
1447 /* copy this file's symbol data and output the inverted index postings */
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 */
1463 do { /* innermost loop optimized to only one test */
1464 while (*cp
!= '\n') {
1467 } while (*++cp
== '\0' && (cp
= readblock()) != NULL
);
1468 dbputc('\n'); /* copy the newline */
1470 /* get the next character */
1471 if (*(cp
+ 1) == '\0') {
1474 /* exit if at the end of this file's data */
1480 lineoffset
= dboffset
+ 1;
1485 type
= getrefchar();
1487 case NEWFILE
: /* file name */
1489 case INCLUDE
: /* #included file */
1499 if (c
& 0200) { /* digraph char? */
1500 c
= dichar1
[(c
& 0177) / 8];
1502 /* if this is a symbol */
1503 if (isalpha(c
) || c
== '_') {
1508 putposting(symbol
, type
);
1510 if (blockp
== NULL
) {
1519 /* process the #included file in the old database */
1530 /* replace the old file with the new file */
1533 movefile(char *new, char *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
);
1542 if (unlink(new) == -1) {
1543 (void) perror("cscope");
1544 (void) fprintf(stderr
, "cscope: cannot unlink file %s\n", new);
1549 /* enter curses mode */
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
);
1564 /* exit curses mode */
1569 /* clear the bottom line */
1570 (void) move(LINES
- 1, 0);
1574 /* exit curses and restore the terminal modes */
1578 /* restore the mouse */
1580 (void) fflush(stdout
);
1583 /* no activity timeout occurred */
1588 /* if there is a child process, don't exit until it does */
1595 (void) fprintf(stderr
, "cscope: no activity for %d hours--exiting\n",
1600 /* cleanup and exit */
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
) {
1617 /* dump core for debugging on the quit signal */
1618 if (sig
== SIGQUIT
) {