/*
>>:yam7.c 10-25-81 (diff 10-25-81)
 * File open and close stuff and more ...
 * This file assumes operation on a CP/M system
 */
#include "yam.h"
/* dpb dph blocks from CP/M Interface Guide Sect 6.5 */

struct dpb {	/* CP/M Version 2 Disk Parameter Block */
	unsigned dpb_spt;	/* sectors per track */
	char dpb_bsh;		/* block shift factor */
	char dpb_blm;
	char dpb_exm;		/* Extent Mask */
	unsigned dpb_dsm;	/* Highest block number on this disk */
	unsigned dpb_drm;	/* total number of directory entries -1 */
	unsigned dpb_al;	/* bit field corresponding to direc blocks */
	unsigned dpb_cks;	/* size of the directory check vector */
	unsigned dpb_off;	/* number of reserved tracks on this disk */
};

char *call();

struct dph {	/* CP/M Version 2 Disk Parameter Header */
	char **dph_xlt;		/* logical to physical xlat vector */
	int dph_ooo;
	char *dph_dirbuf;	/* 128 byte scratchpad for directory use */
	struct dpb *dph_dpb;	/* disk param block for this type of disk */
	char **dph_csv;		/* scratch area for detecting changed disks */
	char *dph_alv;		/* pointer to bit vector alloc map for disk */
};

struct fcb {	/* CP/M Version 2 fcb AS SEEN BY THE USER */
	char	dr;		/* drive number */
	char	fname[8];	/* fname[1] used by TAG2 */
	char	ftype[3];	/* ftype[1] 8th bit set for $SYS */
	char	ex;		/* file extent normally 0 */
	char s1;		/* reserved for bdos's benefit */
	char s2;		/* likewise, =0 on call to open,make,search */
	char	rc;		/* record count for extent[ex]  0...128 */
	char dmap[16];
	char cr;		/* current record, initialized to 0 by usr */
	unsigned recn;		/* highest record number */
	char recovf;		/* overflow of above */
};

/* following are BIOS calls */
#define CONST 2			/* bios console char ready */
#define CONIN 3			/* bios cons char input */
#define CONOUT 4		/* bios cons char output */

/* following are BDOS calls */
#define BDOS 5			/* address used to call bdos */
#define SELDSK 14		/* bdos select disk 0=default disk */
#define SRCH 17 		/* bdos search for file pattern*/
#define SRCHNXT 18		/* search for next occurrence */
#define GETDEFDISK 25		/* get current disk  (0-15) */
#define SETDMA 26		/* set address for read, write, etc. */
#define GETALLOCP 27		/* get address of allocation vector */
#define SETATTRIB 30		/* update file attributes */
#define GETDPBP 31		/* get DPB address for disk */
#define SETGETUSER 32		/* set or get user number */
#define COMPFILSIZ 35		/* compute file size into recn and recovf */
#define UFNSIZE 15		/* a:foobar12.urk\0 is 15 chars */

openrx(name)
char *name;
{

#ifdef RESTRICTED
	char *s;
	if(s=cisubstr(name, ".com"))	/* upload .com files as .obj */
		strcpy(s, ".OBJ");
	if(cisubstr(name, "$$$"))
		return ERROR;		/* don't allow upload of $$$.sub */
	for(s=name; *s; )
		if(*s++ > 'z')
			return ERROR;	/* no garbage names please */
#endif
	unspace(name);
	lprintf("'%s' ", name);		/* show the name right away */
	if(!Creamfile && fopen(name, fout) != ERROR) {
		fclose(fout);
		printf("Exists ", name);
#ifdef XMODEM
		return ERROR;
#else
		printf("Replace it (y/n)??");
		if(tolower(getchar())!= 'y')
			return ERROR;
#endif
	}
	if(fcreat(name, fout)==ERROR){
		printf("Can't create %s\n", name);
		return ERROR;
	}
	Rfile= TRUE;
	strcpy(Rname, name);
	Dumping= !Squelch;
	lprintf("Created%s\n", Dumping? "" : " recording OFF");
	return OK;
}

