/* cpm.c : i read/write/directory cpm standard format 8" diskettes */

/* edit history
 *
 * pf01	27jan85	invent xoutput() being generalised output() suitable for
 *		binary output, then implement -x binary output command
 * pf02 28may85	add cputdir() to two "no more room on diskette" errors
 *		so user gets at least what we DID write to diskette
 *		recorded in directory of diskette. One fix each in newext()
 *		and xoutput().
 */

/*)BUILD	$(PROGRAM)	=	cpm
		$(FILES)	=	{ cpm }
		$(LIBS)		=	{ lb:[1,1]cx/lb }
		$(TKBOPTIONS)	=	{
						TASK = ...CPM
						UNITS = 8
					}
*/



#include <stdio.h>

#include <algol68.h>

#ifdef decus
#ifdef rsx
#include <cx.h>
#include <qiofun.h>
#include <spcio.h>
#endif
#endif

/* this is very primitive C so it ports to non-KR Cs like BDS */

struct	dirent		/* CP/M directory entry */
BEGIN
char	d_usernumber;	/* 0xE5 means empty entry */
#define	EMPTY	0xE5
char	d_name[8];
char	d_type[3];
char	d_extent;	/* 0:31 */
char	d_unknown;
char	d_cluster;
char	d_lastrecord;	/* 1-org highest sector number in this extent */
			/* 0:128 */
char	d_block[16];	/* 2:242 */
END
;
/*
 *	standard CP/M diskette has:
 *	77 tracks			0:76
 *	track 0,1	boot
 *	track 2:76	CP/M space
 *	each track has 26 sectors, used in order :
 *	0 6 12 18 ... (see cxlate())
 *	CP/M uses records:	record 0 of track is sector 0
 *				record 1 of track is sector 6
 *				.
 *				.
 *				.
 *	a block is 1024 bytes = 8 records
 *	hence diskette has 26*75 = 1950 records
 *	1950/8 = 243 blocks
 *	block 0 & 1 are directory
 *	block 2:242 are user data
 *	within directory, 64 slots, each of 32 bytes
 *	one slot describes one extent
 *	an extent may have 0:16 blocks
 */
#define ENTSIZ 32	/* sizeof(dirent) */
#define DIRSIZ 64	/* number of directory entries */

struct	dirent	directory[DIRSIZ];

#define NBLOCKS 243	/* number of blocks on diskette */
char	bytmap[NBLOCKS];/* one element is 1 CP/M block
			   0 means this block is unused
			   1 : DIRSIZ means it is used by that slot of directory
				hence identifies which extent it is in
			*/

#define CPMEOF 26	/* this character means 'end of file' */

/* this information comes from user's command line */

char	*diskette;	/* points to a diskette name */
char	wname[11];	/* filename.typ
			   0 means this char is wild */
int	wuser;		/* user number mask
			   bit n set means include user n */

/* this comes from wildcarding */

char	infilnam[50];	/* print name of inputfile: a string */


#include "oscpm.h"



helpless(gripe)
char	*gripe;
BEGIN
	printf("%s\7\n",gripe);
	printf("I am CPM: I read, write or directory CP/M 8\" standard diskettes\n");
	printf("usage: CPM switch cpm-wild-card-file-spec\n");
	printf("switch:	-b binary input: gives 128-byte records\n");
	printf("	-d directory to standard output\n");
	printf("	-e erase file(s) from cpm diskette\n");
	printf("	-i input file(s) from cpm diskette\n");
	printf("	-l long directory (and block map) to standard output\n");
	printf("	-o output file(s) to CPM diskette from current logged-in area\n");
	printf("	-x output binary file(s) like -b\n");
	printf("	-z zap directory: refresh the disk\n");
	printf("cpm-wild-file-spec:\n");
	printf("<diskette> : { <user-number> : } <ambiguous-name> { . <ambiguous-type> }\n");
	printf("e.g.:	DY:5:?foo?bar.x*\n");
	printf("user number is assumed to be ALL unless explicitly given\n");
	exit();
END

