/*
 *   Copyright (C) 1988-2006 Lucent Technologies Inc. and others.
 *   All Rights Reserved.
 */

/*	cscope - interactive C symbol cross-reference
 *
 *	display functions
 */

#include "global.h"
#ifdef CCS
#include "sgs.h"	/* SGS and RELEASE */
#else
#include "version.h"	/* FILEVERSION and FIXVERSION */
#endif
#include <curses.h>	/* COLS and LINES */
#include <errno.h>	/* errno */
#include <setjmp.h>	/* jmp_buf */

/* see if the function column should be displayed */
#define	displayfcn()	(extendedmenu == NO ? field <= ASSIGN : field < FILENAME)

#define	MINCOLS	68	/* minimum columns for 3 digit Lines message numbers */

int	*displine;		/* screen line of displayed reference */
int	disprefs;		/* displayed references */
int	field;			/* input field */
unsigned fldcolumn;		/* input field column */
int	mdisprefs;		/* maximum displayed references */
int	selectlen;		/* selection number field length */
int	nextline;		/* next line to be shown */
int	topline = 1;		/* top line of page */
int	bottomline;		/* bottom line of page */
int	totallines;		/* total reference lines */
FILE	*refsfound;		/* references found file */
FILE	*nonglobalrefs;		/* non-global references file */

static	int	fldline;		/* input field line */
static	int	subsystemlen;		/* OGS subsystem name display field length */
static	int	booklen;		/* OGS book name display field length */
static	int	filelen;		/* file name display field length */
static	int	fcnlen;			/* function name display field length */
static	int	symlen;			/* symbol display field length */
static	jmp_buf	env;			/* setjmp/longjmp buffer */
static	int	lastdispline;		/* last displayed reference line */
static	char	lastmsg[MSGLEN + 1];	/* last message displayed */
static	int	numlen;			/* line number display field length */
static	char	depthstring[] = "Depth: ";
static	char	helpstring[] = "Press the ? key for help";

#if BSD && !sun	/* compiler bug workaround */
#define	Void	char *
#else
#define	Void	void
#endif

#if defined( __STDC__ ) || defined( __BORLANDC__ )
void	dispinit(void);
void	display(void);
void	initfield(void);
void	setfield(void);
void	atfield(void);
void	jumpback(int sig);
BOOL	search(void);
BOOL	writerefsfound(void);
void	countrefs(void);
void	myperror(char *text);
void	putmsg(char *msg);
void	clearmsg2(void);
void	putmsg2(char *msg);
void	seekline(int line);
void	ogsnames(char *file, char **subsystem, char **book);
char	*pathcomponents(char *path, int components);
#else
char	*findgreppat();
Void	findallfcns(), findassignments(), findcalling(), findcalledby(), finddef(),
	findincludes(), finddefine(), findtypedef(), findglobals(), findlocals(),
	findstructures(), findparameters(), findccalling(), findccalls(),
	findfile(), findinclude(), findsymbol(), findregexpfcns();
#endif

typedef char *(*FP)();	/* pointer to function returning a character pointer */

typedef struct	{
	char	*text1;
	char	*text2;
	FP	findfcn;
	enum {
		EGREP,
		REGCMP
	} patterntype;
} FIELDISP, *FIELDISPTR;

/* Function Menu */
static FIELDISP normalfields[FIELDS + 2] = {	/* last two searches are not part of the cscope display */
#ifdef MULTILINGUAL
	{ "Find this", "symbol",			(FP) findsymbol,	REGCMP	},
#else
	{ "Find this", "C symbol",			(FP) findsymbol,	REGCMP	},
#endif
	{ "Find this", "definition",			(FP) finddef,		REGCMP	},
	{ "Find", "functions called by this function",	(FP) findcalledby,	REGCMP	},
	{ "Find", "functions calling this function",	(FP) findcalling,	REGCMP	},
	{ "Find", "assignments to",			(FP) findassignments,	REGCMP	},
	{ "Change this", "grep pattern",		findgreppat,		EGREP	},
	{ "Find this", "egrep pattern",			findegreppat,		EGREP	},
	{ "Find this", "file",				(FP) findfile,		REGCMP	},
	{ "Find", "files #including this file",		(FP) findinclude,	REGCMP	},
	{ "Find all", "function/class definitions",	(FP) findallfcns,	REGCMP	},
	{ "Find all", "function/class definitions",	(FP) findregexpfcns,	REGCMP	},
};

