/* 
 * Very RAW xP/M i/o routines...
 *
 * FILE: XPMIO.C
 *
 *    Copyright (c) 1984,1985,1986  Tim Gary
 *	       All rights reserved.
 *
 *
 * 08/14/86 free_space() fixed..
 * 09/02/85 Added routine to return amount of free disk space.. (in K)
 * 01/30/85 Allow getchar/putchar, etc.. with a single define.. default is
 *	   to exclude those routines..
 * 10/28/84 uread function bug fixed (wrong byte_rem_count).
 *
 *  User area for each file is kept track of for all reads/writes, not
 * just set when file was opened.
 *
 *  If the macro MULTI_USER is defined, then file operations are for
 * MP/M and TURBODOS Multi-user operating systems.
 *
 *	CP/M and MP/M are registered trademarks of Digital Research.
 *	TURBODOS is a registered trademark of Software 2000, Inc.
 */

#define XPMIO

#include "xpm.h"
#define	NULL	0

/* bdos call numbers.... */

#define DIRIO	6

#define SELDRV	14	/* file i/o stuff */
#define	OPNFIL	15
#define CLSFIL	16
#define DELFIL	19
#define MAKFIL	22
#define SETDMA	26
#define GETALV	27
#define GETDPB	31
#define	SETUSR	32
#define READRN	33
#define WRITRN	34
#define FILSIZ	35
#define SETREC	36

#define kbhit() bdos(CONSTAT,0)		/* console status..		    */


/* open a file .. direct cpm open, allocates a buffer for FCB/record inform of
 * 	FILE...		If file exists, a simple open is done..
 *			if non-existant, a file is made..
 *			if couldn't make.. then return NULL as error...
 * Try and be sure to close opened files (assuming they were successfully
 * opened), since buffer space is dynamically allocated.
 */

FILE *open(name,mode)
register char *name;
register int mode;		/* see xpm.h for modes.. */
{
register FILE *fd;
int err_code;		/* from open file function */

if ((fd=malloc(sizeof(FILE)+1))!=NULL) 
	{
	setmem(fd,sizeof(FILE),0);		/* alloc memory for fcb */
	setusr(fd->_user=fcbinit(name,fd));	/* setup fcb, get user area */

#ifdef MULTI_USER	/* the following applies to multiuser ONLY */

	if (mode & F_UNLOCK) fd->f_name[4]|=0x80;
	if (mode & F_RO) fd->f_name[5]|=0x80;

#endif	/* multi user */

	fd->_bp=fd->_buffer;	/* set up current char position */
	if ( (err_code=bdos(OPNFIL,&fd->_fcb)) >3 || err_code<0)
	/* hanging to next statement set.. */

#ifdef MULTI_USER

	     if (err_code==5)
		{		/* file locked.. wait */
		unsigned u;
		while (bdoshl(OPNFIL,&fd->_fcb)==5)
			{
			printf("\nQueued. ");
			for (u=0; u<50000; u++);
			}
		}
     	else

#endif	/* multi user */

	 if (mode & F_RW) {
		 if (bdos(MAKFIL,&fd->_fcb)==0xff)
			{
			close(fd);	/* free up space */
			fd=NULL;
			}
		} else	{ close(fd);	fd=NULL; }
	}
	else printf("\nUnable to alloc space for file: %s\nStack near %xh ",name,&err_code);
return (fd);		/* return pointer to the fcb */
}


/* close a file... and free up allocated space... */

close(fd)
register FILE *fd;
{
register int code;

  if ((fd->_bp==NULL) || (fd==NULL)) return 0;	/* 0=ok.. but no file there. */
  setusr(fd->_user);		/* restore file's user area */
  code=bdos(CLSFIL,&fd->_fcb);	/* CP/M close file  */
  free(fd);
  fd->_bp=NULL;			/* in case close again */

 return code==0xff ? ERROR : NULL;
}


/* random xP/M read.. and optional increment of record position...
 *  if rinc=0 then no increment.. else increment by rinc sectors
 * The record is placed in the files buffer space (bufloc(fd)).
 */

read(fd,rinc)	/* read one cp/m sector (128 bytes) and return # bytes read */
register FILE *fd;
unsigned rinc;	/* record increment.. used so can read without advance */
{
register int code;
bdos(SETDMA,(fd->_bp=fd->_buffer));	/* set DMA to file buffer area	*/
setusr(fd->_user);			/* restore file user number	*/
if ((code=bdos(READRN,&fd->_fcb))>0) return (code);  /* read random record.. */
fd->_fcb.f_record+=rinc;		/* incrememt record count... */
setbuf(fd,0);				/* set this to zero */
return 128;				/* 128 bytes read */
}


/*************************************************
 * Unix style read into buffer, for size bytes.. *
 *************************************************/

