#define	STDIOEDIT	10

/* tell(fd) */
/* seek(fd, offset, origin) */
/* open(filename,mode) mode = 0,1,2 */
/* creat(filename) */
/* close(fd) */
/* fopen(filename,mode,buffer_size) mode = "r","w","a","rw" */
/* write(fd,buffer,byte_count) */
/* read(fd,buffer,byte_count) */
/* cio(mode,fcb_pointer,buffer,byte_count) */
/* cpmio(mode,fcb_pointer,buffer) */

/*
int * allocs;
#include "alloc.h"
*/

#define	TRUE		1
#define	FALSE		0
#define	ERROR		(-1)
#define	BDOSERR		255

#define	BCE		1	/* Byte count error */
#define	NOFD		2	/* No open FD's */
#define	NOFCB		3	/* All fcb area in use */
#define	INVFD		4	/* Invalid FD, unopened file */
#define	INVMODE		5	/* IO type mismatch */
#define	OPENERR		6	/* Error on CC_OPEN */
#define	CREATERR	7	/* Error on CC_CREAT */
#define	FSPECERR	8	/* Invalid filename */
#define	BADDEV		9	/* Device out of range */
#define	CLOSEERR	10	/* bdos error in close */
#define	INVREC		11	/* Invalid record in seek */
#define	INVSEEK		12
#define	BUFOVF		30
#define	INVBUF		31
#define	READERR		32
#define	EOFERR		33
#define	NOGETC		34

#define	REC_SIZE	128	/* Byte count of a CP/M record */
#define	EXT_SIZE	128	/* Records in a extent 1..EXT_SIZE */
#define	FCB_SIZE	33	/* Bytes in a CP/M fcb */
#define	FCB_ASIZE	37	/* Full fcb size */
#define	SOVER		6	/* Bytes to over allocate seq. buf. */
#define	FCB_READ	0
#define	FCB_WRITE	1
#define	FCB_R_W		2
#define	INIT_DMA	0x80	/* Each open sets dma here */
#define	START		0	/* Seek from beginning */
#define	CURRENT		1	/* Seek from current record */
#define	LAST		2	/* Seek from end */

/* Byte offsets into FCB */
#define	EX		12	/* Byte address of extent value */
#define	RC		15	/* Record count in extent */
#define	NR		32	/* Byte address of next record value */
#define	FM		33	/* File mode */
#define	ME		34	/* Maximum extent */
#define	PS		35	/* Pointer to seq buffer (2 bytes) */

/* Integer offsets into seq. buffer */
#define	AP		0	/* Active pointer */
#define	AC		1	/* Active count */
#define	BS		2	/* Buffer size */
#define	BU		3	/* Start of buffer */

/* BDOS calling codes */
#define	SET_DRIVE	14	/* Select disk drive */
#define	CC_OPEN		15
#define	CC_CLOSE	16
#define	CC_FIND		17	/* Find first occurence */
#define	CC_NEXT		18	/* Find next occurence */
#define	CC_DELETE	19
#define	CC_READ		20	/* Read next CP/M record */
#define	CC_WRITE	21	/* Write next CP/M record */
#define	CC_CREAT	22
#define	GET_DRIVE	25	/* Interrogate drive number */
#define	SET_DMA		26

int	errno;

seek(fd,offset,origin)
char	*fd;
int	offset;
char	origin;
{
	register int t3;

	int	t1, t2;

	switch(origin)
	{
		case CURRENT:
			t3 = fd[EX] * EXT_SIZE;
			t1 = cc_ex(offset+fd[NR]+t3);
			t2 = cc_nr(offset+fd[NR]+t3);
			break;
		case START:
			t1 = cc_ex(offset);
			t2 = cc_nr(offset);
			break;
		case LAST:
			if(back_up(fd,offset) == ERROR)
				return ERROR;
			else
				return TRUE;
		default:
			errno = INVSEEK;
			return ERROR;
	}
	t3 = fd[EX];
	if(move_ex(fd,t1) == ERROR)
		return ERROR;
	if(move_nr(fd,t2) == ERROR)
	{
		move_ex(fd,t3);
		return ERROR;
	}
	return TRUE;
}

unlink(filename)
char	filename[];
{
	char	temp_fcb[FCB_SIZE];

	fill_fcb(temp_fcb,filename);	/* where is the check? */
	bdos(CC_DELETE,temp_fcb);
	return TRUE;
}

