/*
 * 5799-WZQ (C) COPYRIGHT = NONE
 * LICENSED MATERIALS - PROPERTY OF IBM
 */
/* $Header:fio.c 12.0$ */
/* $ACIS:fio.c 12.0$ */
/* $Source: /ibm/acis/usr/src/old/sdb/RCS/fio.c,v $ */

#ifndef lint
static char *rcsid = "$Header:fio.c 12.0$";
#endif

static	char sccsid[] = "@(#)fio.c 4.2 8/17/82";
/*
 * sdb - a symbolic debugger for unix - source file access routines.
 */
#include "head.h"
#include <stdio.h>

/*
 * These procedures manage the source files examined by sdb,
 * providing access to lines by number and utilities for printing
 * and scrolling.  One file is kept open by these routines, and
 * line index tables are maintained for all files which have been
 * ``current'' at any time so far.  This makes line access trivial,
 * since the location of each line in the files is known,
 * although we get ``burned'' if the file is changed.
 * SHOULD WATCH THE MODTIME OF FILES AND REINDEX IF IT CHANGES.
 */

/*
 * Structure for files which have been ``indexed''.
 * Contains a pointer to the file name, a pointer to an
 * array of seek pointers for the lines in the file,
 * and a next link in a chain of these for all files we have indexed,
 * The currently open file is cinfo->; the chain of active files is finfo.
 */
struct	finfo {
	char	*name;			/* name of this file w/o common pfx */
	off_t	*lines;			/* array of seek pointers */
/* line i stretches from lines[i-1] to lines[i] - 1, if first line is 1 */
	int	nlines;			/* number of lines in file */
/* lines array actually has nlines+1 elements, so last line is bracketed */
	struct	finfo *next;		/* link in chain of known files */
} *finfo, *cfile;
FILE	*FIO;				/* current open file (only 1 now) */
char	fibuf[BUFSIZ];
/*
 * We use stdio when first reading the file, but thereafter
 * use our own routines, because we want to be able
 * to read backwards efficiently and avoid a tell() system
 * call on each line.  Fseekpt remebers where we are in the current
 * file.
 */
off_t	fseekpt;

/*
 * Make ``name'' the current source file, if it isn't already.
 * If we have never seen this file before, then we create a finfo
 * structure for it indexing the lines (this requires reading the
 * entire file and building an index, but is well worth it since
 * we otherwise have to brute force search the files all the time.)
 */
finit(name)
	char *name;
{
	char buf[BUFSIZ];
	register off_t *lp;
	
	if (cfile && !strcmp(cfile->name, name))
		return;			/* its already current, do nothing */
	/* IT WOULD BE BETTER TO HAVE A COUPLE OF FILE DESCRIPTORS, LRU */
	if (FIO) {
		fclose(FIO);
		FIO = NULL;
	}
	/*
	 * Paste the given name onto the common prefix (directory path)
	 * to form the full name of the file to be opened.
	 */
	strcpy(fp, name);
	if ((FIO = fopen(filework, "r")) == NULL) {
		nolines = 1;
		perror(filework);
		return;
	}
	setbuf(FIO, fibuf);
	fseekpt = -BUFSIZ;		/* putatively illegal */
	strcpy(curfile, name);
	/*
	 * See if we have alread indexed this file.
	 * If so, nothing much to do.
	 */
	for (cfile = finfo; cfile; cfile = cfile->next)
		if (!strcmp(cfile->name, name))
			return;
	/*
	 * Create a structure for this (new) file.
	 * Lines array grows 100 lines at a time.
	 * 1 extra so last line is bracketed.
	 */
	cfile = (struct finfo *)sbrk(sizeof (struct finfo));
	lp = cfile->lines = (off_t *)sbrk(101 * sizeof (off_t));
	*lp++ = 0;		/* line 1 starts at 0 ... */
	cfile->nlines = 0;
	/* IT WOULD PROBABLY BE FASTER TO JUST USE GETC AND LOOK FOR \n */
	while (fgets(buf, sizeof buf, FIO)) {
		if ((++cfile->nlines % 100) == 0)
			sbrk(100 * sizeof (off_t));
		/*
		 * Mark end of the cfile->nlines'th line
		 */
		lp[0] = lp[-1] + strlen(buf);
		lp++;
	}
	if (cfile->nlines == 0) {
		printf("%s: no lines in file\n", filework);
		cfile = 0;
		return;
	}
	/*
	 * Allocate space for the name, making sure to leave the
	 * break on a word boundary.
	 * IT WOULD BE MUCH BETTER TO USE MALLOC AND REALLOC IN SDB.
	 */
	sbrk(lp + ((strlen(name)+sizeof(off_t)-1)&~(sizeof(off_t)-1)));
	strcpy(cfile->name = (char *)lp, name);
	cfile->next = finfo;
	finfo = cfile;
}

