/*
	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.


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 fastfile(). 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. 

Since the number of file buffers is allocated at fastfile() init
time, handles are obtained directly from DOS when the local system
runs out of room. You lose the performance, but at least it works.
The direct handles have 80h added to them to distinguish them. You
should increase the number of file buffers at fastfile() time to
accomodate your needs.


	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.

	25 Jan 88
		Added direct-DOS access at open/create when we run
		out of handles.

	27 Aug 90
		Changed "< 0" to "== -1" in _gfh(). Changed -1's to 
		NOTOPEN, changed all handles to unsigneds. Changed 
		_something_I_could_never_remember() to _isdosh().

fastfile(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 lower 8 bits of mode are passed through to DOS.
	This returns a handle, or -1 if none available or the file
	does not exist.

	The upper 8 bits of mode mean:
		0x100			open non-buffered
		...

creat(name,mode)
	Same as above, except creates the file if necessary.

write(f,buff,count)
read(f,buff,count)
	Same as standard level 1 I/O.

fflush(f)
	Flush output buffers of file f.

close(f)
	Closes the specified handle; if any writing was done, buffers
	are flushed first. Returns -1 if an error.

ftime(0,file,0L) Get file time/date
ftime(1,file,0L) Set file time/date
	Sets or gets the packed MSDOS time and date of the open
	file. The date and time words are concatenated; date is
	high word.

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.
*/

long lseek();			/* we define here */
long _lseek();			/* lseek() uses this for current pos */

/*
If defined, then we store the filename in the structures. Helps
debugging.
*/

#define NAME 1



#define NOTOPEN (-1)		/* file is not open value */

#define BUFSIZE 512

struct _iof {
	int handle;		/* 0..n, -1 */
#ifdef NAME
	char name[65];		/* full pathname */
#endif
	char buff[BUFSIZE];
	char dirty;
	unsigned count;
	unsigned index;
	long pos;
	long fpos;
};

/* Table of handles used for unbuffered files. */

#define DOSH_MAX 8			/* max. unbuffered files at one time */
struct {
	int handle;
#ifdef NAME
	char name[65];
#endif
} _dosh[DOSH_MAX];

/*
	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 unsigned _iomh = 0;	/* number of buffered files */
static struct _iof *_iom;	/* I/O buffers */
static long filetime;		/* used by ftime() */

/* Initialize the file system; allocate the specified number of io slots,
and mark them all as empty. */

fastfile(n)
int n;
{
unsigned memsize;
long sizmem();

	memsize= sizeof(struct _iof) * n;	/* memory needed */
	if (sizmem() < memsize) return(0);	/* cant do it */
	_iom= (struct _iof *) getmem(memsize);	/* yes we can */
	_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= NOTOPEN;	/* handle (not open) */
#ifdef NAME
		*_iom[n].name= '\0';
#endif
	}
	filetime= -1L;				/* mark as unused */

	for (n= 0; n < DOSH_MAX; n++) {
		_dosh[n].handle= NOTOPEN;	/* mark as unused */
#ifdef NAME
		*_dosh[n].name= '\0';
#endif
	}
	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. Handles
0 - (_iomh - 1) are buffered files; handles (_iomh) - (_iomh + DOSH_MAX - 1) are
unbuffered files. */

fmake(name,mode,flag)
char *name;
int mode,flag;
{
unsigned f;

	if (! (mode & 0x100)) {				/* if we want buffered */
		for (f= 0; f < _iomh; f++) {		/* find a free handle */
			if (_iom[f].handle == NOTOPEN) break;
		}

	} else f= _iomh;				/* else non-buffered */

	mode &= 0xff;					/* DOS gets only 8 bits */

/* If no buffers left (or forced un-buffered) locate a free slot in the
_dosh[] table. */

	if (f >= _iomh) {				/* none available! */
		for (f= 0; f < DOSH_MAX; f++) 		/* so find a direct-DOS */
			if (_dosh[f].handle == NOTOPEN) break; /* file handle */
		if (f >= DOSH_MAX) return(NOTOPEN);	/* no room there either! */

		if (flag) _dosh[f].handle= _creat(name,mode);
		else _dosh[f].handle= _open(name,mode);	/* direct to DOS */
		if (_dosh[f].handle == NOTOPEN) return(NOTOPEN);
#ifdef NAME
		strcpy(_dosh[f].name,name);		/* remember the name */
#endif
		return(f + _iomh);			/* return local handle */
	}

/* File is buffered. */

	if (flag) _iom[f].handle= _creat(name,mode);	/* create it, */
	else _iom[f].handle= _open(name,mode);		/* or open it */
	if (_iom[f].handle == NOTOPEN) return(NOTOPEN);	/* cant open */

#ifdef NAME
	strcpy(_iom[f].name,name);
#endif
	_iom[f].count= 0;
	_iom[f].index= 0;
	_iom[f].dirty= 0;
	_iom[f].pos= 0L;
	_iom[f].fpos= 0L;
	return(f);
}

