/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*
 * Copyright (c) 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#pragma ident	"@(#)find.c	1.1	99/01/11 SMI"

/*
 * 	cscope - interactive C symbol or text cross-reference
 *
 *	searching functions
 */

#include <unistd.h>
#include <stdio.h>
#include <libgen.h>
#include "global.h"
#include "vp.h"

/*
 * most of these functions have been optimized so their innermost loops have
 * only one test for the desired character by putting the char and
 * an end-of-block marker (\0) at the end of the disk block buffer.
 * When the inner loop exits on the char, an outer loop will see if
 * the char is followed by a \0.  If so, it will read the next block
 * and restart the inner loop.
 */

char	block[BUFSIZ + 2];		/* leave room for end-of-block mark */
int	blocklen;			/* length of disk block read */
char	blockmark;			/* mark character to be searched for */
long	blocknumber;			/* block number */
char	*blockp;			/* pointer to current char in block */
char	lastfilepath[PATHLEN + 1];	/* last file that full path was */
					/* computed for */

static	char	cpattern[PATLEN + 1];	/* compressed pattern */
static	long	lastfcnoffset;		/* last function name offset */
static	long	postingsfound;		/* retrieved number of postings */
static	char	*regexp;		/* regular expression */
static	POSTING	*postingp;		/* retrieved posting set pointer */
static	long	searchcount;		/* count of files searched */
static	long	starttime;		/* start time for progress messages */

static POSTING *getposting(void);
static void putsource(FILE *output);
static void putref(char *file, char *function);
static void findcalledbysub(char *file);
static void findterm(void);
static void fileprogress(void);
static void putpostingref(POSTING *p);
static void putline(FILE *output);
static char *strtolower(char *s);
static char *filepath(char *file);

/* find the symbol in the cross-reference */

void
findsymbol(void)
{
	char	file[PATHLEN + 1];	/* source file name */
	char	function[PATLEN + 1];	/* function name */
	char	macro[PATLEN + 1];	/* macro name */
	char	symbol[PATLEN + 1];	/* symbol name */
	char	*cp;
	char	c;
	char	*s;

	if (invertedindex == YES) {
		long	lastline = 0;
		POSTING	*p;

		findterm();
		while ((p = getposting()) != NULL) {
			if (p->type != INCLUDE && p->lineoffset != lastline) {
				putpostingref(p);
				lastline = p->lineoffset;
			}
		}
		return;
	}
	(void) scanpast('\t');		/* find the end of the header */
	skiprefchar();			/* skip the file marker */
	getstring(file);		/* save the file name */
	*function = '\0';
	/* a macro can be inside a function, but not vice versa */
	*macro = '\0';

	/* find the next symbol */
	/* note: this code was expanded in-line for speed */
	/* while (scanpast('\n') != NULL) { */
	/* other macros were replaced by code using cp instead of blockp */
	cp = blockp;
	for (;;) {
		setmark('\n');
		do {	/* innermost loop optimized to only one test */
			while (*cp != '\n') {
				++cp;
			}
		} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);

		/* skip the found character */
		if (cp != NULL && *(++cp + 1) == '\0') {
			cp = readblock();
		}
		if (cp == NULL) {
			break;
		}
		/* look for a source file or function name */
		if (*cp == '\t') {
			blockp = cp;
			switch (getrefchar()) {

			case NEWFILE:		/* file name */

				/* save the name */
				skiprefchar();
				getstring(file);

				/* check for the end of the symbols */
				if (*file == '\0') {
					return;
				}
				fileprogress();
				/* FALLTHROUGH */

			case FCNEND:		/* function end */
				*function = '\0';
				goto notmatched;	/* don't match name */

			case FCNDEF:		/* function name */
				s = function;
				break;

			case DEFINE:		/* could be a macro */
				if (fileversion >= 10) {
					s = macro;
				} else {
					s = symbol;
				}
				break;

			case DEFINEEND:
				*macro = '\0';
				goto notmatched;	/* don't match name */

			case INCLUDE:		/* #include file */
				goto notmatched;	/* don't match name */
			default:		/* other symbol */
				s = symbol;
			}
			/* save the name */
			skiprefchar();
			getstring(s);

			/* see if this is a regular expression pattern */
			if (regexp != NULL) {
				if (caseless == YES) {
					s = strtolower(s);
				}
				if (*s != '\0' && regex(regexp, s) != NULL) {
					goto matched;
				}
			}
			/* match the symbol to the text pattern */
			else if (strequal(pattern, s)) {
				goto matched;
			}
			goto notmatched;
		}
		/* if this is a regular expression pattern */
		if (regexp != NULL) {
			c = *cp;
			if (c & 0200) {	/* digraph char? */
				c = dichar1[(c & 0177) / 8];
			}
			/* if this is a symbol */
			if (isalpha(c) || c == '_') {
				blockp = cp;
				getstring(symbol);
				s = symbol;
				if (caseless == YES) {
					s = strtolower(s);
				}
				/* match the symbol to the regular expression */
				if (regex(regexp, s) != NULL) {
					goto matched;
				}
				goto notmatched;
			}
		}
		/* match the character to the text pattern */
		else if (*cp == cpattern[0]) {
			blockp = cp;

			/* match the rest of the symbol to the text pattern */
			if (matchrest()) {
				s = NULL;
matched:
				/*
				 * output the file, calling function or macro,
				 * and source line
				 */
				if (*macro != '\0' && s != macro) {
					putref(file, macro);
				} else if (s != function) {
					putref(file, function);
				} else {
					putref(file, "");
				}
				if (blockp == NULL) {
					return;
				}
			}
notmatched:
			cp = blockp;
		}
	}
	blockp = cp;
}

