xref: /illumos-gate/usr/src/tools/cscope-fast/find.c (revision dd72704bd9e794056c558153663c739e2012d721)
1 /*
2  * CDDL HEADER START
3  *
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.
8  *
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.
13  *
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]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1988 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 
26 /*
27  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
28  * Use is subject to license terms.
29  */
30 
31 /*
32  * 	cscope - interactive C symbol or text cross-reference
33  *
34  *	searching functions
35  */
36 
37 #include <unistd.h>
38 #include <stdio.h>
39 #include <libgen.h>
40 #include "global.h"
41 #include "vp.h"
42 
43 /*
44  * most of these functions have been optimized so their innermost loops have
45  * only one test for the desired character by putting the char and
46  * an end-of-block marker (\0) at the end of the disk block buffer.
47  * When the inner loop exits on the char, an outer loop will see if
48  * the char is followed by a \0.  If so, it will read the next block
49  * and restart the inner loop.
50  */
51 
52 char	block[BUFSIZ + 2];		/* leave room for end-of-block mark */
53 int	blocklen;			/* length of disk block read */
54 char	blockmark;			/* mark character to be searched for */
55 long	blocknumber;			/* block number */
56 char	*blockp;			/* pointer to current char in block */
57 char	lastfilepath[PATHLEN + 1];	/* last file that full path was */
58 					/* computed for */
59 
60 static	char	cpattern[PATLEN + 1];	/* compressed pattern */
61 static	long	lastfcnoffset;		/* last function name offset */
62 static	long	postingsfound;		/* retrieved number of postings */
63 static	char	*regexp;		/* regular expression */
64 static	POSTING	*postingp;		/* retrieved posting set pointer */
65 static	long	searchcount;		/* count of files searched */
66 static	long	starttime;		/* start time for progress messages */
67 
68 static POSTING *getposting(void);
69 static void putsource(FILE *output);
70 static void putref(char *file, char *function);
71 static void findcalledbysub(char *file);
72 static void findterm(void);
73 static void fileprogress(void);
74 static void putpostingref(POSTING *p);
75 static void putline(FILE *output);
76 static char *strtolower(char *s);
77 static char *filepath(char *file);
78 
79 /* find the symbol in the cross-reference */
80 
81 void
82 findsymbol(void)
83 {
84 	char	file[PATHLEN + 1];	/* source file name */
85 	char	function[PATLEN + 1];	/* function name */
86 	char	macro[PATLEN + 1];	/* macro name */
87 	char	symbol[PATLEN + 1];	/* symbol name */
88 	char	*cp;
89 	char	c;
90 	char	*s;
91 
92 	if (invertedindex == YES) {
93 		long	lastline = 0;
94 		POSTING	*p;
95 
96 		findterm();
97 		while ((p = getposting()) != NULL) {
98 			if (p->type != INCLUDE && p->lineoffset != lastline) {
99 				putpostingref(p);
100 				lastline = p->lineoffset;
101 			}
102 		}
103 		return;
104 	}
105 	(void) scanpast('\t');		/* find the end of the header */
106 	skiprefchar();			/* skip the file marker */
107 	getstring(file);		/* save the file name */
108 	*function = '\0';
109 	/* a macro can be inside a function, but not vice versa */
110 	*macro = '\0';
111 
112 	/* find the next symbol */
113 	/* note: this code was expanded in-line for speed */
114 	/* while (scanpast('\n') != NULL) { */
115 	/* other macros were replaced by code using cp instead of blockp */
116 	cp = blockp;
117 	for (;;) {
118 		setmark('\n');
119 		do {	/* innermost loop optimized to only one test */
120 			while (*cp != '\n') {
121 				++cp;
122 			}
123 		} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);
124 
125 		/* skip the found character */
126 		if (cp != NULL && *(++cp + 1) == '\0') {
127 			cp = readblock();
128 		}
129 		if (cp == NULL) {
130 			break;
131 		}
132 		/* look for a source file or function name */
133 		if (*cp == '\t') {
134 			blockp = cp;
135 			switch (getrefchar()) {
136 
137 			case NEWFILE:		/* file name */
138 
139 				/* save the name */
140 				skiprefchar();
141 				getstring(file);
142 
143 				/* check for the end of the symbols */
144 				if (*file == '\0') {
145 					return;
146 				}
147 				fileprogress();
148 				/* FALLTHROUGH */
149 
150 			case FCNEND:		/* function end */
151 				*function = '\0';
152 				goto notmatched;	/* don't match name */
153 
154 			case FCNDEF:		/* function name */
155 				s = function;
156 				break;
157 
158 			case DEFINE:		/* could be a macro */
159 				if (fileversion >= 10) {
160 					s = macro;
161 				} else {
162 					s = symbol;
163 				}
164 				break;
165 
166 			case DEFINEEND:
167 				*macro = '\0';
168 				goto notmatched;	/* don't match name */
169 
170 			case INCLUDE:		/* #include file */
171 				goto notmatched;	/* don't match name */
172 			default:		/* other symbol */
173 				s = symbol;
174 			}
175 			/* save the name */
176 			skiprefchar();
177 			getstring(s);
178 
179 			/* see if this is a regular expression pattern */
180 			if (regexp != NULL) {
181 				if (caseless == YES) {
182 					s = strtolower(s);
183 				}
184 				if (*s != '\0' && regex(regexp, s) != NULL) {
185 					goto matched;
186 				}
187 			}
188 			/* match the symbol to the text pattern */
189 			else if (strequal(pattern, s)) {
190 				goto matched;
191 			}
192 			goto notmatched;
193 		}
194 		/* if this is a regular expression pattern */
195 		if (regexp != NULL) {
196 			c = *cp;
197 			if (c & 0200) {	/* digraph char? */
198 				c = dichar1[(c & 0177) / 8];
199 			}
200 			/* if this is a symbol */
201 			if (isalpha(c) || c == '_') {
202 				blockp = cp;
203 				getstring(symbol);
204 				s = symbol;
205 				if (caseless == YES) {
206 					s = strtolower(s);
207 				}
208 				/* match the symbol to the regular expression */
209 				if (regex(regexp, s) != NULL) {
210 					goto matched;
211 				}
212 				goto notmatched;
213 			}
214 		}
215 		/* match the character to the text pattern */
216 		else if (*cp == cpattern[0]) {
217 			blockp = cp;
218 
219 			/* match the rest of the symbol to the text pattern */
220 			if (matchrest()) {
221 				s = NULL;
222 matched:
223 				/*
224 				 * output the file, calling function or macro,
225 				 * and source line
226 				 */
227 				if (*macro != '\0' && s != macro) {
228 					putref(file, macro);
229 				} else if (s != function) {
230 					putref(file, function);
231 				} else {
232 					putref(file, "");
233 				}
234 				if (blockp == NULL) {
235 					return;
236 				}
237 			}
238 notmatched:
239 			cp = blockp;
240 		}
241 	}
242 	blockp = cp;
243 }
244 
245 /* find the function definition or #define */
246 
247 void
248 finddef(void)
249 {
250 	char	file[PATHLEN + 1];	/* source file name */
251 	char	function[PATLEN + 1];	/* function name */
252 	char	macro[PATLEN + 1];	/* macro name */
253 	char	symbol[PATLEN + 1];	/* symbol name */
254 	char	*s;
255 
256 	if (invertedindex == YES) {
257 		POSTING	*p;
258 
259 		findterm();
260 		while ((p = getposting()) != NULL) {
261 			switch (p->type) {
262 			case DEFINE:		/* could be a macro */
263 			case FCNDEF:
264 			case CLASSDEF:
265 			case ENUMDEF:
266 			case MEMBERDEF:
267 			case STRUCTDEF:
268 			case TYPEDEF:
269 			case UNIONDEF:
270 			case GLOBALDEF:		/* other global definition */
271 			case LOCALDEF:		/* other local definition */
272 			case PARAMETER:
273 				putpostingref(p);
274 			}
275 		}
276 		return;
277 	}
278 	/* find the next file name or definition */
279 	*function = '\0';
280 	/* a macro can be inside a function, but not vice versa */
281 	*macro = '\0';
282 
283 	while (scanpast('\t') != NULL) {
284 		switch (*blockp) {
285 
286 		case NEWFILE:
287 			skiprefchar();	/* save file name */
288 			getstring(file);
289 			if (*file == '\0') {	/* if end of symbols */
290 				return;
291 			}
292 			fileprogress();
293 			/* FALLTHROUGH */
294 
295 		case FCNEND:		/* function end */
296 			*function = '\0';
297 			break;
298 
299 		case FCNDEF:		/* function name */
300 			s = function;
301 			goto def;
302 
303 		case DEFINE:		/* could be a macro */
304 			if (fileversion >= 10) {
305 				s = macro;
306 			} else {
307 				s = symbol;
308 			}
309 			goto def;
310 
311 		case DEFINEEND:
312 			*macro = '\0';
313 			break;
314 
315 		case CLASSDEF:
316 		case ENUMDEF:
317 		case MEMBERDEF:
318 		case STRUCTDEF:
319 		case TYPEDEF:
320 		case UNIONDEF:
321 		case GLOBALDEF:		/* other global definition */
322 		case LOCALDEF:		/* other local definition */
323 		case PARAMETER:
324 			s = symbol;
325 		def:
326 			/* save the name */
327 			skiprefchar();
328 			getstring(s);
329 
330 			/* see if this is a regular expression pattern */
331 			if (regexp != NULL) {
332 				if (caseless == YES) {
333 					s = strtolower(s);
334 				}
335 				if (*s != '\0' && regex(regexp, s) != NULL) {
336 					goto matched;
337 				}
338 			} else if (strequal(pattern, s)) {
339 				/* match the symbol to the text pattern */
340 matched:
341 				/*
342 				 * output the file, calling function or macro,
343 				 * and source line
344 				 */
345 				if (*macro != '\0' && s != macro) {
346 					putref(file, macro);
347 				} else if (s != function) {
348 					putref(file, function);
349 				} else {
350 					putref(file, "");
351 				}
352 			}
353 		}
354 	}
355 }
356 
357 /* find all function definitions (used by samuel only) */
358 
359 void
360 findallfcns(void)
361 {
362 	char	file[PATHLEN + 1];	/* source file name */
363 	char	function[PATLEN + 1];	/* function name */
364 
365 	/* find the next file name or definition */
366 	while (scanpast('\t') != NULL) {
367 		switch (*blockp) {
368 		case NEWFILE:
369 			skiprefchar();	/* save file name */
370 			getstring(file);
371 			if (*file == '\0') {	/* if end of symbols */
372 				return;
373 			}
374 			fileprogress();
375 			break;
376 
377 		case FCNDEF:
378 		case CLASSDEF:
379 			skiprefchar();	/* save function name */
380 			getstring(function);
381 
382 			/* output the file, function and source line */
383 			putref(file, function);
384 			break;
385 		}
386 	}
387 }
388 
389 /* find the functions called by this function */
390 
391 void
392 findcalledby(void)
393 {
394 	char	file[PATHLEN + 1];	/* source file name */
395 
396 	if (invertedindex == YES) {
397 		POSTING	*p;
398 
399 		findterm();
400 		while ((p = getposting()) != NULL) {
401 			switch (p->type) {
402 			case DEFINE:		/* could be a macro */
403 			case FCNDEF:
404 				if (dbseek(p->lineoffset) != -1 &&
405 				    scanpast('\t') != NULL) {	/* skip def */
406 					findcalledbysub(srcfiles[p->fileindex]);
407 				}
408 			}
409 		}
410 		return;
411 	}
412 	/* find the function definition(s) */
413 	while (scanpast('\t') != NULL) {
414 		switch (*blockp) {
415 		case NEWFILE:
416 			skiprefchar();	/* save file name */
417 			getstring(file);
418 			if (*file == '\0') {	/* if end of symbols */
419 				return;
420 			}
421 			fileprogress();
422 			break;
423 
424 		case DEFINE:		/* could be a macro */
425 			if (fileversion < 10) {
426 				break;
427 			}
428 			/* FALLTHROUGH */
429 
430 		case FCNDEF:
431 			skiprefchar();	/* match name to pattern */
432 			if (match()) {
433 				findcalledbysub(file);
434 			}
435 			break;
436 		}
437 	}
438 }
439 
440 static void
441 findcalledbysub(char *file)
442 {
443 	/* find the next function call or the end of this function */
444 	while (scanpast('\t') != NULL) {
445 		switch (*blockp) {
446 		case DEFINE:		/* #define inside a function */
447 			if (fileversion >= 10) {	/* skip it */
448 				while (scanpast('\t') != NULL &&
449 				    *blockp != DEFINEEND)
450 					;
451 			}
452 			break;
453 		case FCNCALL:		/* function call */
454 
455 			/* output the file name */
456 			(void) fprintf(refsfound, "%s ", filepath(file));
457 
458 			/* output the function name */
459 			skiprefchar();
460 			putline(refsfound);
461 			(void) putc(' ', refsfound);
462 
463 			/* output the source line */
464 			putsource(refsfound);
465 			break;
466 
467 		case DEFINEEND:		/* #define end */
468 		case FCNEND:		/* function end */
469 		case FCNDEF:		/* function end (pre 9.5) */
470 		case NEWFILE:		/* file end */
471 			return;
472 		}
473 	}
474 }
475 
476 /* find the functions calling this function */
477 
478 void
479 findcalling(void)
480 {
481 	char	file[PATHLEN + 1];	/* source file name */
482 	char	function[PATLEN + 1];	/* function name */
483 	char	macro[PATLEN + 1];	/* macro name */
484 
485 	if (invertedindex == YES) {
486 		POSTING	*p;
487 
488 		findterm();
489 		while ((p = getposting()) != NULL) {
490 			if (p->type == FCNCALL) {
491 				putpostingref(p);
492 			}
493 		}
494 		return;
495 	}
496 	/* find the next file name or function definition */
497 	/* a macro can be inside a function, but not vice versa */
498 	*macro = '\0';
499 
500 	while (scanpast('\t') != NULL) {
501 		switch (*blockp) {
502 		case NEWFILE:		/* save file name */
503 			skiprefchar();
504 			getstring(file);
505 			if (*file == '\0') {	/* if end of symbols */
506 				return;
507 			}
508 			fileprogress();
509 			/* FALLTHROUGH */
510 		case FCNEND:		/* function end */
511 			*function = '\0';
512 			break;
513 		case DEFINE:		/* could be a macro */
514 			if (fileversion >= 10) {
515 				skiprefchar();
516 				getstring(macro);
517 			}
518 			break;
519 
520 		case DEFINEEND:
521 			*macro = '\0';
522 			break;
523 
524 		case FCNDEF:		/* save calling function name */
525 			skiprefchar();
526 			getstring(function);
527 			break;
528 		case FCNCALL:		/* match function called to pattern */
529 			skiprefchar();
530 			if (match()) {
531 				/* output the file, calling function or */
532 				/* macro, and source */
533 				if (*macro != '\0') {
534 					putref(file, macro);
535 				} else {
536 					putref(file, function);
537 				}
538 			}
539 		}
540 	}
541 }
542 
543 /* find direct assignment to, and increment and decrement of, this variable */
544 
545 void
546 findassignments(void)
547 {
548 	char	file[PATHLEN + 1];	/* source file name */
549 	char	function[PATLEN + 1];	/* function name */
550 	char	macro[PATLEN + 1];	/* macro name */
551 
552 	if (fileversion < 13) {
553 		putmsg("Database built with cscope version < 13 does not "
554 		    "have assignment information");
555 		(void) sleep(3);
556 		return;
557 	}
558 #if CTRACE
559 	ctroff();
560 #endif
561 	if (invertedindex == YES) {
562 		POSTING	*p;
563 
564 		findterm();
565 		while ((p = getposting()) != NULL) {
566 			switch (p->type) {
567 			case ASSIGNMENT:
568 			case GLOBALDEF:		/* can have initializer */
569 			case LOCALDEF:		/* can have initializer */
570 			case PARAMETER:		/* initial value */
571 				putpostingref(p);
572 			}
573 		}
574 		return;
575 	}
576 	/* find the next file name or function definition */
577 	/* a macro can be inside a function, but not vice versa */
578 	*macro = '\0';
579 
580 	while (scanpast('\t') != NULL) {
581 		switch (*blockp) {
582 		case NEWFILE:		/* save file name */
583 			skiprefchar();
584 			getstring(file);
585 			if (*file == '\0') {	/* if end of symbols */
586 				return;
587 			}
588 			fileprogress();
589 			/* FALLTHROUGH */
590 		case FCNEND:		/* function end */
591 			*function = '\0';
592 			break;
593 		case DEFINE:		/* could be a macro */
594 			if (fileversion >= 10) {
595 				skiprefchar();
596 				getstring(macro);
597 			}
598 			break;
599 
600 		case DEFINEEND:
601 			*macro = '\0';
602 			break;
603 
604 		case FCNDEF:		/* save calling function name */
605 			skiprefchar();
606 			getstring(function);
607 			break;
608 		case ASSIGNMENT:	/* match assignment to pattern */
609 		case GLOBALDEF:		/* can have initializer */
610 		case LOCALDEF:		/* can have initializer */
611 		case PARAMETER:		/* initial value */
612 			skiprefchar();
613 			if (match()) {
614 				/* output the file, calling function or */
615 				/* macro, and source */
616 				if (*macro != '\0') {
617 					putref(file, macro);
618 				} else {
619 					putref(file, function);
620 				}
621 			}
622 		}
623 	}
624 }
625 
626 /* find the grep pattern in the source files */
627 
628 char *
629 findgreppat(void)
630 {
631 	char	egreppat[2 * PATLEN];
632 	char	*cp, *pp;
633 
634 	/* translate egrep special characters in the regular expression */
635 	cp = egreppat;
636 	for (pp = pattern; *pp != '\0'; ++pp) {
637 		if (strchr("+?|()", *pp) != NULL) {
638 			*cp++ = '\\';
639 		}
640 		*cp++ = *pp;
641 	}
642 	*cp = '\0';
643 
644 	/* search the source files */
645 	return (findegreppat(egreppat));
646 }
647 
648 /* find this regular expression in the source files */
649 
650 char *
651 findegreppat(char *egreppat)
652 {
653 	int	i;
654 	char	*egreperror;
655 	char	msg[MSGLEN + 1];
656 
657 	/* compile the pattern */
658 	if ((egreperror = egrepinit(egreppat)) == NULL) {
659 
660 		/* search the files */
661 		for (i = 0; i < nsrcfiles; ++i) {
662 			char *file = filepath(srcfiles[i]);
663 			fileprogress();
664 			if (egrep(file, refsfound, "%s <unknown> %ld ") < 0) {
665 				(void) sprintf(msg, "Cannot open file %s",
666 				    file);
667 				putmsg2(msg);
668 			}
669 		}
670 	}
671 	return (egreperror);
672 }
673 
674 /* find matching file names */
675 
676 void
677 findfile(void)
678 {
679 	int	i;
680 	char	*s;
681 
682 	for (i = 0; i < nsrcfiles; ++i) {
683 		s = srcfiles[i];
684 		if (caseless == YES) {
685 			s = strtolower(s);
686 		}
687 		if (regex(regexp, s) != NULL) {
688 			(void) fprintf(refsfound, "%s <unknown> 1 <unknown>\n",
689 				filepath(srcfiles[i]));
690 		}
691 	}
692 }
693 
694 /* find files #including this file */
695 
696 void
697 findinclude(void)
698 {
699 	char	file[PATHLEN + 1];	/* source file name */
700 
701 	if (invertedindex == YES) {
702 		POSTING	*p;
703 
704 		findterm();
705 		while ((p = getposting()) != NULL) {
706 			if (p->type == INCLUDE) {
707 				putpostingref(p);
708 			}
709 		}
710 		return;
711 	}
712 	/* find the next file name or function definition */
713 	while (scanpast('\t') != NULL) {
714 		switch (*blockp) {
715 
716 		case NEWFILE:		/* save file name */
717 			skiprefchar();
718 			getstring(file);
719 			if (*file == '\0') {	/* if end of symbols */
720 				return;
721 			}
722 			fileprogress();
723 			break;
724 
725 		case INCLUDE:		/* match function called to pattern */
726 			skiprefchar();
727 			/* skip global or local #include marker */
728 			skiprefchar();
729 			if (match()) {
730 				/* output the file and source line */
731 				putref(file, "");
732 			}
733 		}
734 	}
735 }
736 
737 /* initialize */
738 
739 FINDINIT
740 findinit(void)
741 {
742 	char	buf[PATLEN + 3];
743 	BOOL	isregexp = NO;
744 	int	i;
745 	char	*s;
746 	unsigned c;
747 
748 	/* remove trailing white space */
749 	for (s = pattern + strlen(pattern) - 1; isspace(*s); --s) {
750 		*s = '\0';
751 	}
752 	/* allow a partial match for a file name */
753 	if (field == FILENAME || field == INCLUDES) {
754 		/* allow types.h to match #include <sys/types.h> */
755 		if (invertedindex == YES && field == INCLUDES &&
756 		    strncmp(pattern, ".*", 2) != 0) {
757 			(void) sprintf(pattern, ".*%s", strcpy(buf, pattern));
758 		}
759 		if ((regexp = regcmp(pattern, (char *)NULL)) == NULL) {
760 			return (REGCMPERROR);
761 		}
762 		return (NOERROR);
763 	}
764 	/* see if the pattern is a regular expression */
765 	if (strpbrk(pattern, "^.[{*+$") != NULL) {
766 		isregexp = YES;
767 	} else {
768 		/* check for a valid C symbol */
769 		s = pattern;
770 		if (!isalpha(*s) && *s != '_') {
771 			return (NOTSYMBOL);
772 		}
773 		while (*++s != '\0') {
774 			if (!isalnum(*s) && *s != '_') {
775 				return (NOTSYMBOL);
776 			}
777 		}
778 		/*
779 		 * look for use of the -T option (truncate symbol to 8
780 		 * characters) on a database not built with -T
781 		 */
782 		if (truncatesyms == YES && isuptodate == YES &&
783 		    dbtruncated == NO && s - pattern >= 8) {
784 			(void) strcpy(pattern + 8, ".*");
785 			isregexp = YES;
786 		}
787 	}
788 	/* if this is a regular expression or letter case is to be ignored */
789 	/* or there is an inverted index */
790 	if (isregexp == YES || caseless == YES || invertedindex == YES) {
791 
792 		/* remove a leading ^ */
793 		s = pattern;
794 		if (*s == '^') {
795 			(void) strcpy(newpat, s + 1);
796 			(void) strcpy(s, newpat);
797 		}
798 		/* remove a trailing $ */
799 		i = strlen(s) - 1;
800 		if (s[i] == '$') {
801 			s[i] = '\0';
802 		}
803 		/* if requested, try to truncate a C symbol pattern */
804 		if (truncatesyms == YES && strpbrk(s, "[{*+") == NULL) {
805 			s[8] = '\0';
806 		}
807 		/* must be an exact match */
808 		/*
809 		 * note: regcmp doesn't recognize ^*keypad$ as an syntax error
810 		 * unless it is given as a single arg
811 		 */
812 		(void) sprintf(buf, "^%s$", s);
813 		if ((regexp = regcmp(buf, (char *)NULL)) == NULL) {
814 			return (REGCMPERROR);
815 		}
816 	} else {
817 		/* if requested, truncate a C symbol pattern */
818 		if (truncatesyms == YES && field <= CALLING) {
819 			pattern[8] = '\0';
820 		}
821 		/* compress the string pattern for matching */
822 		s = cpattern;
823 		for (i = 0; (c = pattern[i]) != '\0'; ++i) {
824 			if (dicode1[c] && dicode2[(unsigned)pattern[i + 1]]) {
825 				c = (0200 - 2) + dicode1[c] +
826 				    dicode2[(unsigned)pattern[i + 1]];
827 				++i;
828 			}
829 			*s++ = (char)c;
830 		}
831 		*s = '\0';
832 	}
833 	return (NOERROR);
834 }
835 
836 void
837 findcleanup(void)
838 {
839 	/* discard any regular expression */
840 	if (regexp != NULL) {
841 		free(regexp);
842 		regexp = NULL;
843 	}
844 }
845 
846 /* find this term, which can be a regular expression */
847 
848 static void
849 findterm(void)
850 {
851 	char	*s;
852 	int	len;
853 	char	prefix[PATLEN + 1];
854 	char	term[PATLEN + 1];
855 
856 	npostings = 0;		/* will be non-zero after database built */
857 	lastfcnoffset = 0;	/* clear the last function name found */
858 	boolclear();		/* clear the posting set */
859 
860 	/* get the string prefix (if any) of the regular expression */
861 	(void) strcpy(prefix, pattern);
862 	if ((s = strpbrk(prefix, ".[{*+")) != NULL) {
863 		*s = '\0';
864 	}
865 	/* if letter case is to be ignored */
866 	if (caseless == YES) {
867 
868 		/*
869 		 * convert the prefix to upper case because it is lexically
870 		 * less than lower case
871 		 */
872 		s = prefix;
873 		while (*s != '\0') {
874 			*s = toupper(*s);
875 			++s;
876 		}
877 	}
878 	/* find the term lexically >= the prefix */
879 	(void) invfind(&invcontrol, prefix);
880 	if (caseless == YES) {	/* restore lower case */
881 		(void) strcpy(prefix, strtolower(prefix));
882 	}
883 	/*
884 	 * a null prefix matches the null term in the inverted index,
885 	 * so move to the first real term
886 	 */
887 	if (*prefix == '\0') {
888 		(void) invforward(&invcontrol);
889 	}
890 	len = strlen(prefix);
891 	do {
892 		(void) invterm(&invcontrol, term);	/* get the term */
893 		s = term;
894 		if (caseless == YES) {
895 			s = strtolower(s);	/* make it lower case */
896 		}
897 		/* if it matches */
898 		if (regex(regexp, s) != NULL) {
899 			/* add it's postings to the set */
900 			if ((postingp = boolfile(&invcontrol,
901 			    &npostings, OR)) == NULL) {
902 				break;
903 			}
904 		} else if (len > 0) {
905 			/* if there is a prefix */
906 
907 			/*
908 			 * if ignoring letter case and the term is out of the
909 			 * range of possible matches
910 			 */
911 			if (caseless == YES) {
912 				if (strncmp(term, prefix, len) > 0) {
913 					break;	/* stop searching */
914 				}
915 			}
916 			/* if using letter case and the prefix doesn't match */
917 			else if (strncmp(term, prefix, len) != 0) {
918 				break;	/* stop searching */
919 			}
920 		}
921 		/* display progress about every three seconds */
922 		if (++searchcount % 50 == 0) {
923 			progress("%ld of %ld symbols matched",
924 			    searchcount, totalterms);
925 		}
926 	} while (invforward(&invcontrol));	/* while didn't wrap around */
927 
928 	/* initialize the progress message for retrieving the references */
929 	initprogress();
930 	postingsfound = npostings;
931 }
932 
933 /* display the file search progress about every three seconds */
934 
935 static void
936 fileprogress(void)
937 {
938 	if (++searchcount % 10 == 0) {
939 		progress("%ld of %ld files searched", searchcount,
940 		    (long)nsrcfiles);
941 	}
942 }
943 
944 /* initialize the progress message */
945 
946 void
947 initprogress(void)
948 {
949 	searchcount = 0;
950 	starttime = time((long *)NULL);
951 }
952 
953 /* display the progress every three seconds */
954 
955 void
956 progress(char *format, long n1, long n2)
957 {
958 	char	msg[MSGLEN + 1];
959 	long	now;
960 
961 	/* print after 2 seconds so the average is nearer 3 seconds */
962 	if (linemode == NO && (now = time((long *)NULL)) - starttime >= 2) {
963 		starttime = now;
964 		(void) sprintf(msg, format, n1, n2);
965 		putmsg(msg);
966 	}
967 }
968 
969 /* match the pattern to the string */
970 
971 BOOL
972 match(void)
973 {
974 	char	string[PATLEN + 1];
975 	char	*s;
976 
977 	/* see if this is a regular expression pattern */
978 	if (regexp != NULL) {
979 		getstring(string);
980 		if (*string == '\0') {
981 			return (NO);
982 		}
983 		s = string;
984 		if (caseless == YES) {
985 			s = strtolower(s);
986 		}
987 		return (regex(regexp, s) ? YES : NO);
988 	}
989 	/* it is a string pattern */
990 	return ((BOOL)(*blockp == cpattern[0] && matchrest()));
991 }
992 
993 /* match the rest of the pattern to the name */
994 
995 BOOL
996 matchrest(void)
997 {
998 	int	i = 1;
999 
1000 	skiprefchar();
1001 	do {
1002 		while (*blockp == cpattern[i]) {
1003 			++blockp;
1004 			++i;
1005 		}
1006 	} while (*(blockp + 1) == '\0' && readblock() != NULL);
1007 
1008 	if (*blockp == '\n' && cpattern[i] == '\0') {
1009 		return (YES);
1010 	}
1011 	return (NO);
1012 }
1013 
1014 /* get the next posting for this term */
1015 
1016 static POSTING *
1017 getposting(void)
1018 {
1019 	if (npostings-- <= 0) {
1020 		return (NULL);
1021 	}
1022 	/* display progress about every three seconds */
1023 	if (++searchcount % 100 == 0) {
1024 		progress("%ld of %ld possible references retrieved",
1025 		    searchcount, postingsfound);
1026 	}
1027 	return (postingp++);
1028 }
1029 
1030 /* put the posting reference into the file */
1031 
1032 static void
1033 putpostingref(POSTING *p)
1034 {
1035 	static	char	function[PATLEN + 1];	/* function name */
1036 
1037 	if (p->fcnoffset == 0) {
1038 		*function = '\0';
1039 	} else if (p->fcnoffset != lastfcnoffset) {
1040 		if (dbseek(p->fcnoffset) != -1) {
1041 			getstring(function);
1042 			lastfcnoffset = p->fcnoffset;
1043 		}
1044 	}
1045 	if (dbseek(p->lineoffset) != -1) {
1046 		putref(srcfiles[p->fileindex], function);
1047 	}
1048 }
1049 
1050 /* put the reference into the file */
1051 
1052 static void
1053 putref(char *file, char *function)
1054 {
1055 	FILE	*output;
1056 
1057 	/* put global references first */
1058 	if (*function == '\0') {
1059 		function = "<global>";
1060 		output = refsfound;
1061 	} else {
1062 		output = nonglobalrefs;
1063 	}
1064 	if (fprintf(output, "%s %s ", filepath(file), function) == EOF) {
1065 		cannotwrite(temp1);
1066 		/* NOTREACHED */
1067 	}
1068 	putsource(output);
1069 }
1070 
1071 /* put the source line into the file */
1072 
1073 static void
1074 putsource(FILE *output)
1075 {
1076 	char	*cp, nextc = '\0';
1077 
1078 	if (fileversion <= 5) {
1079 		(void) scanpast(' ');
1080 		putline(output);
1081 		(void) putc('\n', output);
1082 		return;
1083 	}
1084 	/* scan back to the beginning of the source line */
1085 	cp = blockp;
1086 	while (*cp != '\n' || nextc != '\n') {
1087 		nextc = *cp;
1088 		if (--cp < block) {
1089 			/* read the previous block */
1090 			(void) dbseek((blocknumber - 1) * BUFSIZ);
1091 			cp = &block[BUFSIZ - 1];
1092 		}
1093 	}
1094 	/* there must be a double newline followed by a line number */
1095 	blockp = cp;
1096 	setmark(' ');	/* so getrefchar doesn't skip the last block char */
1097 	if (*blockp != '\n' || getrefchar() != '\n' ||
1098 	    !isdigit(getrefchar()) && fileversion >= 12) {
1099 		putmsg("Internal error: cannot get source line from database");
1100 		myexit(1);
1101 	}
1102 	/* until a double newline is found */
1103 	do {
1104 		/* skip a symbol type */
1105 		if (*blockp == '\t') {
1106 			skiprefchar();
1107 			skiprefchar();
1108 		}
1109 		/* output a piece of the source line */
1110 		putline(output);
1111 	} while (blockp != NULL && getrefchar() != '\n');
1112 	(void) putc('\n', output);
1113 }
1114 
1115 /* put the rest of the cross-reference line into the file */
1116 
1117 static void
1118 putline(FILE *output)
1119 {
1120 	char	*cp;
1121 	unsigned c;
1122 
1123 	setmark('\n');
1124 	cp = blockp;
1125 	do {
1126 		while ((c = *cp) != '\n') {
1127 			/* check for a compressed digraph */
1128 			if (c & 0200) {
1129 				c &= 0177;
1130 				(void) putc(dichar1[c / 8], output);
1131 				(void) putc(dichar2[c & 7], output);
1132 			} else if (c < ' ') {
1133 				/* a compressed keyword */
1134 				(void) fputs(keyword[c].text, output);
1135 				if (keyword[c].delim != '\0') {
1136 					(void) putc(' ', output);
1137 				}
1138 				if (keyword[c].delim == '(') {
1139 					(void) putc('(', output);
1140 				}
1141 			} else {
1142 				(void) putc((int)c, output);
1143 			}
1144 			++cp;
1145 		}
1146 	} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);
1147 	blockp = cp;
1148 }
1149 
1150 /* put the rest of the cross-reference line into the string */
1151 
1152 void
1153 getstring(char *s)
1154 {
1155 	char	*cp;
1156 	unsigned c;
1157 
1158 	setmark('\n');
1159 	cp = blockp;
1160 	do {
1161 		while ((c = *cp) != '\n') {
1162 			if (c & 0200) {
1163 				c &= 0177;
1164 				*s++ = dichar1[c / 8];
1165 				*s++ = dichar2[c & 7];
1166 			} else {
1167 				*s++ = (char)c;
1168 			}
1169 			++cp;
1170 		}
1171 	} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);
1172 	blockp = cp;
1173 	*s = '\0';
1174 }
1175 
1176 /* scan past the next occurence of this character in the cross-reference */
1177 
1178 char *
1179 scanpast(int c)
1180 {
1181 	char	*cp;
1182 
1183 	setmark(c);
1184 	cp = blockp;
1185 	do {	/* innermost loop optimized to only one test */
1186 		while (*cp != c) {
1187 			++cp;
1188 		}
1189 	} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);
1190 	blockp = cp;
1191 	if (cp != NULL) {
1192 		skiprefchar();	/* skip the found character */
1193 	}
1194 	return (blockp);
1195 }
1196 
1197 /* read a block of the cross-reference */
1198 
1199 char *
1200 readblock(void)
1201 {
1202 	/* read the next block */
1203 	blocklen = read(symrefs, block, BUFSIZ);
1204 	blockp = block;
1205 
1206 	/* add the search character and end-of-block mark */
1207 	block[blocklen] = blockmark;
1208 	block[blocklen + 1] = '\0';
1209 
1210 	/* return NULL on end-of-file */
1211 	if (blocklen == 0) {
1212 		blockp = NULL;
1213 	} else {
1214 		++blocknumber;
1215 	}
1216 	return (blockp);
1217 }
1218 
1219 /* seek to the database offset */
1220 
1221 long
1222 dbseek(long offset)
1223 {
1224 	long	n;
1225 	int	rc = 0;
1226 
1227 	if ((n = offset / BUFSIZ) != blocknumber) {
1228 		if ((rc = lseek(symrefs, n * BUFSIZ, 0)) == -1) {
1229 			myperror("Lseek failed");
1230 			(void) sleep(3);
1231 			return (rc);
1232 		}
1233 		(void) readblock();
1234 		blocknumber = n;
1235 	}
1236 	blockp = block + offset % BUFSIZ;
1237 	return (rc);
1238 }
1239 
1240 /* convert the string to lower case */
1241 
1242 static char *
1243 strtolower(char *s)
1244 {
1245 	static char buf[PATLEN + 1];
1246 	char *lp = buf;
1247 
1248 	while (*s != '\0') {
1249 		/*
1250 		 * note: s in not incremented in this line because the BSD
1251 		 * compatibility tolower macro evaluates its argument twice
1252 		 */
1253 		*lp++ = tolower(*s);
1254 		++s;
1255 	}
1256 	*lp = '\0';
1257 	return (buf);
1258 }
1259 
1260 /* if needed, convert a relative path to a full path */
1261 
1262 static char *
1263 filepath(char *file)
1264 {
1265 	static	char	path[PATHLEN + 1];
1266 	int	i;
1267 
1268 	if (*file != '/') {
1269 
1270 		/* if same file as last time, return the same path */
1271 		if (strequal(file, lastfilepath)) {
1272 			return (path);
1273 		}
1274 		(void) strcpy(lastfilepath, file);
1275 
1276 		/* if requested, prepend a path to a relative file path */
1277 		if (prependpath != NULL) {
1278 			(void) sprintf(path, "%s/%s", prependpath, file);
1279 			return (path);
1280 		}
1281 		/*
1282 		 * if the database was built with a view path, return a
1283 		 * full path so "cscope -d -f" does not have to be called
1284 		 * from the build directory with the same view path
1285 		 */
1286 		if (dbvpndirs > 1) {
1287 			for (i = 0; i < dbvpndirs; i++) {
1288 				(void) sprintf(path,
1289 				    "%s/%s", dbvpdirs[i], file);
1290 				if (access(path, READ) != -1) {
1291 					return (path);
1292 				}
1293 			}
1294 		}
1295 		(void) strcpy(path, file);	/* for lastfilepath check */
1296 	}
1297 	return (file);
1298 }
1299