main(argc,argv)
int	argc;
char	**argv;
BEGIN
char	*p;
char	*q;
char	*w;
int	usrnum;
char	*errmsg;	/* error message from bytmap construction */
int	errblk;		/* offending block number */
int	errslt;		/* offending directory slot number */
int	wildmake();	/* argument to xoutput() */
int	wildnext();	/* argument to xoutput() */
int	osget();	/* argument to xoutput() */
int	wilbmake();	/* argument to xoutput() */
int	wilbnext();	/* argument to xoutput() */
int	osbget();	/* argument to xoutput() */

	IF	(argc!=3)
	THEN	helpless("I need exactly 2 arguments");
	FI
	IF	(argv[1][0]!='-')
	THEN	helpless("1st argument must be a switch hence must begin with '-'");
	FI
	IF	(!isalpha(argv[1][1]))
	THEN	helpless("2nd character of 1st argument must be a letter viz '-b' '-d' '-e' '-i' '-l' '-o'");
	FI
	IF	(strlen(argv[1])!=2)
	THEN	helpless("2nd argument must be exactly '-' followed by a letter");
	FI

	/* parse wild filename */

	FOR	(p=argv[2];	*p!=':';	p++)
	DO	IF	(!*p)
		THEN	helpless("must have ':' after diskette name in 2nd argument");
		FI
	OD
	*p++ = 0;
	diskette = argv[2];
	FOR	(q=p;	isdigit(*q);	q++)
	DO	
	OD
	IF	(*q==':')
	THEN	/* we have a user # */
		sscanf(p,"%d",&usrnum);
		IF	(usrnum>15)
		THEN	helpless("user number must be less than 16");
		FI
		wuser = 1<<usrnum;
		p = q+1;
	ELSE	/* every user */
		wuser = ~0;
	FI
	/* here with user # valid & p points to rest of filename */
	FOR	(w=wname;	w<wname+8;	w++)
	DO	switch	(*p)
			{
		case '*':	*w = 0;				break;
		case '?':	*w = 0;	p++;			break;
		case 0:
		case '.':	*w = ' ';			break;
		default:	*w = toupper(*p++);		break;
			}
		;
	OD
	IF	(*p && *p!='.' && *p!='*')
	THEN	helpless("junk follows file name");
	FI
	IF	(*p=='*')
	THEN	p++;
	FI
	IF	(*p=='.')
	THEN	p++;
		/* p points to filetype */
		FOR	(w=wname+8;	w<wname+11;	w++)
		DO	switch	(*p)
				{
			case '*':	*w = 0;				break;
			case '?':	*w = 0;	p++;			break;
			case 0:
			case '.':	*w = ' ';			break;
			default:	*w = toupper(*p++);		break;
				}
			;
		OD
		IF	(*p=='*')
		THEN	p++;
		FI
	ELSE	copy(wname+8,"   ",3);	/* force explicit type */
	FI
	IF	(*p)
	THEN	helpless("junk follows file type");
	FI

	cbegin();	/* kickstart CP/M diskette */

	cgetdir();	/* suck the whole directory in */

	cbytmap(&errmsg,&errblk,&errslt);	/* build byte map */
	IF	(*errmsg)
	THEN	printf("\7error %s block=%d. directory-slot=%d.\n",errmsg,errblk,errslt);
	FI

	switch	(tolower(argv[1][1]))
		{
	case 'b':	cdsort();
			binput();
			break;
	case 'd':	cdsort();
			cbytmap(&errmsg,&errblk,&errslt);	/* build byte map */
			IF	(*errmsg)
			THEN	printf("\7error %s block=%d. directory-slot=%d.\n",errmsg,errblk,errslt);
			FI
			qdir();
			break;
	case 'l':	cdsort();
			cbytmap(&errmsg,&errblk,&errslt);	/* build byte map */
			IF	(*errmsg)
			THEN	printf("\7error %s block=%d. directory-slot=%d.\n",errmsg,errblk,errslt);
			FI
			xdir();
			xblock();
			break;
	case 'e':	delete();
			cputdir();
			break;
	case 'i':	cdsort();
			input();
			break;
	case 'o':	xoutput(wildmake,wildnext,osget);		/*pf01*/
			break;
	case 'x':	xoutput(wilbmake,wilbnext,osbget);		/*pf01*/
			break;
	case 'z':	for	(p=directory; p<(directory+DIRSIZ); p++)	*p=0xE5;
			cputdir();
			break;
	default:	helpless("invalid switch in first argument");
			break;
		}
	;

	cend();		/* forget (please!) CP/M diskette */