/* find the function definition or #define */

void
finddef(void)
{
	char	file[PATHLEN + 1];	/* source file name */
	char	function[PATLEN + 1];	/* function name */
	char	macro[PATLEN + 1];	/* macro name */
	char	symbol[PATLEN + 1];	/* symbol name */
	char	*s;

	if (invertedindex == YES) {
		POSTING	*p;

		findterm();
		while ((p = getposting()) != NULL) {
			switch (p->type) {
			case DEFINE:		/* could be a macro */
			case FCNDEF:
			case CLASSDEF:
			case ENUMDEF:
			case MEMBERDEF:
			case STRUCTDEF:
			case TYPEDEF:
			case UNIONDEF:
			case GLOBALDEF:		/* other global definition */
			case LOCALDEF:		/* other local definition */
			case PARAMETER:
				putpostingref(p);
			}
		}
		return;
	}
	/* find the next file name or definition */
	*function = '\0';
	/* a macro can be inside a function, but not vice versa */
	*macro = '\0';

	while (scanpast('\t') != NULL) {
		switch (*blockp) {

		case NEWFILE:
			skiprefchar();	/* save file name */
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			/* FALLTHROUGH */

		case FCNEND:		/* function end */
			*function = '\0';
			break;

		case FCNDEF:		/* function name */
			s = function;
			goto def;

		case DEFINE:		/* could be a macro */
			if (fileversion >= 10) {
				s = macro;
			} else {
				s = symbol;
			}
			goto def;

		case DEFINEEND:
			*macro = '\0';
			break;

		case CLASSDEF:
		case ENUMDEF:
		case MEMBERDEF:
		case STRUCTDEF:
		case TYPEDEF:
		case UNIONDEF:
		case GLOBALDEF:		/* other global definition */
		case LOCALDEF:		/* other local definition */
		case PARAMETER:
			s = symbol;
		def:
			/* save the name */
			skiprefchar();
			getstring(s);

			/* see if this is a regular expression pattern */
			if (regexp != NULL) {
				if (caseless == YES) {
					s = strtolower(s);
				}
				if (*s != '\0' && regex(regexp, s) != NULL) {
					goto matched;
				}
			} else if (strequal(pattern, s)) {
				/* match the symbol to the text pattern */
matched:
				/*
				 * output the file, calling function or macro,
				 * and source line
				 */
				if (*macro != '\0' && s != macro) {
					putref(file, macro);
				} else if (s != function) {
					putref(file, function);
				} else {
					putref(file, "");
				}
			}
		}
	}
}

/* find all function definitions (used by samuel only) */

void
findallfcns(void)
{
	char	file[PATHLEN + 1];	/* source file name */
	char	function[PATLEN + 1];	/* function name */

	/* find the next file name or definition */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {
		case NEWFILE:
			skiprefchar();	/* save file name */
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			break;

		case FCNDEF:
		case CLASSDEF:
			skiprefchar();	/* save function name */
			getstring(function);

			/* output the file, function and source line */
			putref(file, function);
			break;
		}
	}
}