/* Extended Menu */
static FIELDISP extrafields[FIELDS + 2] = {	/* last two searches are not part of the cscope display */
	{ "Find this", "type definition",		(FP) findtypedef,	REGCMP	},
	{ "Find this", "preprocessor definition",	(FP) finddefine,	REGCMP	},
	{ "Find", "parameters of this function",	(FP) findparameters,	REGCMP	},
	{ "Find", "functions calling this function",	(FP) findccalling,	REGCMP	},
	{ "Find this", "global definition",		(FP) findglobals,	REGCMP	},
	{ "Find this", "local definition",		(FP) findlocals,	REGCMP	},
	{ "Find this", "structure definition",		(FP) findstructures,	REGCMP	},
	{ "Find this", "file",				(FP) findfile,		REGCMP	},
	{ "Find this", "preprocessor include",		(FP) findincludes,	REGCMP	},
	{ "Find", "definition of this function",	(FP) findccalls,	REGCMP	},
	{ "Find all", "function/class definitions",	(FP) findregexpfcns,	REGCMP	},
};

static FIELDISPTR fields;

char	*pathcomponents();
void	ogsnames();

/* initialize display parameters */

void
dispinit()
{
	/* calculate the maximum displayed reference lines */
	lastdispline = FLDLINE - 2;
	mdisprefs = lastdispline - REFLINE + 1;
	if (mdisprefs <= 0) {
		(void) printw("%s: window must be at least %d lines high", argv0, FIELDS + 6);
		myexit(1);
	}
	if (COLS < MINCOLS) {
		(void) printw("%s: window must be at least %d columns wide", argv0, MINCOLS);
		myexit(1);
	}
	if (!mouse) {
		if (returnrequired == NO && mdisprefs > 9) {
			mdisprefs = 9;	/* single digit selection number */
		}
		/* calculate the maximum selection number width */
		(void) sprintf(newpat, "%d", mdisprefs);
		selectlen = strlen(newpat);
	}
	/* allocate the displayed line array */
	displine = (int *) mymalloc(mdisprefs * sizeof(int));
}

/* display a page of the references */