END

xblock()		/* print block map on stdout */
BEGIN
int	i;
int	bn;		/* block number */
char	*b;

	printf("   ");
	FOR	(i=0;	i<25;	i++)
	DO	printf(" %2d",i);
	OD
	FOR	(bn=0,b=bytmap;	b<bytmap+NBLOCKS;	b++,bn++)
	DO	
		IF	(!(bn%25))
		THEN	printf("\n%3d",bn);
		FI
		IF	(*b)
		THEN	printf("%3d",*b);
		ELSE	printf("   ");
		FI
	OD
	putchar('\n');
END

xdir()			/* directory command:
			   wild-filtered directory to stdout */
BEGIN
struct	dirent	*d;
char		*p;
char		*e;	/* end */
char		*b;	/* block */
unsigned	xsec;	/* number of sectors this extent */
int		fsec;	/* number of sectors this file */
int		i;

	fsec = 0;
	FOR	(d=directory; d<directory+DIRSIZ; d++)
	DO	
		IF	(cmatch(d))
		THEN	
#ifdef debug
printf("*d=");
p=d;
FOR	(i=0;	i<33;	i++)
DO	printf("%2x",p[i]);
OD
putchar('\n');
#endif
			printf("%2d%2d ",d-directory+1,(d->d_usernumber&0xFF));
			FOR	(p=d->d_name,e=p+8;	p<e;	p++)
			DO	putchar(*p&0x7F);
			OD
			putchar('.');
			FOR	(e=p+3;	p<e;	p++)
			DO	putchar(*p&0x7F);
			OD
			xsec = (d->d_lastrecord&0xFF);
			printf(" %2d%4d",d->d_extent,xsec);
			fsec += xsec;
			b = d->d_block;
			FOR	(e=b+(xsec+7)/8,i=0;	b<e;	b++,i++)
			DO	IF	(!(i&1))
				THEN	putchar(' ');
				FI
				printf("%3d",(*b)&0xFF);
			OD
			putchar('\n');
			IF	(d==(directory+DIRSIZ-1) || cfcomp(d,d+1))
			THEN	printf("                    %4d\n",fsec);
				fsec = 0;
			FI
		FI
	OD
END

qdir()			/* directory command:
			   wild-filtered directory to stdout */
BEGIN
struct	dirent	*d;
char		*p;
char		*e;	/* end */
unsigned	xsec;	/* number of sectors this extent */
int		fsec;	/* number of sectors this file */
#ifdef debug
int		i;
#endif

	fsec = 0;
	FOR	(d=directory; d<directory+DIRSIZ; d++)
	DO	
		IF	(cmatch(d))
		THEN	
#ifdef debug
printf("*d=");
p=d;
FOR	(i=0;	i<33;	i++)
DO	printf("%2x",p[i]);
OD
putchar('\n');
#endif
			xsec = (d->d_lastrecord&0xFF);
			fsec += xsec;
			IF	(d==(directory+DIRSIZ-1) || cfcomp(d,d+1))
			THEN
				FOR	(p=d->d_name,e=p+8;	p<e;	p++)
				DO	putchar(*p&0x7F);
				OD
				putchar('.');
				FOR	(e=p+3;	p<e;	p++)
				DO	putchar(*p&0x7F);
				OD
				printf("  %4d",fsec);
				fsec = 0;
			putchar('\n');
			FI
		FI
	OD
END

int	cxlate(secnum)	/* translate sector # to spanned sector #
			   by CP/M conventional rules */
int	secnum;		/* 0:25 */
BEGIN
#ifdef debug
	IF	(secnum<0 || secnum>25)
	THEN	printf("\7CXLATE:secnum=%d.\n",secnum);
		exit();
	FI
#endif

	return("\1\7\15\23\31\5\13\21\27\3\11\17\25\2\10\16\24\32\6\14\22\30\4\12\20\26"[secnum]-1);
END

cdsort()		/* sort directory */
BEGIN
int	cdcomp();