/* find the functions called by this function */

void
findcalledby(void)
{
	char	file[PATHLEN + 1];	/* source file name */

	if (invertedindex == YES) {
		POSTING	*p;

		findterm();
		while ((p = getposting()) != NULL) {
			switch (p->type) {
			case DEFINE:		/* could be a macro */
			case FCNDEF:
				if (dbseek(p->lineoffset) != -1 &&
				    scanpast('\t') != NULL) {	/* skip def */
					findcalledbysub(srcfiles[p->fileindex]);
				}
			}
		}
		return;
	}
	/* find the function definition(s) */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {
		case NEWFILE:
			skiprefchar();	/* save file name */
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			break;

		case DEFINE:		/* could be a macro */
			if (fileversion < 10) {
				break;
			}
			/* FALLTHROUGH */

		case FCNDEF:
			skiprefchar();	/* match name to pattern */
			if (match()) {
				findcalledbysub(file);
			}
			break;
		}
	}
}

static void
findcalledbysub(char *file)
{
	/* find the next function call or the end of this function */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {
		case DEFINE:		/* #define inside a function */
			if (fileversion >= 10) {	/* skip it */
				while (scanpast('\t') != NULL &&
				    *blockp != DEFINEEND)
					;
			}
			break;
		case FCNCALL:		/* function call */

			/* output the file name */
			(void) fprintf(refsfound, "%s ", filepath(file));

			/* output the function name */
			skiprefchar();
			putline(refsfound);
			(void) putc(' ', refsfound);

			/* output the source line */
			putsource(refsfound);
			break;

		case DEFINEEND:		/* #define end */
		case FCNEND:		/* function end */
		case FCNDEF:		/* function end (pre 9.5) */
		case NEWFILE:		/* file end */
			return;
		}
	}
}

/* find the functions calling this function */

void
findcalling(void)
{
	char	file[PATHLEN + 1];	/* source file name */
	char	function[PATLEN + 1];	/* function name */
	char	macro[PATLEN + 1];	/* macro name */

	if (invertedindex == YES) {
		POSTING	*p;

		findterm();
		while ((p = getposting()) != NULL) {
			if (p->type == FCNCALL) {
				putpostingref(p);
			}
		}
		return;
	}
	/* find the next file name or function definition */
	/* a macro can be inside a function, but not vice versa */
	*macro = '\0';

	while (scanpast('\t') != NULL) {
		switch (*blockp) {
		case NEWFILE:		/* save file name */
			skiprefchar();
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			/* FALLTHROUGH */
		case FCNEND:		/* function end */
			*function = '\0';
			break;
		case DEFINE:		/* could be a macro */
			if (fileversion >= 10) {
				skiprefchar();
				getstring(macro);
			}
			break;

		case DEFINEEND:
			*macro = '\0';
			break;

		case FCNDEF:		/* save calling function name */
			skiprefchar();
			getstring(function);
			break;
		case FCNCALL:		/* match function called to pattern */
			skiprefchar();
			if (match()) {
				/* output the file, calling function or */
				/* macro, and source */
				if (*macro != '\0') {
					putref(file, macro);
				} else {
					putref(file, function);
				}
			}
		}
	}
}

/* find direct assignment to, and increment and decrement of, this variable */

void
findassignments(void)
{
	char	file[PATHLEN + 1];	/* source file name */
	char	function[PATLEN + 1];	/* function name */
	char	macro[PATLEN + 1];	/* macro name */

	if (fileversion < 13) {
		putmsg("Database built with cscope version < 13 does not "
		    "have assignment information");
		(void) sleep(3);
		return;
	}
#if CTRACE
	ctroff();
#endif
	if (invertedindex == YES) {
		POSTING	*p;

		findterm();
		while ((p = getposting()) != NULL) {
			switch (p->type) {
			case ASSIGNMENT:
			case GLOBALDEF:		/* can have initializer */
			case LOCALDEF:		/* can have initializer */
			case PARAMETER:		/* initial value */
				putpostingref(p);
			}
		}
		return;
	}
	/* find the next file name or function definition */
	/* a macro can be inside a function, but not vice versa */
	*macro = '\0';

	while (scanpast('\t') != NULL) {
		switch (*blockp) {
		case NEWFILE:		/* save file name */
			skiprefchar();
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			/* FALLTHROUGH */
		case FCNEND:		/* function end */
			*function = '\0';
			break;
		case DEFINE:		/* could be a macro */
			if (fileversion >= 10) {
				skiprefchar();
				getstring(macro);
			}
			break;

		case DEFINEEND:
			*macro = '\0';
			break;

		case FCNDEF:		/* save calling function name */
			skiprefchar();
			getstring(function);
			break;
		case ASSIGNMENT:	/* match assignment to pattern */
		case GLOBALDEF:		/* can have initializer */
		case LOCALDEF:		/* can have initializer */
		case PARAMETER:		/* initial value */
			skiprefchar();
			if (match()) {
				/* output the file, calling function or */
				/* macro, and source */
				if (*macro != '\0') {
					putref(file, macro);
				} else {
					putref(file, function);
				}
			}
		}
	}
}

