/*
	T. Jennings 
	164 Shipley
	San Francisco CA 94107

This is a copyrighted work of Tom Jennings, 1987. This source may
be used for non-commercial purposes only; please contact me for
other uses.


(k) All rights reversed.



This is a fast buffered "Level 1" type file system, layered on top
of sluggish MSDOS. It does nothing magic, only reasonably smart
disk buffering. Reads are deferred until the data is actually requested;
writes are deferred until more room in the buffer is actualyl needed;
seeks keep the pointer within the data in the buffer if possible, and
read/write/read/write sequences do not fool it.

Reads and writes larger than the local buffer size go directly to
MSDOS; this means that existing user code that does it's own buffering
with large buffers stays fast. 

This calls the MSDOS functions, here named _open for the MSDOS 
INT 21h handle open call, _close for close, etc. If you use only level 1,
ie. call "open()", etc, to use this system all you have to to is link 
it in and call fileinit(). No other changes are necessary. (Assuming
there are no undiscovered bugs :-)

Buffer size beyond 512 bytes don't seem to help that much. There is a
slight improvement with 1024 bytes, and little beyond that.

BE CAREFUL: The internal "position" mimics the MSDOS file "position"
by very careful arithmetic. It's real easy to mess it up, and was
the hardest thing of all to design & keep working. 



	written 27 Nov 86

	26 July 87
		flush error fixed; writes out 'count' bytes, not
		'index' bytes. Write flush was missed when a
		lseek(...0L,0) was done after a write; it depended
		on index being non zero.

	15 Aug 87
		Fixed iof[-1].... bug: accessing closed file handles.
		Wasnt checking < 0 handles.





fileinit(n)
	    Initialize the buffer system, making n files available.
	0 is returned if there is not enough room.

open(name,mode)
	Open a file, presumably for reading, though writing can be 
	used. The mode is passed through to DOS, and not used internally.
	This returns a handle, or -1 if none available or the file
	does not exist.

creat(name,mode)
	Similar to above, except creates the file if necessary.

write(f,buff,count)
read(f,buff,count)
	Same as standard level 1 I/O.

close(f)
	Closes the specified handle; if any writing was done, buffers
	are flushed first. Returns -1 if an error.


lseek(f,pos,mode)
	Seek within a file. This is not too effecient, it flushes the
	buffer if dirty, marks it empty, then seeks and lets the next
	read/write fill the buffer.

rline(f,buf,size)
	Read a line of text, null terminate it. Returns 0 when the file
	is empty.
*/

#define BUFSIZE 512

struct _iof {
	int handle;
	char buff[BUFSIZE];	/* was: char *buff; */
	char dirty;
	unsigned count;
	unsigned index;
	long pos;
	long fpos;
};

/*
	buffer & count & index:

	BUFFER holds (1) read-ahead data for file reads, (2) write-delay
	for file writes, or a combination of the two.

	INDEX is the pointer within the buffer from which data is read from
	or written to. When a read is done, if the buffer is empty (index == 
	count) the buffer is filled and 'count' is set to the amount read,
	0 - BUFSIZE. 

	For reading, index is advanced until it equals count, ie. empty.
	The buffer is filled by loading the buffer and setting count to
	the amt read & index to 0.

	For writing, index is advanced, and pushed 'count' up if the
	file is being grown. When the buffer is flushed, count bytes 
	are written, and index and count are zeroed.

	'pos' is the logical position of the beginning of the buffer,
	ie. the current logical position is pos + index. 'fpos' is the
	"physical" position of the host file being buffered, and is used
	to determine when a seek is needed. 

*/

static int _iomh = 0;		/* number of files */
static struct _iof *_iom;	/* I/O buffers */

/* Initialize the file system; allocate the specified number of io slots,
and mark them all as empty. */

fileinit(n)
int n;
{
unsigned memsize;

	memsize= sizeof(struct _iof) * n;	/* memory needed */
	if (getbuf(memsize,memsize,&_iom) == 0)	/* for the structs */
		return(0);
	_iomh= n;				/* allocated handles */

	while (n--) {				/* ready all the files */
		_iom[n].count= 0;
		_iom[n].index= 0;
		_iom[n].dirty= 0;
		_iom[n].pos= 0L;
		_iom[n].fpos= 0L;
		_iom[n].handle= -1;		/* handle (not open) */
	}
	return(memsize);
}
/* Open a buffered file. Return the handle or -1 if error. */