#ifdef debug
struct	dirent	*d;
char	*p;
char	*b;
FOR	(p=b=directory;	p<(b+(ENTSIZ*DIRSIZ));	p++)
DO	IF	(!((p-b)%ENTSIZ))
	THEN	putchar('\n');
	FI
	printf("%2x",0xFF&*p);
OD
putchar('\n');
#endif

	qsort(directory,DIRSIZ,ENTSIZ,cdcomp);

#ifdef debug
printf("ENTSIZ=%d.\n",ENTSIZ);
FOR	(d=directory;	d<directory+63;	d++)
DO
	FOR	(p=b=d;	p<b+ENTSIZ;	p++)
	DO	printf("%2x",0xFF&*p);
	OD
	putchar('\n');
	IF	(cdcomp(d,d+1)>0)
	THEN	error("\7qsort failed %d.",d-directory);
	FI
OD
#endif
END

cdcomp(a,b)		/* compare CP/M directory entries
			   return -1:0:1 as a<b:a==b:a>b
			   check <user><filename><type><extent>
			*/
struct	dirent	*a;
struct	dirent	*b;
BEGIN
int		r;
int		ax;
int		bx;

	r = cfcomp(a,b);
	IF	(!r)
	THEN	ax = a -> d_extent;
		bx = b -> d_extent;
		r = ax==bx ? 0 : (ax<bx?-1:1) ;
	FI
	return(r);
END

cfcomp(a,b)		/* compare CP/M directory entry's file names
			   return -1:0:1 as a<b:a==b:a>b
			   check <user><filename><type>
			*/
char		*a;
char		*b;
BEGIN
char		*l;	/* limit */

	FOR	(l=a+12;	a<l && (*a&0x7F)==(*b&0x7F);	a++,b++)	;
	IF	(a<l)
	THEN	return((*a&0x7F)<(*b&0x7F)?-1:1);
	FI
	return(0);
END

cgetdir()		/* read directory from CP/M diskette into 'directory' */
BEGIN
struct	dirent	*d;
int		sect;

	FOR	(d=directory,sect=0;	d<directory+DIRSIZ;	d+=4,sect++)
	DO	cgetsec(sect,d);
	OD
END

cputdir()		/* write from 'directory' to CP/M diskette */
BEGIN
struct	dirent	*d;
int		sect;

	FOR	(d=directory,sect=0;	d<directory+DIRSIZ;	d+=4,sect++)
	DO	cputsec(sect,d);
	OD
END

cbytmap(perr,pblk,pslot)	/* create a byte mape of which extent
				   each block belongs to
				   in:	directory
				   out:	bytmap
					*perr="" if OK else = error message
					*pblk = offending block
					*pslot = offending slot in directory
				*/
char		**perr;
int		*pblk;
int		*pslot;
BEGIN
struct	dirent	*d;
char		*b;
int		numblks;
unsigned	blk;
int		slot;

	zero(bytmap,NBLOCKS);
	*perr="";
	FOR	(d=directory;	d<directory+DIRSIZ;	d++)
	DO	IF	((d->d_usernumber&0xFF)!=EMPTY)
		THEN	slot = d - directory;
			numblks = ((d->d_lastrecord&0xFF)+7) / 8;
			FOR	(b=d->d_block;	b<(d->d_block)+numblks;	b++)
			DO	blk = (*b)&0xFF;
				IF	(blk<243)
				THEN	IF	(bytmap[blk])
					THEN	*perr="block used twice";
						*pslot = slot;
						*pblk = blk;
					ELSE	bytmap[blk] = slot+1;
					FI
				ELSE	*perr = "block number too big";
					*pslot = slot;
					*pblk = blk;
				FI
			OD
		FI
	OD
END

int	cmatch(d)	/* TRUE if directory entry matches wild spec */
struct	dirent	*d;
BEGIN
char		*p;
char		*w;
char		*e;	/* end */

	IF	( (d->d_usernumber&0xFF)>0xF || !((1<<(d->d_usernumber&0xFF))&wuser) )
	THEN	return(FALSE);
	FI
	FOR	(p=d->d_name,e=d->d_name+11,w=wname;	p<e;	p++,w++)
	DO	
		IF	(*w && *w!=(*p&0x7F))
		THEN	return(FALSE);
		FI
	OD
	return(TRUE);
END