void
display()
{
	char	*subsystem;		/* OGS subsystem name */
	char	*book;			/* OGS book name */
	char	file[PATHLEN + 1];	/* file name */
	char	function[PATLEN + 1];	/* function name */
	char	linenum[NUMLEN + 1];	/* line number */
	char	symbol[PATLEN + 1];	/* symbol */
	int	screenline;		/* screen line number */
	int	width;			/* source line display width */
	register int	i;
	register char	*s;

	(void) erase();

	/* if there are no references */
	if (totallines == 0) {
		if (*lastmsg != '\0') {
			(void) addstr(lastmsg);	/* redisplay any message */
		}
		else {
#if CCS
			if (displayversion == YES) {
				(void) printw("%s%s - %s", SGS, argv0, RELEASE);
			}
			else {
				(void) printw("cscope");
			}
#else
			(void) printw("%s version %d%s", argv0, FILEVERSION, FIXVERSION);
#endif
			(void) move(0, COLS - (int) sizeof(helpstring));
			(void) addstr(helpstring);
		}
	}
	else {	/* display the pattern */
		if (changing == YES) {
			(void) printw("Change \"%s\" to \"%s\"", pattern, newpat);
		}
		else {
			(void) printw("%c%s: %s", toupper(fields[field].text2[0]),
				fields[field].text2 + 1, pattern);
		}
		/* display the cscope invocation nesting depth */
		if (cscopedepth > 1) {
			(void) move(0, COLS - (int) sizeof(depthstring) - 2);
			(void) addstr(depthstring);
			(void) printw("%d", cscopedepth);
		}
		/* display the column headings */
		(void) move(2, selectlen + 1);
		if (ogs == YES && field != FILENAME) {
			(void) printw("%-*s ", subsystemlen, "Subsystem");
			(void) printw("%-*s ", booklen, "Book");
		}
		if (dispcomponents > 0) {
			(void) printw("%-*s ", filelen, "File");
		}
		if (displayfcn()) {
			(void) printw("%-*s ", fcnlen, "Function");
		}
		if (field != FILENAME) {
			(void) addstr("Line");
		}
		if (field != FILENAME && extendedmenu == YES) {
			(void) addch(' ');
			(void) addstr("Ccalls");
		}
		(void) addch('\n');

		/* if at end of file go back to beginning */
		if (nextline > totallines) {
			seekline(1);
		}
		/* calculate the source text column */
		width = COLS - selectlen - numlen - 2;
		if (extendedmenu == YES) {
			width -= symlen + 1;
		}
		if (ogs == YES) {
			width -= subsystemlen + booklen + 2;
		}
		if (dispcomponents > 0) {
			width -= filelen + 1;
		}
		if (displayfcn()) {
			width -= fcnlen + 1;
		}
		/* until the max references have been displayed or 
		   there is no more room */
		topline = nextline;
		for (disprefs = 0, screenline = REFLINE;
		    disprefs < mdisprefs && screenline <= lastdispline;
		    ++disprefs, ++screenline) {
			
			/* read the reference line */
			if (extendedmenu == NO) {
				if (fscanf(refsfound, "%s%s%s %[^\n]", file,
				    function, linenum, yytext) < 4) {
					break;
				}
			} else {
				if (fscanf(refsfound, "%s%s%s%s %[^\n]", file,
				    function, linenum, symbol, yytext) < 5) {
					break;
				}
			}
			++nextline;
			displine[disprefs] = screenline;
			
			/* if no mouse, display the selection number */
			if (!mouse) {
				(void) printw("%*d", selectlen, disprefs + 1);
			}
			/* display any change mark */
			if (changing == YES && 
			    change[topline + disprefs - 1] == YES) {
				(void) addch('>');
			}
			else {
				(void) addch(' ');
			}
			/* display the file name */
			if (field == FILENAME) {
				(void) printw("%-.*s\n", COLS - 3, file);
				continue;
			}
			/* if OGS, display the subsystem and book names */
			if (ogs == YES) {
				ogsnames(file, &subsystem, &book);
				(void) printw("%-*.*s ", subsystemlen,
				    subsystemlen, subsystem);
				(void) printw("%-*.*s ", booklen, booklen, book);
			}
			/* display the requested path components */
			if (dispcomponents > 0) {
				(void) printw("%-*.*s ", filelen, filelen, 
				    pathcomponents(file, dispcomponents));
			}
			/* display the function name */
			if (displayfcn()) {
				(void) printw("%-*.*s ", fcnlen, fcnlen, function);
			}
			/* display the line number */
			(void) printw("%*s ", numlen, linenum);

			if (extendedmenu == YES) {
				/* display the symbol */
				(void) printw("%-*.*s ", symlen, symlen, symbol);
			}

			/* there may be tabs in egrep output */
			while ((s = strchr(yytext, '\t')) != NULL) {
				*s = ' ';
			}
#ifdef FOR_WIN32
			/* there may be CRLF in egrep output */
			while ((s = strchr(yytext, '\r')) != NULL) {
				*s = ' ';
			}
#endif
			/* display the source line */
			s = yytext;
			for (;;) {
				/* see if the source line will fit */
				if ((i = strlen(s)) > width) {
					
					/* find the nearest blank */
					for (i = width; s[i] != ' ' && i > 0; --i) {
						;
					}
					if (i == 0) {
						i = width;	/* no blank */
					}
				}
				/* print up to this point */
				(void) printw("%.*s", i, s);
				s += i;
				
				/* if line didn't wrap around */
				if (i < width) {
					(void) addch('\n');	/* go to next line */
				}
				/* skip blanks */
				while (*s == ' ') {
					++s;
				}
				/* see if there is more text */
				if (*s == '\0') {
					break;
				}
				/* if the source line is too long */
				if (++screenline > lastdispline) {
					
					/* if this is the first displayed line,
					   display what will fit on the screen */
					if (topline == nextline - 1) {
						goto endrefs;
					}
					/* erase the reference */
					while (--screenline >= displine[disprefs]) {
						(void) move(screenline, 0);
						(void) clrtoeol();
					}
					++screenline;
					 
					/* go back to the beginning of this reference */
					--nextline;
					seekline(nextline);
					goto endrefs;
				}
				/* indent the continued source line */
				(void) move(screenline, COLS - width);
			}

		}
	endrefs:
		/* check for more references */
		bottomline = nextline;
		if (bottomline - topline < totallines) {
			(void) move(FLDLINE - 1, 0);
			(void) standout();
			(void) printw("%*s", selectlen + 1, "");
			if (bottomline - 1 == topline) {
				(void) printw("Line %d", topline);
			}
			else {
				(void) printw("Lines %d-%d", topline, bottomline - 1);
			}
			(void) printw(" of %d, press the space bar to display next lines",
			    totallines);
			(void) standend();
		}
	}
	/* display the input fields */
	(void) move(FLDLINE, 0);
	for (i = 0; i < FIELDS; ++i) {
		(void) printw("%s %s:\n", fields[i].text1, fields[i].text2);
	}
	drawscrollbar(topline, nextline, totallines);
}

