
/****************************************************************
*								*
*		P-CP/M BDOS File I/O Module			*
*								*
*	This module contains all file handling BDOS functions	*
*	except for read and write for P-CP/M.  Included are:	*
*								*
*		seldsk()    - select disk			*
*		openfile()  - open file				*
*		close_fi()  - close file			*
*		search()    - search for first/next file match	*
*		create()    - create file			*
*		delete()    - delete file			*
*		rename()    - rename file			*
*		set_attr()  - set file attributes		*
*		getsize()   - get file size			*
*		setran()    - set random record field		*
*		free_sp()   - get disk free space		*
*		move()	    - general purpose byte mover	*
*								*
*								*
*	Modified for memory management on Z8000			*
*	The main change is that search no longer calls tmp_sel	*
*	tmp_sel in bdosmain.c now handles drive number = '?'	*
*								*
****************************************************************/

#include "stdio.h"		/* Standard I/O declarations */

#include "bdosdef.h"		/* Type and structure declarations for BDOS */

#include "biosdef.h"		/* and BIOS */

#include "pktio.h"		/* Packet I/O definitions */

/* declare external fucntions */
EXTERN UWORD 	dirscan();	/* directory scanning routine	*/
EXTERN UWORD	error();	/* disk error routine		*/
EXTERN UWORD	do_phio();	/* packet disk i/o handler	*/
EXTERN		clraloc();	/* clear bit in allocation vector */
EXTERN		setaloc();	/* set bit in allocation vector */
EXTERN UWORD	swap();		/* assembly language byte swapper */
EXTERN UWORD	dir_wr();	/* directory write routine */
EXTERN		tmp_sel();	/* temporary select disk routine */
EXTERN UWORD	calcext();	/* calc max extent allocated for fcb */
EXTERN UWORD	udiv();		/* unsigned divide routine	*/


/* declare external variables */
EXTERN UWORD	log_dsk;	/* logged-on disk vector	*/
EXTERN UWORD	ro_dsk;		/* read-only disk vector	*/
EXTERN UWORD	crit_dsk;	/* vector of disks in critical state	*/


/************************************
*  This function passed to dirscan  *
*	from seldsk (below)	    *
************************************/

BOOLEAN alloc(fcbp, dirp, dirindx)	/* ARGSUSED */
/* Set up allocation vector for directory entry pointed to by dirp */

struct fcb	*fcbp;		/* not used in this function	*/
REG struct dirent *dirp;	/* pointer to directory entry	*/
WORD		dirindx;	/* index into directory for *dirp */
{
    REG WORD	i;		/* loop counter	*/
    BSETUP

    if ( UBWORD(dirp->entry) < 0x10 )	/* skip MP/M 2.x and CP/M 3.x XFCBs */
    {
	(GBL.dphp)->hiwater = dirindx;	/* set up high water mark for disk */
	i = 0;
	if ((GBL.parmp)->dsm < 256)
	{
	    do setaloc( UBWORD(dirp->dskmap.small[i++]) );
		while (i <= 15);
	}
	else
	{
	    do setaloc(swap(dirp->dskmap.big[i++]));
		while (i <= 7);
	}
    }
}


/************************
*  seldsk entry point	*
************************/

seldsk(dsknum)

REG UBYTE dsknum;		/* disk number to select */

{
    struct iopb selpkt;
    REG WORD	i;
    UWORD	j;
    REG UBYTE	logflag;
    BSETUP

    logflag = ~(log_dsk >> dsknum) & 1;
    if ((GBL.curdsk != dsknum) || logflag)
    {				/* if not last used disk or not logged on */
	selpkt.iofcn = sel_info;
	GBL.curdsk = (selpkt.devnum = dsknum);
	if (UBWORD(dsknum) > 15) error(2);
	selpkt.ioflags = logflag ^ 1;
	do
	{
	    do_phio(&selpkt);	/* actually do the disk select	*/
	    if ( (GBL.dphp = (struct dph *)selpkt.infop) != NULL ) break;
	} while ( ! error(3) );

	GBL.dirbufp = (GBL.dphp)->dbufp;
			/* set up GBL copies of dir_buf and dpb ptrs */
	GBL.parmp = (GBL.dphp)->dpbp;
    }
    if (logflag)
    {		/* if disk not previously logged on, do it now */
	LOCK	/* must lock the file system while messing with alloc vec */
	i = (GBL.parmp)->dsm;
	do clraloc(i); while (i--);	/* clear the allocation vector */
	i = udiv( (LONG)(((GBL.parmp)->drm) + 1), 
		  4 * (((GBL.parmp)->blm) + 1), &j);
					/* calculate nmbr of directory blks */
	if (j) i++;			/* round up */
	do setaloc(--i); while (i);	/* alloc directory blocks */
	dirscan(alloc, NULL, 0x0e);	/* do directory scan & alloc blocks */
	log_dsk |= 1 << dsknum;		/* mark disk as logged in	*/
    }
}