uread(fd,buf,size)
 FILE *fd;
 char *buf;
 unsigned size;
{
register unsigned rec_count,byte_rem_count;
/* # of whole records, and of remaining bytes after the whole records */

setusr(fd->_user);		/* restore file user number */
rec_count=(((size+128)-getbuf(fd))/128);
if (rec_count!=0) rec_count--;
byte_rem_count=(getbuf(fd)+size)%128;

/*	printf("\nrec_count=%d  getbuf(fd)=%xh  byte_rem_count=%d ",
		rec_count,getbuf(fd),byte_rem_count);			*/

if (getbuf(fd)!=0 && getbuf(fd)!=128)
	{
	if (size<=128-getbuf(fd))
		{
		movmem(bufloc(fd)+getbuf(fd),buf,size);
		setbuf(fd,getbuf(fd)+size);
		return size;
		}
	   else {
		movmem(bufloc(fd)+getbuf(fd),buf,128-getbuf(fd));
		buf+=(128-getbuf(fd));
		}
	}

while (rec_count--)
	{
	bdos(SETDMA,buf);
	if (bdos(READRN,&fd->_fcb)>0) return ERROR;
	fd->_fcb.f_record++;		/* incrememt record count... */
	buf+=128;			/* bump buffer one records worth */
	}

if (byte_rem_count)
	{
	if (read(fd,1)!=128) return ERROR;
	movmem(bufloc(fd),buf,byte_rem_count);	/* mov in last bytes */
	setbuf(fd,byte_rem_count);
	}
	else setbuf(fd,0);	/* at zero */

return size;
}


/* write sector.. rinc same format as read... Writes from files buffer space */

write(fd,rinc)	/* write random.. */
register FILE *fd;
unsigned rinc;
{
register int code;	/* eror return code.  */
setusr(fd->_user);	/* restore file user number */
bdos(SETDMA,(fd->_bp=fd->_buffer));
if ((code=bdos(WRITRN,&fd->_fcb))>0) return (code);
fd->_fcb.f_record+=rinc;
return 128;
}


rename(old, new)
char *old, *new;
{
	auto char buff[60];
	register int user;

	user = fcbinit(old,buff);
	fcbinit(new,buff+16);
	setusr(user);
	if (bdos(15,buff+16) != 0xff) {
		bdos(16,buff+16);
		bdos(19,buff+16);	/* delete file if was there.. */
	}
		bdos(23,buff);		/* rename */
}


/* primative getc/putc function */

getc(fd)
 register FILE *fd;
{
register char ch;
 if (eobuf(fd))
	if (read(fd,1)!=128) return EOF;	/* end of file */
 ch=gchar(fd);
 return ch!=0x1a ? ch : EOF;			/* also cp/m EOF ^Z */
}

putc(ch,fd)
 register char ch;
 register FILE *fd;
{
 if (ch=='\n') putc('\r',fd);			/* recursive cr/lf */
 if (eobuf(fd))
	if (write(fd,1)!=128) return ERROR;
 return pchar(fd,ch);
}


fputs(s,fd)
 register char *s;
 register FILE *fd;
{ 
 while (*s) putc(*s++,fd);
}

fgets(s,fd)
 register char *s;
 register FILE *fd;
{
 register int i;
 char *cp;

 cp=s;
 while ((i=getc(fd))!='\r' && i!=EOF) *cp++=i;
 *cp='\0';
 return(s);
}


/* function to return amount of free disk space on logged drive..(in K) */

unsigned free_space(drive)
 int drive;
{
register struct _dpb *dpb;
int tdrive;
char *av;
unsigned count,all;

tdrive=bdos(0x19,0);  bdos(0x0e,drive-1);  /* remember old, select new drive */
dpb=bdoshl(GETDPB,0);  av=bdoshl(GETALV,0);
bdos(0x0e,tdrive);  /* restore old drive */

for (all=0; all<16; all++)
    if ( !bit_test(&dpb->dir1_alloc,all) ) break;

for (count=0; all<=(dpb->max_alloc); all++)
    if ( !bit_test(av,all) ) ++count;

return count << (dpb->blk_shift-3);	/* Return number of K free */
}


bit_test(bv,bit)
 char *bv;
 unsigned bit;
{
return bv[bit >> 3] & (128 >> (bit & 7));
}


/* further i/o defines are set up in cpm.h ... they include routines for
 * the following..	get buffer location
 *			get/put char within buffer
 *			get/set char POSition within buffer
 *			absolute record seek (from begin of file)
 *			relative record seek (from current pos)
 *			detect end of buffer condition (used after get/put)
 *	all seek positions are by 128 byte cp/m records..
 */

#ifdef CON_IO
/* some new char i/o routines.. NOT USED WITH METAL! */

getchar()
{
register int ch;
 while((ch=bdos(6,0xff))==0);
 putchar(ch);
 return ch;
}

putchar(ch)
register int ch;
{
if (ch=='\n') bdos(6,'\r');
bdos(6,ch);
return ch;
}

char *gets(s)
 char *s;
{
 register char *cp;
 register int i;

cp=s;
while ((i=getchar()) != '\r')
	*cp++=i;
*cp='\0';
return s;
}

puts(s)
 char *s;
{
	while (*s) putchar(*s++);
	return putchar('\n');
}

#endif	/* CON_IO */

/* EOF xpmio.c */