open(filename,mode)
char	filename[], *mode;
{
	register char	*fd;

	if((fd = get_fd()) == ERROR)
		return ERROR;
	if(fill_fcb(fd,filename) < 0)
		return ERROR;

	if(mode > 2)
	{
		errno = INVMODE;
		return FALSE;
	}
	fd[FM] = mode;

	int	t1, new, old;

	if(*fd != 0)
	{
		old = bdos(GET_DRIVE,0);
		new = *fd;
		if(new < 'A' || new > 'D')
		{
			errno = BADDEV;
			return ERROR;
		}
		*fd = 0;
		bdos(SET_DRIVE,new-'A');
	}
	else
		new = 0;

	bdos(SET_DMA,INIT_DMA);
	if(bdos(CC_OPEN,fd) == BDOSERR)
	{
		errno = OPENERR;
		free(fd);
		if(new)
			bdos(SET_DRIVE,old);
		return ERROR;
	}
	if(new)
	{
		bdos(SET_DRIVE,old);
		*fd = new;
	}
	return fd;
}

creat(filename)
char	filename[];
{
	register char	*fd;

	if((fd = get_fd()) == ERROR)
		return ERROR;
	if(fill_fcb(fd,filename) < 0)
		return ERROR;

	fd[FM] = FCB_WRITE;

	int	t1, new, old;

	if(*fd)
	{
		old = bdos(GET_DRIVE,0);
		new = *fd;
		if(new < 'A' || new > 'D')
		{
			errno = BADDEV;
			return ERROR;
		}
		*fd = 0;
		bdos(SET_DRIVE,new-'A');
	}
	else
		new = 0;

	bdos(CC_DELETE,fd);
	if(bdos(CC_CREAT,fd) == BDOSERR)
	{
		errno = CREATERR;
		free(fd);
		if(new)
			bdos(SET_DRIVE,old);
		return ERROR;
	}
	if(new)
	{
		bdos(SET_DRIVE,old);
		*fd = new;
	}
	return fd;
}

close(fd)
char	*fd;
{
	register int *p1;
	int	t1, new, old;

	if(fd<3)
		return 0;

	switch(fd[FM]){
	case 0:
	case 1:
	case 2:
	case 'R':
	case 'W':
	case 'A':
	case 'X':
		break;
	default:
		errno = INVFD;
		return -1;
	}
	if(*fd)
	{
		old = bdos(GET_DRIVE,0);
		new = *fd;
		if(new < 'A' || new > 'D')
		{
			errno = BADDEV;
			return ERROR;
		}
		*fd = 0;
		bdos(SET_DRIVE,new-'A');
	}
	else
		new = 0;

	t1 = FALSE;
	if(bdos(CC_CLOSE,fd) == BDOSERR)
	{
		errno = CLOSEERR;
		t1 = ERROR;
	}
	if(new)
		bdos(SET_DRIVE,old);
	if(fd[FM] > 3)
	{
		p1 = &fd[PS];
		free(*p1);
	}
	free(fd);
	return t1;
}

tell(fd)
char	*fd;
{
	return fd[NR] + fd[EX]*EXT_SIZE;
}

read(fd,buffer,byte_count)
char	*fd, *buffer;
int	byte_count;
{
	switch(fd[FM]){
	case FCB_READ:
	case FCB_R_W:
	case 'R':
		return cio(CC_READ,fd,buffer,byte_count);
	}
	errno = INVMODE;
	return ERROR;
}

write(fd,buffer,byte_count)
char	*fd, *buffer;
int	byte_count;
{
	register int	t1;

	switch(fd[FM]){
	case FCB_WRITE:
	case FCB_R_W:
	case 'W':
	case 'A':
		t1 = cio(CC_WRITE,fd,buffer,byte_count);
		if(fd[ME] < fd[EX])
			fd[ME] = fd[EX];
		return t1;
	default:
		errno = INVMODE;
		return ERROR;
	}
}

cio(mode,fcb_pointer,buffer,byte_count)
char	mode, *fcb_pointer, *buffer;
int	byte_count;
{
	register int records;
	int bytes;

	if(byte_count < REC_SIZE)
	{
		errno = BCE;
		return ERROR;
	}

	bytes = 0;
	for(records = byte_count/REC_SIZE; records; --records)
	{
		if(cpmio(mode,fcb_pointer,buffer) != TRUE)
		{
			errno = mode;
			return bytes;
		}
		buffer += REC_SIZE;
		bytes += REC_SIZE;
	}
	return bytes;
}

cpmio(mode,fcb_pointer,buffer)
char	mode, *fcb_pointer,*buffer;
{
	register char def_device;
	char	t1;
	char	old_device;

	old_device = fcb_pointer[0];
	def_device = bdos(GET_DRIVE,0);
	if(old_device)
	{
		if(def_device != fcb_pointer[0] - 'A')
			bdos(SET_DRIVE,(fcb_pointer[0] - 'A'));
		fcb_pointer[0] = 0;
	}
	bdos(SET_DMA,buffer);
	t1 = (bdos(mode,fcb_pointer) == 0);
	fcb_pointer[0] = old_device;
	bdos(SET_DRIVE,def_device);
	return t1;
}