/*******************************
*  General purpose byte mover  *
*******************************/

move(p1, p2, i)

REG BYTE *p1;
REG BYTE *p2;
REG WORD  i;
{
    while (i--)
	*p2++ = *p1++;
}


/*************************************
*  General purpose filename matcher  *
*************************************/

BOOLEAN match(p1, p2, chk_ext)

REG UBYTE *p1;
REG UBYTE *p2;
BOOLEAN  chk_ext;
{
    REG WORD	i;
    REG UBYTE temp;
    BSETUP

    i = 12;
    do
    {
	temp = (*p1 ^ '?');
	if ( ((*p1++ ^ *p2++) & 0x7f) && temp )
	    return(FALSE);
	i -= 1;
    } while (i);
    if (chk_ext)
    {
	if ( (*p1 != '?') && ((*p1 ^ *p2) & ~((GBL.parmp)->exm)) )
	    return(FALSE);
	p1 += 2;
	p2 += 2;
	if ((*p1 ^ *p2) & 0x3f) return(FALSE);
    }
    return(TRUE);
}


/************************
*  openfile entry point	*
************************/

BOOLEAN openfile(fcbp, dirp, dirindx)	/* ARGSUSED */

REG struct fcb *fcbp;		/* pointer to fcb for file to open */
struct dirent  *dirp;		/* pointer to directory entry	*/
WORD	dirindx;

{
    REG UBYTE fcb_ext;		/* extent field from fcb	*/
    REG BOOLEAN rtn;
    BSETUP

    if ( rtn = match(fcbp, dirp, TRUE) )
    {
	fcb_ext = fcbp->extent;	 /* save extent number from user's fcb */
	move(dirp, fcbp, sizeof *dirp);
				/* copy dir entry into user's fcb  */
	fcbp->extent = fcb_ext;
	fcbp->s2 |= 0x80;	 /* set hi bit of S2 (write flag)	*/
	crit_dsk |= 1 << (GBL.curdsk);
    }
   return(rtn);
}


/*************************/
/* flush buffers routine */
/*************************/

UWORD flushit()
{
    REG UWORD	rtn;		/* return code from flush buffers call */
    struct iopb flushpkt;	/* I/O packet for flush buffers call */

    flushpkt.iofcn = flush;
    while ( rtn = do_phio(&flushpkt) )
	if ( error(1) ) break;
    return(rtn);
}


/*********************************
* file close routine for dirscan *
*********************************/

BOOLEAN close(fcbp, dirp, dirindx)

REG struct fcb *fcbp;		/* pointer to fcb */
REG struct dirent *dirp;	/* pointer to directory entry */
WORD	dirindx;		/* index into directory	*/

{
    REG WORD  i;
    REG UBYTE *fp;
    REG UBYTE *dp;
    REG UWORD fcb_ext;
    REG UWORD dir_ext;
    BSETUP

    if ( match(fcbp, dirp, TRUE) )
    {			/* Note that FCB merging is done here as a final
			   confirmation that disks haven't been swapped */
	LOCK
	fp = &(fcbp->dskmap.small[0]);
	dp = &(dirp->dskmap.small[0]);
	if ((GBL.parmp)->dsm < 256)
	{		/* Small disk map merge routine  */
	    i = 16;
	    do
	    {
		if (*dp)
		{
		    if (*fp)
		    {
			if (*dp != *fp) goto badmerge;
		    }
		    else *fp = *dp;
		}
		else *dp = *fp;
		fp += 1;
		dp += 1;
		i -= 1;
	    } while (i);
	}
	else
	{		/* Large disk map merge routine */
	    i = 8;
	    do
	    {
		if (*(UWORD *)dp)
		{
		    if (*(UWORD *)fp)
		    {
			if (*(UWORD *)dp != *(UWORD *)fp) goto badmerge;
		    }
		    else *(UWORD *)fp = *(UWORD *)dp;
		}
		else *(UWORD *)dp = *(UWORD *)fp;
		fp += sizeof (UWORD);
		dp += sizeof (UWORD);
		i -= 1;
	    } while (i);
	}
	/* Disk map merging complete */
	fcb_ext = calcext(fcbp);	/* calc max extent for fcb */
	dir_ext = (UWORD)(dirp->extent) & 0x1f;
	if ( (fcb_ext > dir_ext) || 
	    ((fcb_ext == dir_ext) && 
		(UBWORD(fcbp->rcdcnt) > UBWORD(dirp->rcdcnt))) )
			/* if fcb points to larger file than dirp */
	{
	    dirp->rcdcnt = fcbp->rcdcnt;	/* set up rc, ext from fcb */
	    dirp->extent = (BYTE)fcb_ext;
	}
	dirp->s1 = fcbp->s1;
	if ( (fcbp->ftype[robit]) & 0x80) error(5,fcbp);
						 /* read-only file error */
	dirp->ftype[arbit] &= 0x7f;		/* clear archive bit	    */
	dir_wr(dirindx >> 2);
	UNLOCK
	return(TRUE);

badmerge:
	UNLOCK
	ro_dsk |= (1 << GBL.curdsk);
	return(FALSE);
    }
    else return(FALSE);
}