/* find the grep pattern in the source files */

char *
findgreppat(void)
{
	char	egreppat[2 * PATLEN];
	char	*cp, *pp;

	/* translate egrep special characters in the regular expression */
	cp = egreppat;
	for (pp = pattern; *pp != '\0'; ++pp) {
		if (strchr("+?|()", *pp) != NULL) {
			*cp++ = '\\';
		}
		*cp++ = *pp;
	}
	*cp = '\0';

	/* search the source files */
	return (findegreppat(egreppat));
}

/* find this regular expression in the source files */

char *
findegreppat(char *egreppat)
{
	int	i;
	char	*egreperror;
	char	msg[MSGLEN + 1];

	/* compile the pattern */
	if ((egreperror = egrepinit(egreppat)) == NULL) {

		/* search the files */
		for (i = 0; i < nsrcfiles; ++i) {
			char *file = filepath(srcfiles[i]);
			fileprogress();
			if (egrep(file, refsfound, "%s <unknown> %ld ") < 0) {
				(void) sprintf(msg, "Cannot open file %s",
				    file);
				putmsg2(msg);
			}
		}
	}
	return (egreperror);
}

/* find matching file names */

void
findfile(void)
{
	int	i;
	char	*s;

	for (i = 0; i < nsrcfiles; ++i) {
		s = srcfiles[i];
		if (caseless == YES) {
			s = strtolower(s);
		}
		if (regex(regexp, s) != NULL) {
			(void) fprintf(refsfound, "%s <unknown> 1 <unknown>\n",
				filepath(srcfiles[i]));
		}
	}
}

/* find files #including this file */

void
findinclude(void)
{
	char	file[PATHLEN + 1];	/* source file name */

	if (invertedindex == YES) {
		POSTING	*p;

		findterm();
		while ((p = getposting()) != NULL) {
			if (p->type == INCLUDE) {
				putpostingref(p);
			}
		}
		return;
	}
	/* find the next file name or function definition */
	while (scanpast('\t') != NULL) {
		switch (*blockp) {

		case NEWFILE:		/* save file name */
			skiprefchar();
			getstring(file);
			if (*file == '\0') {	/* if end of symbols */
				return;
			}
			fileprogress();
			break;

		case INCLUDE:		/* match function called to pattern */
			skiprefchar();
			/* skip global or local #include marker */
			skiprefchar();
			if (match()) {
				/* output the file and source line */
				putref(file, "");
			}
		}
	}
}

/* initialize */

