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 2004 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 or text cross-reference
46 * most of these functions have been optimized so their innermost loops have
47 * only one test for the desired character by putting the char and
48 * an end-of-block marker (\0) at the end of the disk block buffer.
49 * When the inner loop exits on the char, an outer loop will see if
50 * the char is followed by a \0. If so, it will read the next block
51 * and restart the inner loop.
54 char block
[BUFSIZ
+ 2]; /* leave room for end-of-block mark */
55 int blocklen
; /* length of disk block read */
56 char blockmark
; /* mark character to be searched for */
57 long blocknumber
; /* block number */
58 char *blockp
; /* pointer to current char in block */
59 char lastfilepath
[PATHLEN
+ 1]; /* last file that full path was */
62 static char cpattern
[PATLEN
+ 1]; /* compressed pattern */
63 static long lastfcnoffset
; /* last function name offset */
64 static long postingsfound
; /* retrieved number of postings */
65 static char *regexp
; /* regular expression */
66 static POSTING
*postingp
; /* retrieved posting set pointer */
67 static long searchcount
; /* count of files searched */
68 static long starttime
; /* start time for progress messages */
70 static POSTING
*getposting(void);
71 static void putsource(FILE *output
);
72 static void putref(char *file
, char *function
);
73 static void findcalledbysub(char *file
);
74 static void findterm(void);
75 static void fileprogress(void);
76 static void putpostingref(POSTING
*p
);
77 static void putline(FILE *output
);
78 static char *strtolower(char *s
);
79 static char *filepath(char *file
);
81 /* find the symbol in the cross-reference */
86 char file
[PATHLEN
+ 1]; /* source file name */
87 char function
[PATLEN
+ 1]; /* function name */
88 char macro
[PATLEN
+ 1]; /* macro name */
89 char symbol
[PATLEN
+ 1]; /* symbol name */
94 if (invertedindex
== YES
) {
99 while ((p
= getposting()) != NULL
) {
100 if (p
->type
!= INCLUDE
&& p
->lineoffset
!= lastline
) {
102 lastline
= p
->lineoffset
;
107 (void) scanpast('\t'); /* find the end of the header */
108 skiprefchar(); /* skip the file marker */
109 getstring(file
); /* save the file name */
111 /* a macro can be inside a function, but not vice versa */
114 /* find the next symbol */
115 /* note: this code was expanded in-line for speed */
116 /* while (scanpast('\n') != NULL) { */
117 /* other macros were replaced by code using cp instead of blockp */
121 do { /* innermost loop optimized to only one test */
122 while (*cp
!= '\n') {
125 } while (*(cp
+ 1) == '\0' && (cp
= readblock()) != NULL
);
127 /* skip the found character */
128 if (cp
!= NULL
&& *(++cp
+ 1) == '\0') {
134 /* look for a source file or function name */
137 switch (getrefchar()) {
139 case NEWFILE
: /* file name */
145 /* check for the end of the symbols */
152 case FCNEND
: /* function end */
154 goto notmatched
; /* don't match name */
156 case FCNDEF
: /* function name */
160 case DEFINE
: /* could be a macro */
161 if (fileversion
>= 10) {
170 goto notmatched
; /* don't match name */
172 case INCLUDE
: /* #include file */
173 goto notmatched
; /* don't match name */
174 default: /* other symbol */
181 /* see if this is a regular expression pattern */
182 if (regexp
!= NULL
) {
183 if (caseless
== YES
) {
186 if (*s
!= '\0' && regex(regexp
, s
) != NULL
) {
190 /* match the symbol to the text pattern */
191 else if (strequal(pattern
, s
)) {
196 /* if this is a regular expression pattern */
197 if (regexp
!= NULL
) {
199 if (c
& 0200) { /* digraph char? */
200 c
= dichar1
[(c
& 0177) / 8];
202 /* if this is a symbol */
203 if (isalpha(c
) || c
== '_') {
207 if (caseless
== YES
) {
210 /* match the symbol to the regular expression */
211 if (regex(regexp
, s
) != NULL
) {
217 /* match the character to the text pattern */
218 else if (*cp
== cpattern
[0]) {
221 /* match the rest of the symbol to the text pattern */
226 * output the file, calling function or macro,
229 if (*macro
!= '\0' && s
!= macro
) {
231 } else if (s
!= function
) {
232 putref(file
, function
);
236 if (blockp
== NULL
) {
247 /* find the function definition or #define */
252 char file
[PATHLEN
+ 1]; /* source file name */
253 char function
[PATLEN
+ 1]; /* function name */
254 char macro
[PATLEN
+ 1]; /* macro name */
255 char symbol
[PATLEN
+ 1]; /* symbol name */
258 if (invertedindex
== YES
) {
262 while ((p
= getposting()) != NULL
) {
264 case DEFINE
: /* could be a macro */
272 case GLOBALDEF
: /* other global definition */
273 case LOCALDEF
: /* other local definition */
280 /* find the next file name or definition */
282 /* a macro can be inside a function, but not vice versa */
285 while (scanpast('\t') != NULL
) {
289 skiprefchar(); /* save file name */
291 if (*file
== '\0') { /* if end of symbols */
297 case FCNEND
: /* function end */
301 case FCNDEF
: /* function name */
305 case DEFINE
: /* could be a macro */
306 if (fileversion
>= 10) {
323 case GLOBALDEF
: /* other global definition */
324 case LOCALDEF
: /* other local definition */
332 /* see if this is a regular expression pattern */
333 if (regexp
!= NULL
) {
334 if (caseless
== YES
) {
337 if (*s
!= '\0' && regex(regexp
, s
) != NULL
) {
340 } else if (strequal(pattern
, s
)) {
341 /* match the symbol to the text pattern */
344 * output the file, calling function or macro,
347 if (*macro
!= '\0' && s
!= macro
) {
349 } else if (s
!= function
) {
350 putref(file
, function
);
359 /* find all function definitions (used by samuel only) */
364 char file
[PATHLEN
+ 1]; /* source file name */
365 char function
[PATLEN
+ 1]; /* function name */
367 /* find the next file name or definition */
368 while (scanpast('\t') != NULL
) {
371 skiprefchar(); /* save file name */
373 if (*file
== '\0') { /* if end of symbols */
381 skiprefchar(); /* save function name */
384 /* output the file, function and source line */
385 putref(file
, function
);
391 /* find the functions called by this function */
396 char file
[PATHLEN
+ 1]; /* source file name */
398 if (invertedindex
== YES
) {
402 while ((p
= getposting()) != NULL
) {
404 case DEFINE
: /* could be a macro */
406 if (dbseek(p
->lineoffset
) != -1 &&
407 scanpast('\t') != NULL
) { /* skip def */
408 findcalledbysub(srcfiles
[p
->fileindex
]);
414 /* find the function definition(s) */
415 while (scanpast('\t') != NULL
) {
418 skiprefchar(); /* save file name */
420 if (*file
== '\0') { /* if end of symbols */
426 case DEFINE
: /* could be a macro */
427 if (fileversion
< 10) {
433 skiprefchar(); /* match name to pattern */
435 findcalledbysub(file
);
443 findcalledbysub(char *file
)
445 /* find the next function call or the end of this function */
446 while (scanpast('\t') != NULL
) {
448 case DEFINE
: /* #define inside a function */
449 if (fileversion
>= 10) { /* skip it */
450 while (scanpast('\t') != NULL
&&
451 *blockp
!= DEFINEEND
)
455 case FCNCALL
: /* function call */
457 /* output the file name */
458 (void) fprintf(refsfound
, "%s ", filepath(file
));
460 /* output the function name */
463 (void) putc(' ', refsfound
);
465 /* output the source line */
466 putsource(refsfound
);
469 case DEFINEEND
: /* #define end */
470 case FCNEND
: /* function end */
471 case FCNDEF
: /* function end (pre 9.5) */
472 case NEWFILE
: /* file end */
478 /* find the functions calling this function */
483 char file
[PATHLEN
+ 1]; /* source file name */
484 char function
[PATLEN
+ 1]; /* function name */
485 char macro
[PATLEN
+ 1]; /* macro name */
487 if (invertedindex
== YES
) {
491 while ((p
= getposting()) != NULL
) {
492 if (p
->type
== FCNCALL
) {
498 /* find the next file name or function definition */
499 /* a macro can be inside a function, but not vice versa */
502 while (scanpast('\t') != NULL
) {
504 case NEWFILE
: /* save file name */
507 if (*file
== '\0') { /* if end of symbols */
512 case FCNEND
: /* function end */
515 case DEFINE
: /* could be a macro */
516 if (fileversion
>= 10) {
526 case FCNDEF
: /* save calling function name */
530 case FCNCALL
: /* match function called to pattern */
533 /* output the file, calling function or */
534 /* macro, and source */
535 if (*macro
!= '\0') {
538 putref(file
, function
);
545 /* find direct assignment to, and increment and decrement of, this variable */
548 findassignments(void)
550 char file
[PATHLEN
+ 1]; /* source file name */
551 char function
[PATLEN
+ 1]; /* function name */
552 char macro
[PATLEN
+ 1]; /* macro name */
554 if (fileversion
< 13) {
555 putmsg("Database built with cscope version < 13 does not "
556 "have assignment information");
563 if (invertedindex
== YES
) {
567 while ((p
= getposting()) != NULL
) {
570 case GLOBALDEF
: /* can have initializer */
571 case LOCALDEF
: /* can have initializer */
572 case PARAMETER
: /* initial value */
578 /* find the next file name or function definition */
579 /* a macro can be inside a function, but not vice versa */
582 while (scanpast('\t') != NULL
) {
584 case NEWFILE
: /* save file name */
587 if (*file
== '\0') { /* if end of symbols */
592 case FCNEND
: /* function end */
595 case DEFINE
: /* could be a macro */
596 if (fileversion
>= 10) {
606 case FCNDEF
: /* save calling function name */
610 case ASSIGNMENT
: /* match assignment to pattern */
611 case GLOBALDEF
: /* can have initializer */
612 case LOCALDEF
: /* can have initializer */
613 case PARAMETER
: /* initial value */
616 /* output the file, calling function or */
617 /* macro, and source */
618 if (*macro
!= '\0') {
621 putref(file
, function
);
628 /* find the grep pattern in the source files */
633 char egreppat
[2 * PATLEN
];
636 /* translate egrep special characters in the regular expression */
638 for (pp
= pattern
; *pp
!= '\0'; ++pp
) {
639 if (strchr("+?|()", *pp
) != NULL
) {
646 /* search the source files */
647 return (findegreppat(egreppat
));
650 /* find this regular expression in the source files */
653 findegreppat(char *egreppat
)
657 char msg
[MSGLEN
+ 1];
659 /* compile the pattern */
660 if ((egreperror
= egrepinit(egreppat
)) == NULL
) {
662 /* search the files */
663 for (i
= 0; i
< nsrcfiles
; ++i
) {
664 char *file
= filepath(srcfiles
[i
]);
666 if (egrep(file
, refsfound
, "%s <unknown> %ld ") < 0) {
667 (void) sprintf(msg
, "Cannot open file %s",
676 /* find matching file names */
684 for (i
= 0; i
< nsrcfiles
; ++i
) {
686 if (caseless
== YES
) {
689 if (regex(regexp
, s
) != NULL
) {
690 (void) fprintf(refsfound
, "%s <unknown> 1 <unknown>\n",
691 filepath(srcfiles
[i
]));
696 /* find files #including this file */
701 char file
[PATHLEN
+ 1]; /* source file name */
703 if (invertedindex
== YES
) {
707 while ((p
= getposting()) != NULL
) {
708 if (p
->type
== INCLUDE
) {
714 /* find the next file name or function definition */
715 while (scanpast('\t') != NULL
) {
718 case NEWFILE
: /* save file name */
721 if (*file
== '\0') { /* if end of symbols */
727 case INCLUDE
: /* match function called to pattern */
729 /* skip global or local #include marker */
732 /* output the file and source line */
744 char buf
[PATLEN
+ 3];
750 /* remove trailing white space */
751 for (s
= pattern
+ strlen(pattern
) - 1; isspace(*s
); --s
) {
754 /* allow a partial match for a file name */
755 if (field
== FILENAME
|| field
== INCLUDES
) {
756 /* allow types.h to match #include <sys/types.h> */
757 if (invertedindex
== YES
&& field
== INCLUDES
&&
758 strncmp(pattern
, ".*", 2) != 0) {
759 (void) sprintf(pattern
, ".*%s", strcpy(buf
, pattern
));
761 if ((regexp
= regcmp(pattern
, (char *)NULL
)) == NULL
) {
762 return (REGCMPERROR
);
766 /* see if the pattern is a regular expression */
767 if (strpbrk(pattern
, "^.[{*+$") != NULL
) {
770 /* check for a valid C symbol */
772 if (!isalpha(*s
) && *s
!= '_') {
775 while (*++s
!= '\0') {
776 if (!isalnum(*s
) && *s
!= '_') {
781 * look for use of the -T option (truncate symbol to 8
782 * characters) on a database not built with -T
784 if (truncatesyms
== YES
&& isuptodate
== YES
&&
785 dbtruncated
== NO
&& s
- pattern
>= 8) {
786 (void) strcpy(pattern
+ 8, ".*");
790 /* if this is a regular expression or letter case is to be ignored */
791 /* or there is an inverted index */
792 if (isregexp
== YES
|| caseless
== YES
|| invertedindex
== YES
) {
794 /* remove a leading ^ */
797 (void) strcpy(newpat
, s
+ 1);
798 (void) strcpy(s
, newpat
);
800 /* remove a trailing $ */
805 /* if requested, try to truncate a C symbol pattern */
806 if (truncatesyms
== YES
&& strpbrk(s
, "[{*+") == NULL
) {
809 /* must be an exact match */
811 * note: regcmp doesn't recognize ^*keypad$ as an syntax error
812 * unless it is given as a single arg
814 (void) sprintf(buf
, "^%s$", s
);
815 if ((regexp
= regcmp(buf
, (char *)NULL
)) == NULL
) {
816 return (REGCMPERROR
);
819 /* if requested, truncate a C symbol pattern */
820 if (truncatesyms
== YES
&& field
<= CALLING
) {
823 /* compress the string pattern for matching */
825 for (i
= 0; (c
= pattern
[i
]) != '\0'; ++i
) {
826 if (dicode1
[c
] && dicode2
[(unsigned)pattern
[i
+ 1]]) {
827 c
= (0200 - 2) + dicode1
[c
] +
828 dicode2
[(unsigned)pattern
[i
+ 1]];
841 /* discard any regular expression */
842 if (regexp
!= NULL
) {
848 /* find this term, which can be a regular expression */
855 char prefix
[PATLEN
+ 1];
856 char term
[PATLEN
+ 1];
858 npostings
= 0; /* will be non-zero after database built */
859 lastfcnoffset
= 0; /* clear the last function name found */
860 boolclear(); /* clear the posting set */
862 /* get the string prefix (if any) of the regular expression */
863 (void) strcpy(prefix
, pattern
);
864 if ((s
= strpbrk(prefix
, ".[{*+")) != NULL
) {
867 /* if letter case is to be ignored */
868 if (caseless
== YES
) {
871 * convert the prefix to upper case because it is lexically
872 * less than lower case
880 /* find the term lexically >= the prefix */
881 (void) invfind(&invcontrol
, prefix
);
882 if (caseless
== YES
) { /* restore lower case */
883 (void) strcpy(prefix
, strtolower(prefix
));
886 * a null prefix matches the null term in the inverted index,
887 * so move to the first real term
889 if (*prefix
== '\0') {
890 (void) invforward(&invcontrol
);
892 len
= strlen(prefix
);
894 (void) invterm(&invcontrol
, term
); /* get the term */
896 if (caseless
== YES
) {
897 s
= strtolower(s
); /* make it lower case */
900 if (regex(regexp
, s
) != NULL
) {
901 /* add it's postings to the set */
902 if ((postingp
= boolfile(&invcontrol
,
903 &npostings
, OR
)) == NULL
) {
906 } else if (len
> 0) {
907 /* if there is a prefix */
910 * if ignoring letter case and the term is out of the
911 * range of possible matches
913 if (caseless
== YES
) {
914 if (strncmp(term
, prefix
, len
) > 0) {
915 break; /* stop searching */
918 /* if using letter case and the prefix doesn't match */
919 else if (strncmp(term
, prefix
, len
) != 0) {
920 break; /* stop searching */
923 /* display progress about every three seconds */
924 if (++searchcount
% 50 == 0) {
925 progress("%ld of %ld symbols matched",
926 searchcount
, totalterms
);
928 } while (invforward(&invcontrol
)); /* while didn't wrap around */
930 /* initialize the progress message for retrieving the references */
932 postingsfound
= npostings
;
935 /* display the file search progress about every three seconds */
940 if (++searchcount
% 10 == 0) {
941 progress("%ld of %ld files searched", searchcount
,
946 /* initialize the progress message */
952 starttime
= time((long *)NULL
);
955 /* display the progress every three seconds */
958 progress(char *format
, long n1
, long n2
)
960 char msg
[MSGLEN
+ 1];
963 /* print after 2 seconds so the average is nearer 3 seconds */
964 if (linemode
== NO
&& (now
= time((long *)NULL
)) - starttime
>= 2) {
966 (void) sprintf(msg
, format
, n1
, n2
);
971 /* match the pattern to the string */
976 char string
[PATLEN
+ 1];
979 /* see if this is a regular expression pattern */
980 if (regexp
!= NULL
) {
982 if (*string
== '\0') {
986 if (caseless
== YES
) {
989 return (regex(regexp
, s
) ? YES
: NO
);
991 /* it is a string pattern */
992 return ((BOOL
)(*blockp
== cpattern
[0] && matchrest()));
995 /* match the rest of the pattern to the name */
1004 while (*blockp
== cpattern
[i
]) {
1008 } while (*(blockp
+ 1) == '\0' && readblock() != NULL
);
1010 if (*blockp
== '\n' && cpattern
[i
] == '\0') {
1016 /* get the next posting for this term */
1021 if (npostings
-- <= 0) {
1024 /* display progress about every three seconds */
1025 if (++searchcount
% 100 == 0) {
1026 progress("%ld of %ld possible references retrieved",
1027 searchcount
, postingsfound
);
1029 return (postingp
++);
1032 /* put the posting reference into the file */
1035 putpostingref(POSTING
*p
)
1037 static char function
[PATLEN
+ 1]; /* function name */
1039 if (p
->fcnoffset
== 0) {
1041 } else if (p
->fcnoffset
!= lastfcnoffset
) {
1042 if (dbseek(p
->fcnoffset
) != -1) {
1043 getstring(function
);
1044 lastfcnoffset
= p
->fcnoffset
;
1047 if (dbseek(p
->lineoffset
) != -1) {
1048 putref(srcfiles
[p
->fileindex
], function
);
1052 /* put the reference into the file */
1055 putref(char *file
, char *function
)
1059 /* put global references first */
1060 if (*function
== '\0') {
1061 function
= "<global>";
1064 output
= nonglobalrefs
;
1066 if (fprintf(output
, "%s %s ", filepath(file
), function
) == EOF
) {
1073 /* put the source line into the file */
1076 putsource(FILE *output
)
1078 char *cp
, nextc
= '\0';
1080 if (fileversion
<= 5) {
1081 (void) scanpast(' ');
1083 (void) putc('\n', output
);
1086 /* scan back to the beginning of the source line */
1088 while (*cp
!= '\n' || nextc
!= '\n') {
1091 /* read the previous block */
1092 (void) dbseek((blocknumber
- 1) * BUFSIZ
);
1093 cp
= &block
[BUFSIZ
- 1];
1096 /* there must be a double newline followed by a line number */
1098 setmark(' '); /* so getrefchar doesn't skip the last block char */
1099 if (*blockp
!= '\n' || getrefchar() != '\n' ||
1100 !isdigit(getrefchar()) && fileversion
>= 12) {
1101 putmsg("Internal error: cannot get source line from database");
1104 /* until a double newline is found */
1106 /* skip a symbol type */
1107 if (*blockp
== '\t') {
1111 /* output a piece of the source line */
1113 } while (blockp
!= NULL
&& getrefchar() != '\n');
1114 (void) putc('\n', output
);
1117 /* put the rest of the cross-reference line into the file */
1120 putline(FILE *output
)
1128 while ((c
= *cp
) != '\n') {
1129 /* check for a compressed digraph */
1132 (void) putc(dichar1
[c
/ 8], output
);
1133 (void) putc(dichar2
[c
& 7], output
);
1134 } else if (c
< ' ') {
1135 /* a compressed keyword */
1136 (void) fputs(keyword
[c
].text
, output
);
1137 if (keyword
[c
].delim
!= '\0') {
1138 (void) putc(' ', output
);
1140 if (keyword
[c
].delim
== '(') {
1141 (void) putc('(', output
);
1144 (void) putc((int)c
, output
);
1148 } while (*(cp
+ 1) == '\0' && (cp
= readblock()) != NULL
);
1152 /* put the rest of the cross-reference line into the string */
1163 while ((c
= *cp
) != '\n') {
1166 *s
++ = dichar1
[c
/ 8];
1167 *s
++ = dichar2
[c
& 7];
1173 } while (*(cp
+ 1) == '\0' && (cp
= readblock()) != NULL
);
1178 /* scan past the next occurence of this character in the cross-reference */
1187 do { /* innermost loop optimized to only one test */
1191 } while (*(cp
+ 1) == '\0' && (cp
= readblock()) != NULL
);
1194 skiprefchar(); /* skip the found character */
1199 /* read a block of the cross-reference */
1204 /* read the next block */
1205 blocklen
= read(symrefs
, block
, BUFSIZ
);
1208 /* add the search character and end-of-block mark */
1209 block
[blocklen
] = blockmark
;
1210 block
[blocklen
+ 1] = '\0';
1212 /* return NULL on end-of-file */
1213 if (blocklen
== 0) {
1221 /* seek to the database offset */
1229 if ((n
= offset
/ BUFSIZ
) != blocknumber
) {
1230 if ((rc
= lseek(symrefs
, n
* BUFSIZ
, 0)) == -1) {
1231 myperror("Lseek failed");
1238 blockp
= block
+ offset
% BUFSIZ
;
1242 /* convert the string to lower case */
1247 static char buf
[PATLEN
+ 1];
1250 while (*s
!= '\0') {
1252 * note: s in not incremented in this line because the BSD
1253 * compatibility tolower macro evaluates its argument twice
1255 *lp
++ = tolower(*s
);
1262 /* if needed, convert a relative path to a full path */
1265 filepath(char *file
)
1267 static char path
[PATHLEN
+ 1];
1272 /* if same file as last time, return the same path */
1273 if (strequal(file
, lastfilepath
)) {
1276 (void) strcpy(lastfilepath
, file
);
1278 /* if requested, prepend a path to a relative file path */
1279 if (prependpath
!= NULL
) {
1280 (void) sprintf(path
, "%s/%s", prependpath
, file
);
1284 * if the database was built with a view path, return a
1285 * full path so "cscope -d -f" does not have to be called
1286 * from the build directory with the same view path
1288 if (dbvpndirs
> 1) {
1289 for (i
= 0; i
< dbvpndirs
; i
++) {
1290 (void) sprintf(path
,
1291 "%s/%s", dbvpdirs
[i
], file
);
1292 if (access(path
, READ
) != -1) {
1297 (void) strcpy(path
, file
); /* for lastfilepath check */