fspec(fp,fcb)	/* parse fspec. if valid return true and set 
			 ip low 16 bytes of fcb, else return false. */
char *fp, *fcb;
{
	register int c;
	char	c1;

	if(*fp == 0)
	{
		errno = FSPECERR;
		return FALSE;
	}
	if(fp[1] == ':')
	{
		c1=toupper(*fp);
		if((c1 < 'A') || (c1 > 'D'))
		{
			errno=FSPECERR;
			return FALSE;

		}
		*fcb++ = c1;
		fp+=2;
	}
	else
		*fcb++ = 0;

	for(c=0;(c<8) && (*fp!='.') && (*fp);++c)
		*fcb++ = toupper(*fp++);
	while(c++<8)
		*fcb++ = ' ';
	if((*fp) && (*fp != '.'))
	{
		errno = FSPECERR;
		return FALSE;
	}
	if(*fp=='.') ++fp;
	for(c=0;(c<3) && (*fp);++c)
		*fcb++ = toupper(*fp++);
	while(c++ < 3)
		*fcb++ = ' ';
	if(*fp)
	{
		errno=FSPECERR;
		return FALSE;
	}
	return TRUE;
}

/*
dump_fcb(addr)
char	*addr;
{
	register int	t1;

	for(t1 = 0; t1 < FCB_SIZE; ++t1)
		printf("%x:%c\n",*addr,*addr++);
	putchar('\n');
}
*/

back_up(fd,offset)
char	*fd;
int	offset;
{
	register int ex;
	int nr;

	ex = fd[EX];
	nr = fd[NR];
	if(fd[ME] == 255)
	{
		to_end(fd);
		fd[ME] = fd[EX];
	}
	else
		move_ex(fd,fd[ME]);
	fd[NR] = fd[RC];
	if(seek(fd,-offset,CURRENT) == ERROR)
	{
		move_ex(fd,ex);
		move_nr(fd,nr);
		return ERROR;
	}
	else
		return TRUE;
}

to_end(fd)
char	*fd;
{
	register int t1;
	char	temp_fcb[FCB_SIZE];

	for(t1=0; t1<12; ++t1)
		temp_fcb[t1] = fd[t1];
	for(t1=13; t1<FCB_SIZE;)
		temp_fcb[t1++] = 0;

	bdos(SET_DMA,INIT_DMA);
	for(++(temp_fcb[EX]); bdos(CC_OPEN,temp_fcb) != BDOSERR;
		++(temp_fcb[EX]))
		   ;
	if(--(temp_fcb[EX]) == fd[EX])
		return;
	bdos(CC_CLOSE,fd);
	for(t1=0; t1<FCB_SIZE; ++t1)
		fd[t1] = temp_fcb[t1];
}

move_ex(fd,where)
char	*fd;
int	where;
{
	register int	t1;
	char	temp_fcb[FCB_SIZE];

	if(fd[EX] == where)
		return TRUE;

	for(t1=0; t1 < 12; ++t1)
		temp_fcb[t1] = fd[t1];
	for(t1=12; t1 < FCB_SIZE; ++t1)
		temp_fcb[t1] = 0;
	temp_fcb[EX] = where;
	bdos(SET_DMA,INIT_DMA);
	if(bdos(CC_OPEN,temp_fcb) != BDOSERR)
	{
		bdos(CC_CLOSE,fd);
		for(t1=0; t1 < FCB_SIZE; ++t1)
			fd[t1] = temp_fcb[t1];
		return TRUE;
	}
	errno = INVREC;
	return ERROR;
}

move_nr(fd,where)
char	*fd;
int	where;
{
	if(fd[NR] == where)
		return TRUE;
	if(where <= fd[RC] && (where >= 0 && where < REC_SIZE))
	{
		fd[NR] = where;
		return TRUE;
	}
	errno = INVREC;
	return ERROR;
}

cc_ex(where)
int	where;
{
	return where / EXT_SIZE;
}

cc_nr(where)
int	where;
{
	return where % EXT_SIZE;
}

fill_fcb(fcb_pointer,filename)
char	*fcb_pointer, filename[];
{
	register int t1;

	if(!fspec(filename,fcb_pointer))
		return ERROR;

	for(t1 = 12; t1 < FCB_ASIZE;)
		fcb_pointer[t1++] = 0;

	fcb_pointer[ME] = 255;
	return TRUE;
}

get_fd()
{
	register char	*p1;

	if(p1 = alloc(FCB_ASIZE))
		return p1;

	return ERROR;
}