closerx(pad)
{
	if(Rfile) {
		if(!Quiet)
			lpstat("Closing %s", Rname);
#ifdef BDSC
		if(pad)
			do
				putc(CPMEOF, fout);
				while(fout._nleft % SECSIZ);
#endif
		fflush(fout);
		fclose(fout);
		Rfile=FALSE;
#ifdef LOGFILE
		logfile(Rname, pad?'*':'R');	/* record file xmsn */
#endif
 	}
}

opentx(name)
char *name;
{
	struct fcb *fp, *fcbaddr();
	lprintf("'%s' ", name);
	unspace(name);
	if(fopen(name, fin)==ERROR){
		printf("Can't open %s\n", name);
		return ERROR;
	}
#ifdef RESTRICTED
	if(cisubstr(name, ".bad")
	 || (fp=fcbaddr(fin._fd))==ERROR
	 || (fp->fname[1] & 0200)	/* tag2 */
	 || (fp->ftype[1] & 0200)	/* $SYS */
	 ) {
	 	fclose(fin); printf("\n'%s' Not for Distribution\n", name);
		return ERROR;
	}
#endif
	Tfile= TRUE;
	strcpy(Tname, name);
	lprintf("Open\n");
	return OK;
}

/* closetx(status) call with status != 0 if incomplete file xmsn */
closetx(status)
{
	if(Tfile) {
		fclose(fin);
		if(!Quiet)
			lpstat("%s closed", Tname);
#ifdef LOGFILE
		if(!status)
			logfile(Tname, 's');		/* record file xmsn */
#endif
		Tfile=FALSE;
	}
}
/* search the phone file for name */
getphone(name, buffer)
char *name, *buffer;
{
	closetx(TRUE);


	if(fopen("YAMPHONE.T", fin)==ERROR) {
		printf("Cannot open %s\n", "YAMPHONE.T");
		return ERROR;
	}
	else {
		while(fgets(buffer, fin))
			if(cmdeq(buffer, name)) {
				fclose(fin);
				return OK;
			}
	}
	printf("Can't find data for %s\n", name);
	fclose(fin);
	return ERROR;
}

/* channge default disk and optionally, user number */
chdir(p)
char *p;
{
	unsigned newuser;

	newuser=user; *p=toupper(*p);
	if(index(*p, DISKS)) {
		defdisk= *p - 'A';
		bdos(SELDSK, defdisk);
#ifdef CDOS
		return;
#else
		printdfr();
		if(!isdigit(p[1]))
			return;
		if((newuser=atoi(p+1)) <= MAXUSER) {
			bdos(SETGETUSER, newuser);
			user=newuser;
			return;
		}
#endif
	}
	printf("Disk %c and/or User %d Illegal\n", *p, newuser);
}

/* fetch default disk and user number */
initdd()
{
	Secpblk=SECPBLK;
	defdisk= bdos(GETDEFDISK,0);
#ifdef CDOS
	user=0;
#else
	user=bdos(SETGETUSER, 0377);
#endif
}

/*
 * Z19 gets to use it's 25th line. pstat starts at 48th char
 * note that a call to lpstat will erase what pstat displays
 */
/*VARARGS*/
pstat(a,b,c)
char *a, *b, *c;
{
#ifdef Z19
	lprintf("\033x1\033j\033Y8P");
#endif
	lprintf(a,b,c);
#ifdef Z19
	lprintf("\033K\033k");
#else
	lprintf("\n");
#endif
}

/*
 * Z19 gets to use it's 25th line. lpstat starts at col 1
 * Rest of line is erased
 */
/*VARARGS*/
lpstat(a,b,c)
char *a, *b, *c;
{
#ifdef Z19
	lprintf("\033x1\033j\033Y8 ");
#endif
	lprintf(a,b,c);
#ifdef Z19
	lprintf("\033K\033k");
#else
	lprintf("\n");
#endif
}
char getcty()
{
	return bios(CONIN,0);
}
char putcty(c)
char c;
{
#ifdef RXNONO
	if( !index(c, RXNONO))
#endif
		bios(CONOUT, c);
	if(bios(CONST,0)) {
		if((c=bios(CONIN,0))==XOFF)
			bios(CONIN,0);
		else
			return c;
	}
	return FALSE;
}