FINDINIT
findinit(void)
{
	char	buf[PATLEN + 3];
	BOOL	isregexp = NO;
	int	i;
	char	*s;
	unsigned c;

	/* remove trailing white space */
	for (s = pattern + strlen(pattern) - 1; isspace(*s); --s) {
		*s = '\0';
	}
	/* allow a partial match for a file name */
	if (field == FILENAME || field == INCLUDES) {
		/* allow types.h to match #include <sys/types.h> */
		if (invertedindex == YES && field == INCLUDES &&
		    strncmp(pattern, ".*", 2) != 0) {
			(void) sprintf(pattern, ".*%s", strcpy(buf, pattern));
		}
		if ((regexp = regcmp(pattern, (char *)NULL)) == NULL) {
			return (REGCMPERROR);
		}
		return (NOERROR);
	}
	/* see if the pattern is a regular expression */
	if (strpbrk(pattern, "^.[{*+$") != NULL) {
		isregexp = YES;
	} else {
		/* check for a valid C symbol */
		s = pattern;
		if (!isalpha(*s) && *s != '_') {
			return (NOTSYMBOL);
		}
		while (*++s != '\0') {
			if (!isalnum(*s) && *s != '_') {
				return (NOTSYMBOL);
			}
		}
		/*
		 * look for use of the -T option (truncate symbol to 8
		 * characters) on a database not built with -T
		 */
		if (truncatesyms == YES && isuptodate == YES &&
		    dbtruncated == NO && s - pattern >= 8) {
			(void) strcpy(pattern + 8, ".*");
			isregexp = YES;
		}
	}
	/* if this is a regular expression or letter case is to be ignored */
	/* or there is an inverted index */
	if (isregexp == YES || caseless == YES || invertedindex == YES) {

		/* remove a leading ^ */
		s = pattern;
		if (*s == '^') {
			(void) strcpy(newpat, s + 1);
			(void) strcpy(s, newpat);
		}
		/* remove a trailing $ */
		i = strlen(s) - 1;
		if (s[i] == '$') {
			s[i] = '\0';
		}
		/* if requested, try to truncate a C symbol pattern */
		if (truncatesyms == YES && strpbrk(s, "[{*+") == NULL) {
			s[8] = '\0';
		}
		/* must be an exact match */
		/*
		 * note: regcmp doesn't recognize ^*keypad$ as an syntax error
		 * unless it is given as a single arg
		 */
		(void) sprintf(buf, "^%s$", s);
		if ((regexp = regcmp(buf, (char *)NULL)) == NULL) {
			return (REGCMPERROR);
		}
	} else {
		/* if requested, truncate a C symbol pattern */
		if (truncatesyms == YES && field <= CALLING) {
			pattern[8] = '\0';
		}
		/* compress the string pattern for matching */
		s = cpattern;
		for (i = 0; (c = pattern[i]) != '\0'; ++i) {
			if (dicode1[c] && dicode2[(unsigned)pattern[i + 1]]) {
				c = (0200 - 2) + dicode1[c] +
				    dicode2[(unsigned)pattern[i + 1]];
				++i;
			}
			*s++ = (char)c;
		}
		*s = '\0';
	}
	return (NOERROR);
}

void
findcleanup(void)
{
	/* discard any regular expression */
	if (regexp != NULL) {
		free(regexp);
		regexp = NULL;
	}
}

/* find this term, which can be a regular expression */

static void
findterm(void)
{
	char	*s;
	int	len;
	char	prefix[PATLEN + 1];
	char	term[PATLEN + 1];

	npostings = 0;		/* will be non-zero after database built */
	lastfcnoffset = 0;	/* clear the last function name found */
	boolclear();		/* clear the posting set */

	/* get the string prefix (if any) of the regular expression */
	(void) strcpy(prefix, pattern);
	if ((s = strpbrk(prefix, ".[{*+")) != NULL) {
		*s = '\0';
	}
	/* if letter case is to be ignored */
	if (caseless == YES) {

		/*
		 * convert the prefix to upper case because it is lexically
		 * less than lower case
		 */
		s = prefix;
		while (*s != '\0') {
			*s = toupper(*s);
			++s;
		}
	}
	/* find the term lexically >= the prefix */
	(void) invfind(&invcontrol, prefix);
	if (caseless == YES) {	/* restore lower case */
		(void) strcpy(prefix, strtolower(prefix));
	}
	/*
	 * a null prefix matches the null term in the inverted index,
	 * so move to the first real term
	 */
	if (*prefix == '\0') {
		(void) invforward(&invcontrol);
	}
	len = strlen(prefix);
	do {
		(void) invterm(&invcontrol, term);	/* get the term */
		s = term;
		if (caseless == YES) {
			s = strtolower(s);	/* make it lower case */
		}
		/* if it matches */
		if (regex(regexp, s) != NULL) {
			/* add it's postings to the set */
			if ((postingp = boolfile(&invcontrol,
			    &npostings, OR)) == NULL) {
				break;
			}
		} else if (len > 0) {
			/* if there is a prefix */

			/*
			 * if ignoring letter case and the term is out of the
			 * range of possible matches
			 */
			if (caseless == YES) {
				if (strncmp(term, prefix, len) > 0) {
					break;	/* stop searching */
				}
			}
			/* if using letter case and the prefix doesn't match */
			else if (strncmp(term, prefix, len) != 0) {
				break;	/* stop searching */
			}
		}
		/* display progress about every three seconds */
		if (++searchcount % 50 == 0) {
			progress("%ld of %ld symbols matched",
			    searchcount, totalterms);
		}
	} while (invforward(&invcontrol));	/* while didn't wrap around */

	/* initialize the progress message for retrieving the references */
	initprogress();
	postingsfound = npostings;
}