/*
 * Get the current line (fline) into fbuf
 */
fgetline()
{
	register off_t *op = &cfile->lines[fline-1];
	int o, n;

	n = op[1] - op[0];
	fbuf[n] = 0;
	/*
	 * Case 1.  Line begins in current buffer.
	 *
	 * Compute the number of characters into the buffer where
	 * the line starts.  If this offset plus its length is greater
	 * than BUFSIZ, then this line splits across a buffer boundary
	 * so take the rest of this buffer and the first part of the next.
	 * Otherwise just take a chunk of this buffer.
	 */
	if (*op >= fseekpt && *op < fseekpt + BUFSIZ) {
case1:
		o = op[0] - fseekpt;
		if (o + n > BUFSIZ) {
			strncpy(fbuf, fibuf+o, BUFSIZ-o);
			fseekpt += BUFSIZ;
			read(fileno(FIO), fibuf, BUFSIZ);
			strncpy(fbuf+BUFSIZ-o, fibuf, n-(BUFSIZ-o));
		} else
			strncpy(fbuf, fibuf+o, n);
		return;
	}
	/*
	 * Case 2.  Line ends in current buffer.
	 *
	 * If the line ends in this buffer (but doesn't begin in
	 * it or else we would have had case 1) take the beginning
	 * part of the buffer (end of the line) and then back up and
	 * get the rest of the line from the end of the previous block.
	 */
	if (op[1]-1 >= fseekpt && op[1] <= fseekpt+BUFSIZ) {
		o = op[1] - fseekpt;
		strncpy(fbuf+n-o, fibuf, o);
		fseekpt -= BUFSIZ;
		lseek(fileno(FIO), fseekpt, 0);
		read(fileno(FIO), fibuf, BUFSIZ);
		strncpy(fbuf, fibuf+op[0]-fseekpt, n-o);
		return;
	}
	/*
	 * Case 3.  Line not in current buffer at all.
	 *
	 * Read in the buffer where the line starts and then go
	 * back and handle as case 1.
	 */
	fseekpt = (op[0] / BUFSIZ) * BUFSIZ;
	lseek(fileno(FIO), fseekpt, 0);
	read(fileno(FIO), fibuf, BUFSIZ);
	goto case1;
}

/*
 * Advance current line, end-around (like for / search).
 */
fnext()
{
	
	if (cfile == 0)
		return;
	if (fline == cfile->nlines) {
		fline = 1;
	} else
		fline++;
	fgetline();
}

/*
 * Retreat the current line, end around.
 */
fprev()
{

	if (cfile == 0)
		return;
	if (fline == 1)
		fline = cfile->nlines;
	else
		fline--;
	fgetline();
}

/*
 * Print the current line.
 */
fprint()
{
	register char *p;
	
	if (cfile == 0) {
		error("No lines in file");
		return;
	}
	printf("%d: %s", fline, fbuf);
}

/*
 * Make line `num' current.
 */
ffind(num)
	register int num;
{
	
	if (cfile == 0)
		return;
	if (num > cfile->nlines)
		error("Not that many lines in file");
	else if (num <= 0)
		error("Zero or negative line?");
	else {
		fline = num;
		fgetline();
	}
}

/*
 * Go back n lines.
 */
fback(n)
{
	int i;
	
	if (cfile == 0)
		return (0);
	if (n > fline - 1)
		n = fline - 1;
	fline -= n;
	fgetline();
	return (n);
}

/*
 * Go forwards n lines.
 */
fforward(n)
	int n;
{
	register int fnext;
	
	if (cfile == 0)
		return(0);
	if (fline + n > cfile->nlines)
		n = cfile->nlines - fline;
	fline += n;
	fgetline();
	return (n);
}

/*
 * Print (upto) n lines, returning number printed.
 */
fprintn(n)
	int n;
{
	register int i;
	
	if (cfile == 0) {
		error("No lines in file");
		return (0);
	}
	for (i = 1; i <= n; i++) {
		fprint();
		if (fline == cfile->nlines || i == n)
			return(i);
		fnext();
	}
	return (n);
}