fopen(filename,mode,buffer_size)
char	filename[], *mode;
int	buffer_size;
{
	register char	*fd;

	if((buffer_size % REC_SIZE) || buffer_size == 0)
	{
		errno = INVBUF;
		return ERROR;
	}

	int	*p1;

	switch(toupper(*mode))
	{
		case 'R':
			if((fd = open(filename,FCB_READ)) != ERROR)
				break;
			if(toupper(mode[1]) != 'W')
				return ERROR;
		case 'W':
			if((fd = creat(filename)) == ERROR)
				return ERROR;
			break;
		case 'A':
			if((fd = open(filename,FCB_WRITE)) == ERROR)
				return ERROR;
			if(seek(fd,0,LAST) == ERROR)
				return ERROR;
			break;
		default:
			errno = INVMODE;
			return ERROR;
	}
	p1 = &fd[PS];
	if((*p1 = alloc(buffer_size+SOVER)) == FALSE)
	{
		errno = BUFOVF;
		return ERROR;
	}
	p1 = *p1;
	p1[AP] = &p1[BU];
	p1[AC] = 0;
	p1[BS] = buffer_size;
	switch(toupper(*mode)){
	case 'A':
	case 'W':
		fd[FM] = toupper(*mode);
		break;
	default:
		switch(toupper(mode[1])){
		case 'W':
			fd[FM] = 'X';
			break;
		default:
			fd[FM] = 'R';
		}
	}
	return fd;
}

fclose(fd)
int	fd;
{
	putc(26,fd);
	if(fflush(fd) == ERROR)
		return ERROR;
	return close(fd);
}

pgetc(fd)
int	fd;
{
	register char	c;

	if((c = getc(fd)) == '\r')
		if(getc(fd) == '\n')
			c = '\n';
		else
			ungetc(fd);
	return c;
}

getc(fd)
char	*fd;
{
	register int	*p1;

	if(fd[FM] != 'R' && fd[FM] != 'X')
	{
		errno = INVFD;
		return ERROR;
	}


	p1 = *(p1 = &fd[PS]);
	if(p1[AC] <= 0)
		if(fill_buf(fd) == ERROR)
			return ERROR;
	--p1[AC];

	char	*c1;

	return *(c1 = p1[AP]++);
}

getw(fd)
char	*fd;
{
	register int r1;
	int r2;

	if((r1 = getc(fd)) == ERROR
		||
	   (r2 = getc(fd)) == ERROR)
		return ERROR;
	return (r1<<8) | r2;
}

ungetc(c,fd)
char	c, *fd;
{
	int	*p1;

	if(fd[FM] != 'R' && fd[FM] != 'X')
	{
		errno = INVFD;
		return ERROR;
	}
	char	*c1;

	p1 = *(p1 = &fd[PS]);
	++p1[AC];
	*(c1 = --p1[AP]) = c;
}

pputc(c,fd)
char	c;
int	fd;
{
	if(c == '\n')
		if(putc('\r',fd) == ERROR)
			return ERROR;
	putc(c,fd);
}

putc(c,fd)
char	c, *fd;
{
	register int	*p1;

	if(fd < 3)
	{
		putchar(c);
		return TRUE;
	}

	char	*c1;

	switch(fd[FM]){
	case 'X':
	case 'W':
	case 'A':
		p1 = *(p1 = &fd[PS]);
		if(p1[AC] >= p1[BS])
			if(write_buf(fd) == ERROR)
				return ERROR;
		++p1[AC];
		*(c1 = p1[AP]++) = c;
		return TRUE;
	default:
		errno = INVFD;
		return ERROR;
	}
}

putw(i,fd)
int	i, fd;
{
	if(putc(i>>8,fd) == ERROR)
		return ERROR;
	if(putc(i,fd) == ERROR)
		return ERROR;
	return TRUE;
}

fflush(fd)
char	*fd;
{
	register int	*p1;

	switch(fd[FM]){
	case 'X':
	case 'W':
	case 'A':
		p1 = *(p1 = &fd[PS]);
		if(p1[AC])
			return write_buf(fd);
		return TRUE;
	default:
		errno = INVFD;
		return ERROR;
	}
}

fill_buf(fd)
char	*fd;
{
	register int	*p1;

	p1 = *(p1 = &fd[PS]);
	if((p1[AC] = read(fd,&p1[BU],p1[BS])) <= 0)
	{
		errno = EOFERR;
		return ERROR;
	}
	p1[AP] = &p1[BU];
	return TRUE;
}

write_buf(fd)
char	*fd;
{
	register int	*p1;

	p1 = *(p1 = &fd[PS]);
	if(write(fd,&p1[BU],p1[BS]) != p1[BS])
		return ERROR;
	p1[AP] = &p1[BU];
	p1[AC] = 0;
	return TRUE;
}

toupper(cc)
	char	cc;
{
	register char c;

	if((c=cc) >= 'a' && c <= 'z')
		return c-' ';
	return c;
}