open(name,mode)
char *name;
int mode;
{

	return(fmake(name,mode,0));
}

/* Create a file for writing. */

creat(name,mode)
char *name;
int mode;
{
	return(fmake(name,mode,1));
}

/* Open or create a file, and ready it. Return the handle or -1 if none. */

fmake(name,mode,flag)
char *name;
int mode,flag;
{
int f;

	for (f= 0; f < _iomh; f++) {			/* find a free handle */
		if (_iom[f].handle == -1) break;
	}

	if (f >= _iomh) error("No free handles (fmake)");
	if (f >= _iomh) return(-1);			/* none available! */

	if (flag) _iom[f].handle= _creat(name,mode);	/* create it, */
	else _iom[f].handle= _open(name,mode);		/* or open it */

	if (_iom[f].handle == -1) return(-1);		/* cant open */

	_iom[f].count= 0;
	_iom[f].index= 0;
	_iom[f].dirty= 0;
	_iom[f].pos= 0L;
	_iom[f].fpos= 0L;
	return(f);					/* OK or not */
}
/* Debug: Display file statistics. */

filestat(f)
unsigned f;
{
struct _iof *t;

	cprintf("\r\nFile Number: %4d\r\n",f);
	if ((f >= _iomh) || (f < 0)) {
		cprintf("out of range\r\n");

	} else if (_iom[f].handle < 0) {
		cprintf("not open\r\n");
		return;				/* done frequently */

	} else {
		t= &(_iom[f]);
		cprintf("File Handle: %4d\r\n",t-> handle);
		cprintf("Dirty Flag:  %4d\r\n",t-> dirty);
		cprintf("Count:       %4d\r\n",t-> count);
		cprintf("Index:       %4d\r\n",t-> index);
		cprintf("Position:    %4ld\r\n",t-> pos);
		cprintf("File Pos:    %4ld\r\n",t-> fpos);
	}
	cprintf("Hit a Key: "); lconin();
	cprintf("\r\n");
}

/* Local function: Check the handle in range and return a pointer
to the file structure or 0 if bad. */

struct _iof *_gfh(f)
int f;
{
	if ((f >= _iomh) || (f < 0)) return(0);		/* handle out of range */
	if (_iom[f].handle < 0) return(0);		/* file closed */
	return(&_iom[f]);				/* ptr to struct */
}
/* Write to a file, return the number of bytes written. */

write(f,buff,count)
int f;
char *buff;
unsigned count;
{
unsigned i;
long lpos;
struct _iof *t;

	if ((t= _gfh(f)) == 0) return(0);

	if (count >= BUFSIZE) {				/* if large enough */
		if (! fflush(t,f)) return(0);		/* flush first, */
		t-> index= t-> count= 0;		/* buffer empty, */
		count= _write(t-> handle,buff,count);	/* write directly */
		t-> fpos += count;			/* new physical position */
		t-> pos= t-> fpos;			/* buffer offset */

	} else {					/* fits in the buffer, */
		if (count > (BUFSIZE - t-> index)) {	/* if not enough room */
			if (! fflush(t,f)) return(0);	/* flush first */
			t-> index= t-> count= 0;	/* clear both, */
			t-> pos= t-> fpos;		/* this is where we are */
		}
		t-> dirty= 1;				/* buffer is dirty */
		for (i= count; i > 0; i--) {
			t-> buff[t-> index++]= *buff++;
		}
	}
	if (t-> index > t-> count) 			/* if writing grows */
		t-> count= t-> index;			/* the file, track it */

	return(count);
}
/* If the buffer is dirty, flush it out to disk, returns 0 if error. This
writes out the entire buffer; seeks etc means any part of the buffer could
be dirty. */

fflush(t,f)
struct _iof *t;
int f;
{

	if (t-> dirty && t-> count) {			/* write if dirty, */
		if (t-> fpos != t-> pos) {		/* if wrong pos, */
			_lseek(t-> handle,t-> pos,0);	/* do the seek, */
			t-> fpos= t-> pos;		/* same */
		}
		if (_write(t-> handle,t-> buff,t-> count) != t-> count) {
			return(0);			/* write error! */
		}
		t-> fpos += t-> count;			/* new physical position */
	}
	t-> dirty= 0;					/* not dirty now */
	return(1);
}