dolist(argc, argp)
char **argp;
{
	int listfile();
#ifdef XMODEM
	printf("^S pauses, ^K skips to next file, ^X terminates\n");
	expand(listfile, argc, argp);
#else
	expand(listfile, argc, argp);
#endif
}

listfile(name)
char *name;
{
	int c;

#ifdef XMODEM
	printf("\nListing '%s'\n\022", name);
#endif
	closetx(TRUE);
	if(opentx(name)==ERROR)
		return ERROR;
	else {
		while((c=getc(fin))!=EOF && c != CPMEOF) {
			if( !(c=putcty(c)))
				continue;
			if(c==003 || c==CAN || c==013) {
				c=0; break;
			}
		}
		/* record complete xmsn iff terminated by (CPM)EOF */
		closetx(c==0);
#ifdef XMODEM
		sendline(024);	/* squelch in case user downloading */
#endif		
	}
	/* cancel rest of files if ^C ^X */
	if(c==003 || c==CAN)
		return ERROR;
	else
		return OK;
}

/* fill buf with count chars padding with ^Z for CPM */
filbuf(buf, count)
char *buf;
{
	register c, m;
	m=count;
	while((c=getc(fin))!=EOF) {
		*buf++ =c;
		if(--m == 0)
			break;
	}
	if(m==count)
		return 0;
	else
		while(--m>=0)
			*buf++ = 032;
	return count;
}

dodir(argc, argp)
char **argp;
{
	int pdirent();
	cfast=0;		/* counter for 4 across format */
	expand(pdirent, argc, argp);
#ifndef CDOS
	printdfr();
#endif
}
pdirent(name)
{
	printf("%-14s%c", name, (++cfast&03)?' ':'\n');
}
#ifndef CDOS
/* docomp does a directory listing showing sectors for each matched file
 * and computes total transmission time of matched files in batch mode
 * time is sum of:
 * 	number of files * open/close time (assumed 5 seconds)
 *	time to xmit and ACK each sector assuming no path delay or error
 *	disk i/o time at each end, not dependent on baud rate
 */
docomp(argc,argp)
char **argp;
{
	unsigned compsecs();
	unsigned spm;	/* sectors per minute-baud */
	unsigned dminutes;	/* tenths of minutes */
	cfast=Numsecs=Numblks=0;
	expand(compsecs, argc, argp);
	/* (Baudrate*60)/(10 bits each char * 136 chars) */
	spm= Baudrate/23;
	dminutes= Numfiles+((10*(Numfiles+Numsecs))/spm)+(Numsecs/20);
	printf("\n%u Files %u Blocks %u K\n",
	  Numfiles, Numblks, (Numblks*(Secpblk/8)));
	printf("%u Sectors %u.%u Minutes Xmsn Time at %u Baud\n",
	  Numsecs, dminutes/10, dminutes%10, Baudrate);

}
/* add file length (in CP/M 128 byte records) to Numsecs */
unsigned compsecs(ufn)
char *ufn;
{
	struct fcb fstat;
	printf("%-14s", ufn);
	unspace(ufn);
	setfcb( &fstat, ufn);
	bdos(COMPFILSIZ, &fstat);
	Numsecs += fstat.recn;
	Numblks += (fstat.recn+Secpblk)/Secpblk;
	printf("%4u%c",fstat.recn, (++cfast&03)?' ':'\n');
	return fstat.recn;
}
#endif

expand(fnx, argc, argp)
int (*fnx)();
char **argp;
{
	char name[PATHLEN], *s;
	Numfiles=0;

	if(argc<=0)
		return e1xpand(fnx, "*.*");
	else
		while(--argc>=0) {
			/* change b: to b:*.*     */
			strcpy(name, *argp++);
			if((s=index(':', name)) && *++s == 0)
				strcpy(s, "*.*");
			if(e1xpand(fnx, name)==ERROR)
				return ERROR;
		}
	return OK;
}

/*
 * e1xpand expands ambiguous pathname afnp
 * calling fnx for each.
 * Modified from: Parameter list builder by Richard Greenlaw
 *                251 Colony Ct. Gahanna, Ohio 43230
 */