/* initialise the fields array */
void
initfield()
{
	/* initialise the fields array */
	if (extendedmenu == NO)
		fields = normalfields;
	else
		fields = extrafields;
}


/* set the cursor position for the field */
void
setfield()
{
	fldline = FLDLINE + field;
	fldcolumn = strlen(fields[field].text1) + strlen(fields[field].text2) + 3;
}

/* move to the current input field */

void
atfield()
{
	(void) move(fldline, (int) fldcolumn);
}

/* search for the symbol or text pattern */

#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
/*ARGSUSED*/
SIGTYPE
jumpback(sig)
{
	longjmp(env, 1);
}
#endif

BOOL
search()
{
	char	*egreperror = NULL;	/* egrep error message */
	FINDINIT rc = NOERROR;		/* findinit return code */
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
	SIGTYPE	(*savesig)();		/* old value of signal */
#endif
	FP	f;			/* searching function */
	char	*s;
	BOOL	allfiles = YES;		/* assume a search of all files */
	char	file[PATHLEN + 1];	/* file name */
	char	function[PATLEN + 1];	/* function name */
	char	linenum[NUMLEN + 1];	/* line number */
	register int	i;
	register int	c;
	
	/* note: the pattern may have been a cscope argument */
	if (caseless == YES) {
		for (s = pattern; *s != '\0'; ++s) {
			*s = tolower(*s);
		}
	}
	/* open the references found file for writing */
	if (writerefsfound() == NO) {
		return(NO);
	}
	/* find the pattern - stop on an interrupt */
	if (linemode == NO) {
		putmsg("Searching");
	}
	initprogress();
	if (setjmp(env) == 0) {
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
		savesig = signal(SIGINT, jumpback);
#endif
		f = fields[field].findfcn;
		if (fields[field].patterntype == EGREP) {
			egreperror = (*f)(pattern);
		}
		else {
			if ((nonglobalrefs = fopen(temp2, "w")) == NULL) {
				cannotopen(temp2);
				return(NO);
			}
			if ((rc = findinit()) == NOERROR) {
				(void) dbseek(0L); /* goto the first block */
				(*f)();
				findcleanup();
				
				/* append the non-global references */
				(void) freopen(temp2, "r", nonglobalrefs);
				while ((c = getc(nonglobalrefs)) != EOF) {
					(void) putc(c, refsfound);
				}
			}
			(void) fclose(nonglobalrefs);
		}
	}
#if !defined(__BORLANDC__) && !defined(FOR_WIN32)
	(void) signal(SIGINT, savesig);
#endif
	/* reopen the references found file for reading */
#if defined(__BORLANDC__) || defined(FOR_WIN32)
	(void) freopen(temp1, "rt", refsfound);
#else
	(void) freopen(temp1, "r", refsfound);
#endif
	nextline = 1;
	totallines = 0;
	
	/* see if it is empty */
	if ((c = getc(refsfound)) == EOF) {
		if (egreperror != NULL) {
			(void) sprintf(lastmsg, "Egrep %s in this pattern: %s", 
				egreperror, pattern);
		}
		else if (rc == NOTSYMBOL) {
#ifdef MULTILINGUAL
			(void) sprintf(lastmsg, "This is not a symbol: %s", 
				pattern);
#else
			(void) sprintf(lastmsg, "This is not a C symbol: %s", 
				pattern);
#endif
		}
		else if (rc == REGCMPERROR) {
			(void) sprintf(lastmsg, "Error in this regcmp(3X) regular expression: %s", 
				pattern);
		}
		else {
			(void) sprintf(lastmsg, "Could not find the %s: %s", 
				fields[field].text2, pattern);
		}
		return(NO);
	}
	/* put back the character read */
	(void) ungetc(c, refsfound);

#if 0	/* dump the references for debugging */
	while ((c = getc(refsfound)) != EOF) {
		(void) putc(c, stdout);
	}
	rewind(refsfound);
#endif
	countrefs();
	if (filterfiles == YES && totallines > 0) {
		if((subdirpat[0] != '\0') && strcmp(subdirpat,".")) {
			allfiles = NO;	/* subset of all files */
			if((regexp = regcmp(subdirpat,(char *)NULL)) == NULL) {
				(void) sprintf(lastmsg, "Error in this regcmp(3X) regular expression: %s", 
					subdirpat);
				return(NO);
			}
		}

#if defined(__BORLANDC__) || defined(FOR_WIN32)
		if ((nonglobalrefs = fopen(temp2, "wt")) == NULL) {
#else
		if ((nonglobalrefs = fopen(temp2, "w")) == NULL) {
#endif
			cannotopen(temp2);
			return(NO);
		}
		while ((i = fscanf(refsfound, "%250s%250s%6s %5000[^\n]", 
		    file, function, linenum, yytext)) != EOF) {
			/* Skip the file if it doesn't match the pattern */
			if((allfiles == NO) && (regex(regexp,file) == NULL)) {
				continue;	/* skip this file */
			}
			fprintf(nonglobalrefs, "%s %s %s %s\n", file,
				function, linenum, yytext);
		}
		(void) fclose(nonglobalrefs);
/*		(void) fclose(refsfound);*/
		if (readrefs(temp2) == NO) {
			putmsg("Ignoring empty output of ^ command");
			return(NO);
		}
		return(YES);
	}
	return(YES);
}

/* open the references found file for writing */

BOOL
writerefsfound()
{
	if (refsfound == NULL) {
#if defined(__BORLANDC__) || defined(FOR_WIN32)
		if ((refsfound = fopen(temp1, "wt")) == NULL) {
#else
		if ((refsfound = fopen(temp1, "w")) == NULL) {
#endif
			cannotopen(temp1);
			return(NO);
		}
	}
#if defined(__BORLANDC__) || defined(FOR_WIN32)
	else if (freopen(temp1, "wt", refsfound) == NULL) {
#else
	else if (freopen(temp1, "w", refsfound) == NULL) {
#endif
		putmsg("Cannot reopen temporary file");
		return(NO);
	}
	return(YES);
}

/* count the references found */

void
countrefs()
{
	char	*subsystem;		/* OGS subsystem name */
	char 	*book;			/* OGS book name */
	char	file[PATHLEN + 1];	/* file name */
	char	function[PATLEN + 1];	/* function name */
	char	linenum[NUMLEN + 1];	/* line number */
	char	symbol[PATLEN + 1];	/* symbol */
	int	mylen;
	register int	i,j;

	/* count the references found and find the length of the file,
	   function, and line number display fields */
	subsystemlen = 9;	/* strlen("Subsystem") */
	booklen = 4;		/* strlen("Book") */
	filelen = 4;		/* strlen("File") */
	fcnlen = 8;		/* strlen("Function") */
	numlen = 4;		/* strlen("Line") */
	symlen = 6;		/* strlen("Ccalls") */
	i = 1;
	while (i != EOF) {
		if (extendedmenu == NO) {
			i = fscanf(refsfound, "%250s%250s%6s %5000[^\n]",
			    file, function, linenum, yytext);
			j = 4;
		} else {
			i = fscanf(refsfound, "%250s%250s%6s%250s %5000[^\n]",
			    file, function, linenum, symbol, yytext);
			j = 5;
		}
		mylen = strlen(yytext);
		if (i == EOF) continue;
		if (i != j || !isgraph(*file) || !isgraph(*function) ||
		    !isdigit(*linenum)) {
			
			/* This generally happens when user specified an object file as input */
			char msg[256];
			if(i) {
				if(strlen(file) > 180) file[180] = '\0'; /* avoid overflowing message buffer */
			}
			else {
				strcpy(file,"<file token not read>");
			}
			sprintf(msg,"Refs file has bad format (binary file specified?), tokens=%d, file=%s, len=%d",
				i,file,mylen);
			putmsg(msg);
			totallines = 0;
			return;
		}
		if ((i = strlen(pathcomponents(file, dispcomponents))) > filelen) {
			filelen = i;
		}
		if (ogs == YES) {
			ogsnames(file, &subsystem, &book);
			if ((i = strlen(subsystem)) > subsystemlen) {
				subsystemlen = i;
			}
			if ((i = strlen(book)) > booklen) {
				booklen = i;
			}
		}
		if ((i = strlen(function)) > fcnlen) {
			fcnlen = i;
		}
		if ((i = strlen(linenum)) > numlen) {
			numlen = i;
		}
		if (extendedmenu == YES) {
			if ((i = strlen(symbol)) > symlen) {
				symlen = i;
			}
		}
		++totallines;
		if (mylen == 5000) {
			i = fscanf(refsfound,"%*[^\n]");
		}
	}
	rewind(refsfound);

	/* restrict the width of displayed columns */
	if (extendedmenu == NO) {
		i = (COLS - 5) / 3;
	} else {
		i = (COLS - 5) / 4;
	}
	if (ogs == YES) {
		i = (COLS - 7) / 5;
	}
	if (filelen > i && i > 4) {
		filelen = i;
	}
	if (subsystemlen > i && i > 9) {
		subsystemlen = i;
	}
	if (booklen > i && i > 4) {
		booklen = i;
	}
	if (fcnlen > i && i > 8) {
		fcnlen = i;
	}
	if (extendedmenu == YES) {
		if (symlen > i && i > 6) {
			symlen = i;
		}
	}
}

/* print error message on system call failure */

void
myperror(text) 
char	*text; 
{
#if cygwin
#define sys_nerr _sys_nerr
#define sys_errlist _sys_errlist
#elif !darwin
	extern	int	errno, sys_nerr;
#if ilinux
	extern	const char	*const sys_errlist[];
#elif !darwin
	extern	char	*sys_errlist[];
#endif
#endif
	char	msg[MSGLEN + 1];	/* message */
	register char	*s;

	s = "Unknown error";
	if (errno < sys_nerr) {
		s = (char *)sys_errlist[errno];
	}
	(void) sprintf(msg, "%s: %s", text, s);
	putmsg(msg);
}

/* putmsg clears the message line and prints the message */

void
putmsg(msg) 
char	*msg; 
{
	if (incurses == NO) {
		/* Avoid changing read-only memory, such as on Linux */
		char nmsg[256];
		(void) strncpy(nmsg,msg,255);
		*nmsg = tolower(*nmsg);
		(void) fprintf(stderr, "%s: %s\n", argv0, nmsg);
	}
	else {
		(void) move(MSGLINE, 0);
		(void) clrtoeol();
		(void) addstr(msg);
		(void) refresh();
	}
	(void) strncpy(lastmsg, msg, sizeof(lastmsg) - 1);
}

/* clearmsg2 clears the second message line */

void
clearmsg2() 
{
	if (incurses == YES) {
		(void) move(MSGLINE + 1, 0);
		(void) clrtoeol();
	}
}

/* putmsg2 clears the second message line and prints the message */

void
putmsg2(msg) 
char	*msg; 
{
	if (incurses == NO) {
		putmsg(msg);
	}
	else {
		clearmsg2();
		(void) addstr(msg);
		(void) refresh();
	}
}

/* position the references found file at the specified line */

void
seekline(line) 
int	line; 
{
	register int	c;

	/* verify that there is a references found file */
	if (refsfound == NULL) {
		return;
	}
	/* go to the beginning of the file */
	rewind(refsfound);
	
	/* find the requested line */
	nextline = 1;
	while (nextline < line && (c = getc(refsfound)) != EOF) {
		if (c == '\n') {
			nextline++;
		}
	}
}

/* get the OGS subsystem and book names */

void
ogsnames(file, subsystem, book)
char	*file;
char	**subsystem;
char	**book;
{
	static	char	buf[PATHLEN + 1];
	register char	*s, *slash;

	*subsystem = *book = "";
	(void) strcpy(buf,file);
	s = buf;
	if (*s == '/') {
		++s;
	}
	while ((slash = strchr(s, '/')) != NULL) {
		*slash = '\0';
		if (strlen(s) >= 3 && strncmp(slash - 3, ".ss", 3) == 0) {
			*subsystem = s;
			s = slash + 1;
			if ((slash = strchr(s, '/')) != NULL) {
				*book = s;
				*slash = '\0';
			}
			break;
		}
		s = slash + 1;
	}
}

/* get the requested path components */

char *
pathcomponents(path, components)
char	*path;
int	components;
{
	int	i;
	register char	*s;
	
	s = path + strlen(path) - 1;
	for (i = 0; i < components; ++i) {
#if defined(__BORLANDC__) || defined(FOR_WIN32)
		while (s > path && *--s != '/' && *s != '\\') {
#else
		while (s > path && *--s != '/') {
#endif
			;
		}
	}
#if defined(__BORLANDC__) || defined(FOR_WIN32)
	if (s > path && (*s == '/' || *s == '\\')) {
#else
	if (s > path && *s == '/') {
#endif
		++s;
	}
	return(s);
}