/* Read from a buffered file. Return the number of bytes read. */

read(f,buff,count)
unsigned f;
char *buff;
unsigned count;
{
unsigned n;
struct _iof *t;

	if ((t= _gfh(f)) == 0) return(0);

	if (count >= BUFSIZE) {				/* if a large read, */
		if (! fflush(t,f)) return(0);		/* flush first, */
		t-> pos += t-> index;			/* correct pos */
		t-> index= t-> count= 0;		/* assume buffer empty */

		if (t-> fpos != t-> pos) {		/* if not at right spot */
			_lseek(t-> handle,t-> pos,0);	/* seek there, */
		}
		n= _read(t-> handle,buff,count);	/* read direct, */
		t-> pos += n;				/* update file position */
		t-> fpos= t-> pos;			/* logical position */
		return(n);
	}

	if (count > (t-> count - t-> index)) {		/* if not enough in buffer */
		if (! fflush(t,f)) return(0);		/* flush it, */
		ffill(t);				/* fill the buffer, */
	}

	for (n= count; n > 0; n--) {			/* read from buffer */
		if (t-> index >= t-> count) break;
		*buff++= t-> buff[t-> index++];
	}

	count -= n;					/* n == amt not read */
	return(count);
}

/* Fill the buffer with data, adjust count & index & position to match. */

ffill(t)
struct _iof *t;
{
	t-> pos += t-> index;				/* current logical pos */
	if (t-> pos != t-> fpos) {			/* if not there now, */
		_lseek(t-> handle,t-> pos,0);		/* seek there */
		t-> fpos= t-> pos;
	}
	t-> count= _read(t-> handle,t-> buff,BUFSIZE);	/* fill the buffer, */
	t-> fpos += t-> count;				/* update actual position */
	t-> index= 0;
}

/* Seek within a file. This flushes the buffer in case any writes were
done. */

long lseek(f,pos,mode)
int f;
long pos;
int mode;
{
long low;
unsigned n;
struct _iof *t;

	if ((t= _gfh(f)) == 0) return(0);

	switch (mode) {					/* make abs position */
		case 0: break;				/* from BOF */
		case 1: pos += t-> pos; break;		/* from current */
		case 2: 				/* from EOF; flush it, */
			fflush(t,f);
			_lseek(t-> handle,0L,2);	/* seek there, */
			t-> index= t-> count= 0;
			t-> pos= t-> fpos= _lseek(t-> handle,0L,1);		/* make all the same */
			break;
	}

	if ((pos < t-> pos) || (pos > (t-> pos + t-> count))) {
		fflush(t,f);				/* flush it, (write) */
		t-> index= t-> count= 0;		/* its empty (read) */
		_lseek(t-> handle,pos,0);		/* seek there */
		t-> fpos= t-> pos= pos;			/* actual position */

	} else t-> index= pos - t-> pos;		/* adjust accordingly */

	return(pos);
}

/* Close a buffered file, flush if necessary. Return 0 if error. 
KLUDGE: This is called at logoff time to close any files that arent
used anymore; it must be resistant to closes on not open files. */

close(f)
int f;
{
int e;
struct _iof *t;

	if ((t= _gfh(f)) == 0) return(0);

	e= fflush(t,f);
	_close(t-> handle);
	t-> handle= -1;
	return(e);
}

/* Read a line of text from the file, null terminate it. Function returns
zero if EOF. Deletes all CRs and Control-Zs from the stream. Lines are
terminated by LFs. */

rline(file,buf,len)
int file;
char *buf;
int len;
{
int i;
char notempty,c;

	i= 0; notempty= 0;
	--len;						/* compensate for added NUL */
	while (i < len) {
		if (! read(file,&c,1)) break;		/* stop if empty */
		if (c == 0x1a) continue;		/* totally ignore ^Z, */
		notempty= 1;				/* not empty */
		if (c == '\r') continue;		/* skip CR, */
		if (c == '\r' + 128) continue;		/* skip soft CR, */
		if (c == '\n') break;			/* stop if LF */
		buf[i++]= c;
	}
	buf[i]= '\0';
	return(notempty);
}