int	cffblock()	/* return 0 if no free blocks
			   return 0-org 1st free block
			   "First Free BLOCK"
			*/
BEGIN
char		*b;

	FOR	(b=bytmap+2;	b<bytmap+242;	b++)
	DO	IF	(!*b)
		THEN	return(b-bytmap);
		FI
	OD
	return(0);
END


input()			/* input text file(s) from CP/M diskette to host */
BEGIN
int	oscreate();	/* create a text file on host */
int	spewtext();	/* output a sector to that file */
int	osafcreate();	/* close that file */
	xinput(oscreate,spewtext,osafcreate);
END

spewtext(sect,eofflag)	/* output sector to host, set eofflag if EOF */
char	sect[128];
int *	eofflag;
BEGIN
char	*p;
int	eofseen;	/* TRUE if EOF seen */

	eofseen = * eofflag;
	FOR	(p=sect;	!eofseen && p<sect+128;	p++)
	DO	IF	((*p&0x7F)==CPMEOF)
		THEN	eofseen = TRUE;
		ELSE	osput(*p);
		FI
	OD
	* eofflag = eofseen;
END

binput()		/* input file(s) in binary from CP/M to host */
BEGIN
int	osbcreate();	/* create a binary file on host */
int	spewbinary();	/* output a sector to that file */
int	osafbcreate();	/* close that file */
	xinput(osbcreate,spewbinary,osafbcreate);
END

spewbinary(sect,eofflag)/* output sector to host, set eofflag if EOF */
char	sect[128];
int *	eofflag;	/* never set: binary files in CP/M don't have it */
BEGIN
	ossecput(sect);	/* do the hard work */
END

			/* input file(s) from CP/M diskette to host */
xinput(mycreate,myputsec,mydone)
int	(*mycreate)();	/* create a file , given name: return 0 if failed */
int	(*myputsec)();	/* output a cp/m sector, return a end-of-file flag */
int	(*mydone)();	/* close the file we created */
BEGIN
unsigned	rib;	/* record in block   0:7 */
unsigned	therec;	/* record we want, before CP/M 'skew's it */
char		sector[128];
unsigned	block;
BOOL		eofseen;
struct	dirent	*d;
struct	dirent	*x1;	/* 1st extent of file */
char		outfilnam[20];
unsigned	rec;
unsigned	reccard;	/* number of records this extent */
char		*p;
char		*q;
char		*e;

	FOR	(d=directory;	d<directory+DIRSIZ;	d++)
	DO	IF	(cmatch(d))
		THEN	q = d->d_name;
			FOR	(p=outfilnam,e=q+8;	q<e && *q!=' ';	p++,q++)
			DO	
/*
printf("*q=%xx\n",*q);
*/
				*p = toupper(*q);
			OD
			*p++ = '.';
			q = d->d_type;
			FOR	(e=q+3;	q<e && *q!=' ';	p++,q++)
			DO	
/*
printf("*q=%xx\n",*q);
*/
				*p = toupper(*q);
			OD
			*p = 0;
			IF	((*mycreate)(outfilnam))
			THEN	x1 = d;
				FOR	(eofseen=FALSE;	!eofseen && !cfcomp(x1,d);	d++)
				DO	reccard = (d->d_lastrecord&0xFF);
					FOR	(rec=0;	!eofseen && rec<reccard;	rec++)
					DO	block = rec/8;
						rib = rec%8;
						therec = (d->d_block[block]&0xFF)*8 + rib;
						cgetsec(therec,sector);
						(*myputsec)(&sector,&eofseen);
					OD
				OD
				d--;
				(*mydone)();
			ELSE	printf("\7can't create file \"%s\"\n",outfilnam);
			FI
		FI
	OD
END

delete()		/* delete all files that match wild filespec
			   do NOT re-write directory */
BEGIN
struct	dirent	*d;

	FOR	(d=directory;	d<directory+DIRSIZ;	d++)
	DO	IF	(cmatch(d))
		THEN	d -> d_usernumber = EMPTY;
		FI
	OD
END

int cffslot()		/* return -1 or 1st free directory slot #
			   "First Free directory SLOT"
			*/