/* display the file search progress about every three seconds */

static void
fileprogress(void)
{
	if (++searchcount % 10 == 0) {
		progress("%ld of %ld files searched", searchcount,
		    (long)nsrcfiles);
	}
}

/* initialize the progress message */

void
initprogress(void)
{
	searchcount = 0;
	starttime = time((long *)NULL);
}

/* display the progress every three seconds */

void
progress(char *format, long n1, long n2)
{
	char	msg[MSGLEN + 1];
	long	now;

	/* print after 2 seconds so the average is nearer 3 seconds */
	if (linemode == NO && (now = time((long *)NULL)) - starttime >= 2) {
		starttime = now;
		(void) sprintf(msg, format, n1, n2);
		putmsg(msg);
	}
}

/* match the pattern to the string */

BOOL
match(void)
{
	char	string[PATLEN + 1];
	char	*s;

	/* see if this is a regular expression pattern */
	if (regexp != NULL) {
		getstring(string);
		if (*string == '\0') {
			return (NO);
		}
		s = string;
		if (caseless == YES) {
			s = strtolower(s);
		}
		return (regex(regexp, s) ? YES : NO);
	}
	/* it is a string pattern */
	return ((BOOL)(*blockp == cpattern[0] && matchrest()));
}

/* match the rest of the pattern to the name */

BOOL
matchrest(void)
{
	int	i = 1;

	skiprefchar();
	do {
		while (*blockp == cpattern[i]) {
			++blockp;
			++i;
		}
	} while (*(blockp + 1) == '\0' && readblock() != NULL);

	if (*blockp == '\n' && cpattern[i] == '\0') {
		return (YES);
	}
	return (NO);
}

/* get the next posting for this term */

static POSTING *
getposting(void)
{
	if (npostings-- <= 0) {
		return (NULL);
	}
	/* display progress about every three seconds */
	if (++searchcount % 100 == 0) {
		progress("%ld of %ld possible references retrieved",
		    searchcount, postingsfound);
	}
	return (postingp++);
}

/* put the posting reference into the file */

static void
putpostingref(POSTING *p)
{
	static	char	function[PATLEN + 1];	/* function name */

	if (p->fcnoffset == 0) {
		*function = '\0';
	} else if (p->fcnoffset != lastfcnoffset) {
		if (dbseek(p->fcnoffset) != -1) {
			getstring(function);
			lastfcnoffset = p->fcnoffset;
		}
	}
	if (dbseek(p->lineoffset) != -1) {
		putref(srcfiles[p->fileindex], function);
	}
}

/* put the reference into the file */

static void
putref(char *file, char *function)
{
	FILE	*output;

	/* put global references first */
	if (*function == '\0') {
		function = "<global>";
		output = refsfound;
	} else {
		output = nonglobalrefs;
	}
	if (fprintf(output, "%s %s ", filepath(file), function) == EOF) {
		cannotwrite(temp1);
		/* NOTREACHED */
	}
	putsource(output);
}

/* put the source line into the file */

static void
putsource(FILE *output)
{
	char	*cp, nextc = '\0';

	if (fileversion <= 5) {
		(void) scanpast(' ');
		putline(output);
		(void) putc('\n', output);
		return;
	}
	/* scan back to the beginning of the source line */
	cp = blockp;
	while (*cp != '\n' || nextc != '\n') {
		nextc = *cp;
		if (--cp < block) {
			/* read the previous block */
			(void) dbseek((blocknumber - 1) * BUFSIZ);
			cp = &block[BUFSIZ - 1];
		}
	}
	/* there must be a double newline followed by a line number */
	blockp = cp;
	setmark(' ');	/* so getrefchar doesn't skip the last block char */
	if (*blockp != '\n' || getrefchar() != '\n' ||
	    !isdigit(getrefchar()) && fileversion >= 12) {
		putmsg("Internal error: cannot get source line from database");
		myexit(1);
	}
	/* until a double newline is found */
	do {
		/* skip a symbol type */
		if (*blockp == '\t') {
			skiprefchar();
			skiprefchar();
		}
		/* output a piece of the source line */
		putline(output);
	} while (blockp != NULL && getrefchar() != '\n');
	(void) putc('\n', output);
}