/************************
*  close_fi entry point	*
************************/

UWORD close_fi(fcbp)

struct fcb *fcbp;		/* pointer to fcb for file to close */
{
    flushit();				/* first, flush the buffers	*/
    if ((fcbp->s2) & 0x80) return(0);	/* if file write flag not on,
					   don't need to do physical close */
    return( dirscan(close, fcbp, 0));	/* call dirscan with close function */
}


/************************
*  search entry point	*
************************/

/* First two functions for dirscan */

BOOLEAN alltrue(p1, p2, i)	/* ARGSUSED */
UBYTE	*p1;
UBYTE	*p2;
WORD	i;
{
    return(TRUE);
}
 
BOOLEAN matchit(p1, p2, i)	/* ARGSUSED */
UBYTE	*p1;
UBYTE	*p2;
WORD	i;
{
    return(match(p1, p2, TRUE));
}


/* search entry point */ 
UWORD search(fcbp, dsparm, p)	/* ARGSUSED */

REG struct fcb *fcbp;		/* pointer to fcb for file to search */
REG UWORD dsparm;		/* parameter to pass through to dirscan */
UBYTE	*p;			/* pointer to pass through to tmp_sel	*/
				/*     -- now unused --			*/

{
    REG UWORD	rtn;		/* return value */
    BSETUP

    if (fcbp->drvcode == '?')
    {
	rtn = dirscan(alltrue, fcbp, dsparm);
    }
    else
    {
	if (fcbp->extent != '?') fcbp->extent = 0;
	fcbp->s2 = 0;
	rtn = dirscan(matchit, fcbp, dsparm);
    }
    cpy_out( GBL.dirbufp, GBL.dmaadr, SECLEN);
    return(rtn);
}


/************************
*  create entry point	*
************************/

BOOLEAN create(fcbp, dirp, dirindx)

REG struct fcb *fcbp;		/* pointer to fcb for file to create */
REG struct dirent *dirp;	/* pointer to directory entry	*/
REG WORD dirindx;		/* index into directory		*/

{
    REG BYTE *p;
    REG WORD i;
    REG BOOLEAN rtn;
    BSETUP

    if ( rtn = ((dirp->entry) == 0xe5) )
    {
	p = &(fcbp->rcdcnt);
	i = 17;
	do
	{			/* clear fcb rcdcnt and disk map */
	    *p++ = 0;
	    i -= 1;
	} while (i);
	move(fcbp, dirp, sizeof *dirp);	/* move the fcb to the directory */
	dir_wr(dirindx >> 2);		/* write the directory sector */
	if ( dirindx > (GBL.dphp)->hiwater )
	    (GBL.dphp)->hiwater = dirindx;
	crit_dsk |= 1 << (GBL.curdsk);
    }
    return(rtn);
}


/************************
*  delete entry point	*
************************/

BOOLEAN delete(fcbp, dirp, dirindx)

REG struct fcb *fcbp;		/* pointer to fcb for file to delete */
REG struct dirent *dirp;	/* pointer to directory entry	*/
REG WORD dirindx;		/* index into directory		*/

{
    REG WORD i;
    REG BOOLEAN rtn;
    BSETUP

    if ( rtn = match(fcbp, dirp, FALSE) )
    {
	if ( (dirp->ftype[robit]) & 0x80 ) error(5,fcbp);
				/* check for read-only file */
	dirp->entry = 0xe5;
	LOCK
	dir_wr(dirindx >> 2);
	/* Now free up the space in the allocation vector */
	if ((GBL.parmp)->dsm < 256)
	{
	    i = 16;
	    do clraloc(UBWORD(dirp->dskmap.small[--i]));
		while (i);
	}
	else
	{
	    i = 8;
	    do clraloc(swap(dirp->dskmap.big[--i]));
		while (i);
	}
	UNLOCK
    }
    return(rtn);
}


/************************
*  rename entry point	*
************************/

BOOLEAN rename(fcbp, dirp, dirindx)

REG struct fcb *fcbp;		/* pointer to fcb for file to delete */
REG struct dirent *dirp;	/* pointer to directory entry	*/
REG WORD dirindx;		/* index into directory		*/