BEGIN
struct	dirent	*d;

	FOR	(d=directory;	d<directory+DIRSIZ;	d++)
	DO	IF	((d->d_usernumber&0xFF)==EMPTY)
		THEN	return(d-directory);
		FI
	OD
	return(-1);
END

struct	dirent	*newext(ext)	/* create new extent for current file */
				/* wname is the file name */
				/* wuser is the user mask */
				/* infilnam is its print name */
int		ext;		/* extent # */
BEGIN
int		slot;		/* directory slot */
struct	dirent	*d;
int		u;
int		usr;

	slot = cffslot();
	IF	(slot<0)
	THEN	printf("\nNo room in directory for extent %d. of file \"%s\"\n",ext,infilnam);
		cputdir();	/*pf02*/
		exit();
	FI
	d = directory + slot;
	IF	(wuser)
	THEN	FOR	(usr=0,u=wuser;	!(u&1);	u>>=1)
		DO	usr++;
		OD
	ELSE	usr=0;
	FI
	d -> d_usernumber = usr;
	copy(d->d_name, wname, 11);
	d->d_extent	=	ext;
	d->d_unknown	=	0;
	d->d_cluster	=	0;
	d->d_lastrecord	=	0;
	zero(d->d_block,16);
	return(d);
END

xoutput(make,next,get)	/* output (wild) file(s) to CP/M diskette */
int	(*make)();	/* make a wild-file-name template */
int	(*next)();	/* open next file matching template, for read */
int	(*get) ();	/* read next 8-bit char from file */
BEGIN
struct	dirent	*d;
char	*errmsg;	/* error message from bytmap construction */
int	errblk;		/* offending block number */
int	errslt;		/* offending directory slot number */
char	*s;		/* points into sector */
char	sector[128];	/* sector built up here */
int	arecord;	/* record # of the sector we are building in sector */
			/* 0 means we haven't built anything yet:
			   don't write anything to diskette */
int	extent;		/* 0:31		current extent number in file */
int	record;		/* 0:7		current record number in block */
int	block;		/* 0:15 	current block number in extent */
int	ablock;		/* block number of block we are writing */
int		c;

	(*make)();							/*pf01*/
	WHILE	((*next)())						/*pf01*/
	DO	/* now have new wname, infilnam */
		/* file is opened */

		/* delete existing destination file if any */
		FOR	(d=directory;	d<directory+DIRSIZ;	d++)
		DO	IF	(cmatch(d))
			THEN	d -> d_usernumber=EMPTY;
			FI
		OD
		cbytmap(&errmsg,&errblk,&errslt);	/* build byte map */
		IF	(*errmsg)
		THEN	printf("\7error %s block=%d. directory-slot=%d.\n",errmsg,errblk,errslt);
		FI
		/* now we know what free blocks we have */
		arecord= record= block= 0;
		extent = -1;
		FOR	(s=sector;	s<sector+128;	s++)
		DO	*s = CPMEOF;
		OD
		s = sector;
		WHILE	( (c=(*get)()) >=0)				/*pf01*/
		DO	IF	(s==sector)
			THEN	/* new record begun */
				IF	(!record)
				THEN	/* new block begun */
					IF	(!block)
					THEN	/* new extent begun */
						d = newext(++extent);
					FI
					IF	(ablock=cffblock())
					THEN	d->d_block[block]=ablock;
						bytmap[ablock] = d-directory+1;
					ELSE	printf("\7No room on diskette for block %d. of extent %d. of file \"%s\"\n",block,extent,infilnam);
						cputdir();	/*pf02*/
						exit();
					FI
					IF	((++block)>15)
					THEN	block=0;
					FI
				FI
				arecord = ablock*8 + record;
				IF	((++record)>7)
				THEN	record=0;
				FI
				d -> d_lastrecord += 1;
			FI
			*s = c;
			IF	((++s)>(sector+127))
			THEN	cputsec(arecord,sector);
				FOR	(s=sector;	s<sector+128;	s++)
				DO	*s = CPMEOF;
				OD
				s = sector;
			FI
		OD
		IF	(arecord)
		THEN	cputsec(arecord,sector);
		FI
		IF	(extent<0)
		THEN	newext(0);	/* empty file */
		FI
		/* file is closed automagically */
	OD
	cputdir();
END

/* end: cpm.c */