/* put the rest of the cross-reference line into the file */

static void
putline(FILE *output)
{
	char	*cp;
	unsigned c;

	setmark('\n');
	cp = blockp;
	do {
		while ((c = *cp) != '\n') {
			/* check for a compressed digraph */
			if (c & 0200) {
				c &= 0177;
				(void) putc(dichar1[c / 8], output);
				(void) putc(dichar2[c & 7], output);
			} else if (c < ' ') {
				/* a compressed keyword */
				(void) fputs(keyword[c].text, output);
				if (keyword[c].delim != '\0') {
					(void) putc(' ', output);
				}
				if (keyword[c].delim == '(') {
					(void) putc('(', output);
				}
			} else {
				(void) putc((int)c, output);
			}
			++cp;
		}
	} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);
	blockp = cp;
}

/* put the rest of the cross-reference line into the string */

void
getstring(char *s)
{
	char	*cp;
	unsigned c;

	setmark('\n');
	cp = blockp;
	do {
		while ((c = *cp) != '\n') {
			if (c & 0200) {
				c &= 0177;
				*s++ = dichar1[c / 8];
				*s++ = dichar2[c & 7];
			} else {
				*s++ = (char)c;
			}
			++cp;
		}
	} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);
	blockp = cp;
	*s = '\0';
}

/* scan past the next occurence of this character in the cross-reference */

char *
scanpast(int c)
{
	char	*cp;

	setmark(c);
	cp = blockp;
	do {	/* innermost loop optimized to only one test */
		while (*cp != c) {
			++cp;
		}
	} while (*(cp + 1) == '\0' && (cp = readblock()) != NULL);
	blockp = cp;
	if (cp != NULL) {
		skiprefchar();	/* skip the found character */
	}
	return (blockp);
}

/* read a block of the cross-reference */

char *
readblock(void)
{
	/* read the next block */
	blocklen = read(symrefs, block, BUFSIZ);
	blockp = block;

	/* add the search character and end-of-block mark */
	block[blocklen] = blockmark;
	block[blocklen + 1] = '\0';

	/* return NULL on end-of-file */
	if (blocklen == 0) {
		blockp = NULL;
	} else {
		++blocknumber;
	}
	return (blockp);
}

/* seek to the database offset */

long
dbseek(long offset)
{
	long	n;
	int	rc = 0;

	if ((n = offset / BUFSIZ) != blocknumber) {
		if ((rc = lseek(symrefs, n * BUFSIZ, 0)) == -1) {
			myperror("Lseek failed");
			(void) sleep(3);
			return (rc);
		}
		(void) readblock();
		blocknumber = n;
	}
	blockp = block + offset % BUFSIZ;
	return (rc);
}

/* convert the string to lower case */

static char *
strtolower(char *s)
{
	static char buf[PATLEN + 1];
	char *lp = buf;

	while (*s != '\0') {
		/*
		 * note: s in not incremented in this line because the BSD
		 * compatibility tolower macro evaluates its argument twice
		 */
		*lp++ = tolower(*s);
		++s;
	}
	*lp = '\0';
	return (buf);
}

/* if needed, convert a relative path to a full path */

static char *
filepath(char *file)
{
	static	char	path[PATHLEN + 1];
	int	i;

	if (*file != '/') {

		/* if same file as last time, return the same path */
		if (strequal(file, lastfilepath)) {
			return (path);
		}
		(void) strcpy(lastfilepath, file);

		/* if requested, prepend a path to a relative file path */
		if (prependpath != NULL) {
			(void) sprintf(path, "%s/%s", prependpath, file);
			return (path);
		}
		/*
		 * if the database was built with a view path, return a
		 * full path so "cscope -d -f" does not have to be called
		 * from the build directory with the same view path
		 */
		if (dbvpndirs > 1) {
			for (i = 0; i < dbvpndirs; i++) {
				(void) sprintf(path,
				    "%s/%s", dbvpdirs[i], file);
				if (access(path, READ) != -1) {
					return (path);
				}
			}
		}
		(void) strcpy(path, file);	/* for lastfilepath check */
	}
	return (file);
}