e1xpand(fnx, afnp)
int (*fnx)();
char *afnp;	/* possible ambiguous file name*/
{
	struct fcb sfcb, *pfcb;
	FLAG first;
	char *p, *q, i, byteaddr;
	int filecount, m;
	char tbuf[SECSIZ];
	struct {
		char xYxx[UFNSIZE];		/* unambiguous file name */
	} *fp;
	int strcmp();


	/* build CPM fcb   */
	unspace(afnp);
	if(setfcb(&sfcb, afnp) == ERROR) {
		printf("%s is bad pattern\n", afnp);
		return ERROR;
	}

	if(Wrapped || (bufend-bufcq)<2048) {
		dumprxbuff();		/* I need the space for building the pathlist */
		clearbuff();		/* so the printer won't try to list dir's */
		bufmark=bufst;
	} else
		bufmark=bufcq;

	/* Search disk directory for all ufns which match afn*/
	for(fp=bufmark,filecount=0,first=TRUE;; fp++,filecount++) {
tryanother:
		bdos(SETDMA, tbuf);
		/* seems CP/M outta know whether to use SRCH or SRCHNXT !! */
		byteaddr=bdos(first? SRCH:SRCHNXT, &sfcb); first=FALSE;
		if(byteaddr==255)
			break;
		/* calculate pointer to filename fcb returned by bdos */
		pfcb = (tbuf + 32 * (byteaddr % 4));
#ifdef RESTRICTED
		/* check for $SYS or tag bit on 2nd byte of filename (TAG2) */
		if((pfcb->fname[1]&0200) ||(pfcb->ftype[1]&0200))
			goto tryanother;
#endif
		Numfiles++;
		p = fp;
		if(fp>bufend) {	/* Note: assumes some slop after bufend! */
			printf("Out of Memory for pathname expansion\n");
			return ERROR;
		}
		if(*(afnp+1) == ':') {
			/* Drive spec.*/
			*p++ = *afnp;
			*p++ = ':';
		}

		/*Copy filename from directory*/
		q = pfcb;
		for(i =8; i; --i)
			*p++ = (0177& *++q);
		*p++ = '.' ;

		/*Copy file extent*/
		for(i = 3; i; --i)
			*p++ = (0177& *++q);
		*p = '\0' ;

	}
	if(filecount==0) {
		printf("'%s' NOT FOUND\n", afnp);
		return ERROR;
	}

	qsort(bufmark, filecount, UFNSIZE, strcmp);

	for(fp=bufmark; --filecount>=0;) {
		p=fp++;
		/* execute desired function with real pathname */
		if((*fnx)(p)==ERROR)
			return ERROR;
	}
	return OK;
}

/*
 * cisubstr(string, token) searches for lower case token in string s
 * returns pointer to token within string if found, NULL otherwise
 */
char *cisubstr(s, t)
char *s,*t;
{
	char *ss,*tt;
	/* search for first char of token */
	for(ss=s; *s; s++)
		if(tolower(*s)==*t)
			/* compare token with substring */
			for(ss=s,tt=t; ;) {
				if(*tt==0)
					return s;
				if(tolower(*ss++) != *tt++)
					break;
			}
	return NULL;
}
#ifdef XMODEM

/*
 * lprintf is like regular printf but uses direct output to console
 * This prevents status printouts from disrupting file transfers, etc.
 */

lprintf(a,b,c,d,e,f)
char *a, *b, *c, *d, *e, *f;
{
	char lbuf[CMDLEN], *s;
	/* format data into lbuf */
	sprintf(lbuf, a,b,c,d,e,f);
	/* now send lbuf to console directly */
	for(s=lbuf; *s; ) {
		if(*s=='\n') {
			while(!COREADY)		/* expand \n to \r\n */
				;
			outp(CDATA, '\r');
		}
		while(!COREADY)
			;
		outp(CDATA, *s++);
	}
}
#endif

/* copy string s onto itself deleting spaces "hello there" > "hellothere" */
unspace(s)
char *s;
{
	char *p;
	for(p=s; *s; s++)
		if(*s != ' ')
			*p++ = *s;
	*p++ =0;
}

#ifdef LOGFILE
/*
 * logfile keeps a record of files transmitted.
 * Mode is 's' for send file, 'r' for receive file, 't' for type file
 * Lifted from xcmodem by J. Wierda,R. Hart, and W. Earnest
 */