/*******
report() {
int f;

	clprintf(0,"Buffered files:\r\n");
	for (f= 0; f < _iomh; f++) {
		if (! *_iom[f].name) continue;
		clprintf(0,"#%2d: %s %d",f,_iom[f].name,_iom[f].handle);
		if (_iom[f].handle == NOTOPEN) clprintf(0," NOT OPEN");
		clprintf(0,"\r\n");
	}
	clprintf(0,"Unbuffered files:\r\n");
	for (f= 0; f < DOSH_MAX; f++) {
		if (! *_dosh[f].name) continue;
		clprintf(0,"#%2d: %s %d",f + _iomh,_dosh[f].name,_dosh[f].handle);
		if (_dosh[f].handle == NOTOPEN) clprintf(0," NOT OPEN");
		clprintf(0,"\r\n");
	}
	clprintf(0,"\r\n");
}
*******/

/* Debug: Display file statistics. */

/** filestat(f)
unsigned f;
{
struct _iof *t;

	cprintf("\r\nFile Number: %d ",f);
	cprintf("_iomh = %d\r\n",_iomh);
	if (f < 0) cprintf("NOT OPEN\r\n");
	else if (f < _iomh) {
		t= &(_iom[f]);
		cprintf("Buffered file\r\n");
		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);

	} else if (f < _iomh + DOSH_MAX) {
		cprintf("Unbuffered, DOS handle = %d\r\n",isdosh(f));

	} else cprintf("OUT OF RANGE\r\n");

	cprintf("Hit a Key: "); lconin();
	cprintf("\r\n");

} **/

/* Local function: Check the handle and if within the DOS handle table,
return the DOS handle. Else returns NOTOPEN. (Which implies that it's a 
buffered file -- or a bad handle.) */

static int isdosh(f)
unsigned f;
{
	if ((f < _iomh) || (f >= _iomh + DOSH_MAX)) return(NOTOPEN);
	return(_dosh[f - _iomh].handle);
}

/* Local function: Check the handle in range and return a pointer
to the file structure or NOTOPEN if bad. */

static struct _iof *_gfh(f)
unsigned f;
{
	if (f >= _iomh) return((struct _iof *)NOTOPEN);		/* handle out of range */
	if (_iom[f].handle == NOTOPEN) return((struct _iof *)NOTOPEN);	/* file not open */
	return(&_iom[f]);				/* ptr to struct */
}

/* Write to a file, return the number of bytes written. */

write(f,buff,count)
unsigned f;
char *buff;
unsigned count;
{
unsigned i;
long lpos;
struct _iof *t;

	if (isdosh(f) != NOTOPEN) return(_write(isdosh(f),buff,count));
	if ((t= _gfh(f)) == (struct _iof *)NOTOPEN) 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. */

static _fflush(t,f)
struct _iof *t;
unsigned 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 (isdosh(f) != NOTOPEN) return(_read(isdosh(f),buff,count));
	if ((t= _gfh(f)) == (struct _iof *)NOTOPEN) 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. */

static 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;
}


/* Flush the buffers of the specified file. */

fflush(f)
unsigned f;
{
struct _iof *t;

	if (isdosh(f) != NOTOPEN) return(0);	/* N.A. for unbuffered */
	if ((t= _gfh(f)) == (struct _iof *)NOTOPEN) return(0);

	_fflush(t,f);
	t-> index= t-> count= 0;		/* its empty (read) */
}

/* Seek within a file. This flushes the buffer in case any writes were
done. */

long lseek(f,pos,mode)
unsigned f;
long pos;
int mode;
{
long low;
unsigned n;
struct _iof *t;

	if (isdosh(f) != NOTOPEN) return(_lseek(isdosh(f),pos,mode));
	if ((t= _gfh(f)) == (struct _iof *)NOTOPEN) 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;	/* 2nd seek avoids MSDOS 2.00 bug */
			pos= _lseek(t-> handle,0L,1);	/* EOF is "desired" position */
			t-> pos= t-> fpos= pos;		/* 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)
unsigned f;
{
int e;
struct _iof *t;

	if (isdosh(f) != NOTOPEN) {		/* if unbuffered, */
		if (filetime != -1L) _ftime(1,isdosh(f),&filetime);
		e= _close(isdosh(f));		/* close the file, */
		_dosh[f - _iomh].handle= NOTOPEN; /* mark it unused */

	} else if ((t= _gfh(f)) != (struct _iof *)NOTOPEN) {
		e= _fflush(t,f);
		if (filetime != -1L) _ftime(1,t-> handle,&filetime);
		_close(t-> handle);
		t-> handle= NOTOPEN;

	} else e= NOTOPEN;

	filetime= -1L;
	return(e);
}

/* Set or get the file time. */

ftime(flag,f,ft)
int flag,f;
long *ft;
{
struct _iof *t;

	if (flag) {
		filetime= *ft;			/* set, just store til close() */
		return;
	}
	if (isdosh(f) != NOTOPEN) _ftime(0,isdosh(f),ft);	/* get from DOS */
	else if ((t= _gfh(f)) != (struct _iof *)NOTOPEN) _ftime(0,t-> handle,ft);
}

/* 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)
unsigned 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);
}