{
    REG UWORD i;
    REG BYTE *p;		/* general purpose pointers */
    REG BYTE *q;
    REG BOOLEAN rtn;
    BSETUP

    if ( rtn =  match(fcbp, dirp, FALSE) )
    {
	if ( (dirp->ftype[robit]) & 0x80 ) error(5,fcbp);
				/* check for read-only file */
	p = &(fcbp->dskmap.small[1]);
	q = &(dirp->fname[0]);
	i = 11;
	do
	{
	    *q++ = *p++ & 0x7f;
	    i -= 1;
	} while (i);
	dir_wr(dirindx >> 2);
    }
    return(rtn);
}


/************************
*  set_attr entry point	*
************************/

BOOLEAN set_attr(fcbp, dirp, dirindx)

REG struct fcb *fcbp;		/* pointer to fcb for file to delete */
REG struct dirent *dirp;	/* pointer to directory entry	*/
REG WORD dirindx;		/* index into directory		*/

{
    REG BOOLEAN rtn;
    BSETUP

    if ( rtn = match(fcbp, dirp, FALSE) )
    {
	move(&fcbp->fname[0], &dirp->fname[0], 11);
	dir_wr(dirindx >> 2);
    }
    return(rtn);
}


/****************************
*  utility routine used by  *
*  setran and getsize	    *
****************************/

LONG extsize(fcbp)
/* Return size of extent pointed to by fcbp */
REG struct fcb *fcbp;

{
    return( ((LONG)(fcbp->extent & 0x1f) << 7)
		| ((LONG)(fcbp->s2 & 0x3f) << 12) );
}


/************************
*  setran entry point	*
************************/

setran(fcbp)

REG struct fcb *fcbp;		/* pointer to fcb for file to set ran rec */

{
    struct
    {
	BYTE b3;
	BYTE b2;
	BYTE b1;
	BYTE b0;
    };
    LONG random;

    random = (LONG)UBWORD(fcbp->cur_rec) + extsize(fcbp);
				/* compute random record field	*/
    fcbp->ran0 = random.b2;
    fcbp->ran1 = random.b1;
    fcbp->ran2 = random.b0;
}


/**********************************/
/* fsize is a funtion for dirscan */
/* passed from getsize		  */
/**********************************/

BOOLEAN fsize(fcbp, dirp, dirindx)	/* ARGSUSED */

REG struct fcb *fcbp;		/* pointer to fcb for file to delete */
REG struct dirent *dirp;	/* pointer to directory entry	*/
WORD dirindx;			/* index into directory		*/

{
    REG BOOLEAN rtn;
    struct
    {
	BYTE b3;
	BYTE b2;
	BYTE b1;
	BYTE b0;
    };
    LONG temp;

    if ( rtn = match(fcbp, dirp, FALSE) )
    {
	temp = (LONG)UBWORD(dirp->rcdcnt) + extsize(dirp);
				/* compute file size	*/
	fcbp->ran0 = temp.b2;
	fcbp->ran1 = temp.b1;
	fcbp->ran2 = temp.b0;
    }
    return(rtn);
}

/************************
*  getsize entry point	*
************************/

getsize(fcbp)
/* get file size	*/
REG struct fcb *fcbp;		/* pointer to fcb to get file size for */

{
    LONG maxrcd;
    LONG temp;
    REG WORD dsparm;
    struct
    {
	BYTE b3;
	BYTE b2;
	BYTE b1;
	BYTE b0;
    };

    maxrcd = 0;
    dsparm = 0;
    temp = 0;
    while ( dirscan(fsize, fcbp, dsparm) < 255 )
    {				/* loop until no more matches */
	temp.b2 = fcbp->ran0;
	temp.b1 = fcbp->ran1;
	temp.b0 = fcbp->ran2;
	if (temp > maxrcd) maxrcd = temp;
	dsparm = 1;
    }
    fcbp->ran0 = maxrcd.b2;
    fcbp->ran1 = maxrcd.b1;
    fcbp->ran2 = maxrcd.b0;
}


/************************
*  free_sp entry point	*
************************/

free_sp(dsknum)

UBYTE dsknum;		/* disk number to get free space of */
{
    LONG records;
    REG UWORD   *alvec;
    REG UWORD	bitmask;
    REG UWORD	alvword;
    REG WORD	i;
    BSETUP

    seldsk(dsknum);		/* select the disk */
    records = (LONG)0;		/* initialize the variables */
    alvec = (GBL.dphp)->alv;
    bitmask = 0;
    for (i = 0; i <= (GBL.parmp)->dsm; i++)	/* for loop to compute */
    {
	if ( ! bitmask)
	{
	    bitmask = 0x8000;
	    alvword = ~(*alvec++);
	}
	if ( alvword & bitmask)
	    records += (LONG)( ((GBL.parmp)->blm) + 1 );
	bitmask >>= 1;
    }
					/* move # records to DMA address */
    cpy_out(&records, GBL.dmaadr, sizeof records);
}