#define RPB 4	/* log records per buffer */
#define LRL 32	/* logical rec length LRL*RPB == SECSIZ */
logfile(name, mode)
char *name;
char mode;
{
	struct fcb *fcbaddr(),*fp;
	int fd,rnum,bnum,thedisk;
	char *i, *lrec, lbuf[SECSIZ+2];

	/* find out what disk was used */
	thedisk=defdisk + 'a';
	if(i=index(':', name)) {
		thedisk = *name;
		name= ++i;
	}
	bdos(SETGETUSER, 0);	/* get it from user 0 */
	fd=open(LOGFILE,2);
	if(fd == ERROR) {	/* if file absent, create and initialize it */
		fd = creat(LOGFILE);
		if(fd == ERROR)
			return;
		else {
			rnum=1;
			fp=fcbaddr(fd);
			fp->fname[1] |= 0200;	/* set TAG2 bit */
			fp->ftype[1] |= 0200;	/* set SYS bit */
			bdos(SETATTRIB, fp);	/* and update attributes */
		}
	}
	else {
		read(fd, lbuf, 1);
		rnum=atoi(lbuf)+1;
	}
	setmem(lbuf, SECSIZ, 0);
	sprintf(lbuf, "%d\r\n", rnum);
	seek(fd,0,0);
	write(fd, lbuf, 1);		/* update last record number */
	bnum=rnum/RPB;
	rnum=(rnum%RPB)*LRL;
	seek(fd, bnum, 0);
	if(rnum==0)
		setmem(lbuf, SECSIZ, 0);
	else
		read(fd, lbuf, 1);
	sprintf(&lbuf[rnum], "\r\n%c %5u	%c%02d:	%-14s",
	 mode, Baudrate, thedisk, user, name);
	seek(fd, bnum, 0);
	write(fd,lbuf,1);
	close(fd);
	bdos(SETGETUSER, user);
}
#endif


docrck(argc, argp)
char **argp;
{
	int crckfile();

	expand(crckfile, argc, argp);
}

/* Accumulate and print a "crck" for a file */
crckfile(name)
char *name;
{
	unsigned crck();
	char crbuf[SECSIZ]; int fd,st;

	printf("%14s ", name); unspace(name);
	if((fd=open(name, 0))==ERROR)
		return ERROR;

	oldcrc=0;
	while((st=read(fd, crbuf, 1)) ==1)
			oldcrc=crck(crbuf, SECSIZ, oldcrc);
	close(fd);
	if(st != 0)
		printf("READ ERROR ");
	else
		printf("CRCK= %04x\n", oldcrc);
	return OK;
}

/* print number of free blocks on default disk */
printdfr()
{
	struct dpb *dp;

	dp=call(BDOS, 0, 0, GETDPBP, defdisk);
	Secpblk= 1 << dp->dpb_bsh;
	printf("%u kb Free on %c", getfree(defdisk), defdisk+'A');
}

/* return total free kilobytes of disk */
unsigned getfree(disk)
{
	struct dpb *dp;
	/* unsigned */ char v, c, *s;
	unsigned total, count;

	bdos(SELDSK, disk);
	dp=call(BDOS, 0, 0, GETDPBP, disk);
	s=call(BDOS, 0, 0, GETALLOCP, disk);
	total=0;
	count=dp->dpb_dsm+1;
	for(;;) {
		v= *s++;
		for(c=0200; c; c>>=1) {
			if((v & c)==0)
				++total;
			if(--count ==0) {
				bdos(SELDSK, defdisk);
				return total << dp->dpb_bsh-3;
			}
		}
	}
}

doerase(argc, argp)
char **argp;
{
	int erasefile();
	expand(erasefile, argc, argp);
#ifndef CDOS
	printdfr();
#endif
}

erasefile(ufn)
char *ufn;
{
	register c;
	unspace(ufn);
	printf("Erase %s (y/n/q)? ",ufn);
	c=getchar();
	putchar('\n');
	if(tolower(c)=='y')
		unlink(ufn);
	else if(tolower(c)=='n')
		return FALSE;
	else
		return ERROR;
	return FALSE;
}
