/*	SD.C		C language version of the Super Directory
			program. Program lists directory entries
			with file sizes in alphabetic sequence
			by column. Program will pause at the end
			of a screen and ask if MORE is to be
			displayed. A control-C will abort the
			program; any other key will continue the
			display with the next page.

			Utility options:

				SD may be passed 'dash' options
				on the command line, i. e. 

					SD -QT <d:filename.typ>

				Q is a 'quick' listing in row
				  order inplace of the normal
				  column order.

				T is a 'type' listing with the
				  list in filetype - filename
				  sequence.

				Both, either or neither option
				may be specified.			*/

#define	VERSION	2
#define	RELEASE	0
#define	MOD	' '
#define	PATCH	0

#define CII
#define	noC86
#define noCMS

#ifdef	CII
#define	CHAR	char
#define	malloc	alloc
#include	"stdio.h"
#endif

#ifdef	C86
#define	CHAR	unsigned char
#include	"stdio86.h"
#endif

#ifdef	CMS
#define	CHAR	unsigned char
#include	"stdioms.h"
#endif

#define	scrnsz		24
#define	colsz		80

#define	CONIN		0x01
#define	CONSTAT		0x0B
#define	SELDRV		0x0E
#define FINDFRST	0x11
#define	FINDNEXT	0x12
#define	CURDRV		0x19
#define	SETDMA		0x1A
#define	GETVEC		0x1B
#define	DRINFO		0x1F
#define	SETDMAseg	0x33

typedef	unsigned int	uns;

typedef struct
	{
	CHAR	dr;
	CHAR	fn[8];
	CHAR	ft[3];
	CHAR	ex;
	CHAR	s1;
	CHAR	s2;
	CHAR	rc;
	CHAR	dn[16];
	CHAR	cr;
	CHAR	r0;
	CHAR	r1;
	CHAR	r2;
	} fcbs;

typedef	struct tsd
	{
	CHAR		dfn[12];
	uns		recs;
	struct tsd	*lotsd;
	struct tsd	*hitsd;
	} sds;

/*page*/

/*			SD main line code				*/

int	main(argc,argv)
	int		argc;
	CHAR		**argv;
	{
	register int	offset;
	register fcbs	*FCB;
	register CHAR	*dirwork;

	sds		*sdlist = NULL,
			*addsd();

	fcbs		*FCBfill();

	CHAR		*work,
			*dirinfo,
			*DPB,
			*setdma(),
			*getdinfo(),
	    		*getfinfo();

	uns		blkmask,
			exmask,
			filcnt = 0,
			sortmode = 0,
			quikmode = 0,
			dsmask,
			dsk;

	long		showsd(),
			bytcnt = 0;


	printf("Super Dir Version %d.%02d%c%c\n",
		VERSION,
		RELEASE,
		MOD,
		PATCH ? PATCH + '0' : ' ');

	if (--argc)
		{
		work = *++argv;
		if (*work == '-')
			{
			while (*++work)
				if (toupper(*work) == 'T')
					sortmode = 1;
				else if (toupper(*work) == 'Q')
					quikmode = 1;
			--argc;
			++argv;
			}
		}

	FCB = FCBfill(argc,argv);


	DPB = getdinfo(FCB->dr);
	blkmask = *(DPB + 3);
	exmask = *(DPB + 4);
	DPB += 5;
	dsmask = *(uns *)DPB;
	DPB -= 5;
	dsk = starslash((blkmask + 1), (dsmask + 1), 8);

	dirinfo = setdma();

	while (dirwork = getfinfo(FCB,dirinfo))
		{
		if (*(dirwork + 12) <= exmask)
			++filcnt;
		sdlist = addsd(	sortmode,
				*(dirwork + 12) & exmask,
				*(dirwork + 15),
				dirwork + 1,
				sdlist);
		}

	if (sdlist)
		{
		bytcnt = showsd(quikmode,sdlist,blkmask,filcnt);
		printf("--> %d files: %ldk bytes used",
			filcnt,
			bytcnt);
		}
	else
		printf("--> No files found");

	printf(" - %uk available",
		dsk - starslash((blkmask + 1),
				(getavec(FCB->dr, (dsmask >> 3) + 1)),
				(8))
				);

	} /* end main */

/*			starslash does forth type scaling		*/

int	starslash(a,b,c)
	unsigned	a,
			b,
			c;
	{
	long		al,
			bl,
			cl;
	int		r;

	al = a;
	bl = b;
	cl = c;
	r = (al * bl) / cl;
	return(r);
	} /* end starslash */

/*			Fcbfill will create an extended fcb
			from the filename passed as a parameter.
			If the filename is blank the fcb will
			default to all files on the default
			drive.						*/

fcbs	*FCBfill(c,v)
	int		c;
	CHAR		**v;
	{
	register CHAR	*s;
	register fcbs	*FCB;

/*			Grab some memory first				*/

	if (FCB = malloc(sizeof(fcbs)))
		{
		clear(FCB,36,'\0');
		FCB->ex = '?';
		FCB->s2 = '?';

/*			if there is a command line arg parse it.
			A drive letter (x:) defaults to all files
			on that drive (x:????????.???).			*/

		if (c)
			{
			clear(&FCB->fn,11,' ');
			s = *v;
			if (*(s+1) == ':')
				{
				FCB->dr = toupper(*s) - ('A' - 1);
				s += 2;
				}
			c = 0;
			while (*s)
				{
				if (*s == '.')
					break;
				if (*s == '*')
					while (c < 8)
						{
						FCB->fn[c] = '?';
						++c;
						}
				if (c < 8)
					{
					FCB->fn[c] = toupper(*s);
					++c;
					}
				++s;
				}
			if (*s++ == '.')
				{
				c = 0;
				while (*s)
					{
					if (*s == '*')
						while (c < 3)
							{
							FCB->ft[c] = '?';
							++c;
							}
					if (c < 3)
						{
						FCB->ft[c] = toupper(*s);
						++c;
						}
					++s;
					}
				}
			}
		if ((FCB->fn[0] == '\0') ||
		    (FCB->fn[0] == ' '))
			clear(&FCB->fn,11,'?');
		return(FCB);
		}
	else
		exit(1);
	} /* end FCBfill */

/*			Getdinfo gets the dpb for the requested
			disk. Returns pointer to dpb.			*/

CHAR	*getdinfo(drive)
	register CHAR	drive;
	{
	register int	save;
	register CHAR	*dpb;

#ifdef	C86
	CHAR		*getdpb();
#endif

	if (drive)
		{
		save = bdos(CURDRV,0);
		bdos(SELDRV,drive-1);
		}
#ifdef	CII
	dpb = (CHAR *)bdoshl(DRINFO,0);
#endif
#ifdef	C86
	dpb = getdpb();
#endif

	if (drive)
		bdos(SELDRV,save);

	return(dpb);
	} /* end getdinfo */

/*			Setdma grabs 128 bytes for the 'DMA'
			buffer and returns the address.			*/

CHAR	*setdma()
	{
	register CHAR	*buf;

	if (buf = malloc(128))
		{
		clear(buf,128,'\0');
#ifdef	C86
		bdos(SETDMAseg,getds());
#endif
		bdos(SETDMA,buf);
		return(buf);
		}
	exit(1);
	} /* end setdma */

/*			Getfinfo gets file info via a bdos
			call. The index into the fpb is returned.
			0 is returned if no file found.			*/

CHAR	*getfinfo(FCB,info)
	register fcbs	*FCB;
	register CHAR	*info;
	{
	static	int	FINDCMD = FINDFRST;
	register int	offset;

	if ((offset = bdos(FINDCMD,FCB)) == 255)
		return(NULL);
	else
		{
		FINDCMD = FINDNEXT;
		return(info + (offset << 5));
		}
	} /* end getfinfo */

/*			Addsd adds a file to a linked list. If
			the file is in the list the rec count
			will be updated.				*/

sds	*addsd(mode,maskext,siz,ifid,sdl)
	int		mode,
			maskext;
	register int	siz;
	register CHAR	*ifid;
	register sds	*sdl;
	{
	sds		*tmp;
	int		rslt;
	CHAR		fid[12];

	copy(fid,ifid,11);

	if (sdl)
		{
		if (mode)
			{
			if ((rslt = strncmp(fid + 8, &sdl->dfn[8],3)) == 0) 
				rslt = strncmp(fid,sdl->dfn,8);
			}
		else
			rslt = strncmp(fid,sdl->dfn,11);

		if (rslt == 0)
			sdl->recs += (maskext * 128 + siz);
		else if (rslt < 0)
			sdl->lotsd = addsd(mode,maskext,siz,fid,sdl->lotsd);
		else 
			sdl->hitsd = addsd(mode,maskext,siz,fid,sdl->hitsd);
		}
	else
		{
		sdl = (sds *)malloc(sizeof(sds));
		strncpy(sdl->dfn,fid,11);
		sdl->recs = maskext * 128 + siz;
		sdl->lotsd = NULL;
		sdl->hitsd = NULL;
		}
	return(sdl);
	} /* end addsd */

/*			Getavec gets the CP/M allocation vector
			and calculates the amount of disk storage
			used on the drive.				*/

int	getavec(drive,vecsize)
	register int	drive,
			vecsize;
	{
	register CHAR	*Avec;
	int		save,
			val,
			numblks = 0;

#ifdef	C86
	CHAR		*getvec();
#endif

	if (drive)
		{
		save = bdos(CURDRV,0);
		bdos(SELDRV,drive-1);
		}
#ifdef	CII
	Avec = (CHAR *)bdoshl(GETVEC,0);
#endif
#ifdef	C86
	Avec = getvec(vecsize);
#endif

	if (drive)
		bdos(SELDRV,save);

	while (vecsize--)
		{
		val = *Avec++;
		while (val)
			{
			numblks += (val & 1);
			val = val >> 1;
			}
		}

	return(numblks);
	} /* end getavec */

/*			Showsd displays the files in alpha
			order. Records are converted to kB
			before display.					*/

	CHAR		tb[21],
			*buf,
			*bp;
	int		lines,
			waiting = 0,
			fcnt,
			fc = 0;
	uns		tcnt;
	long		cnt = 0;

long	showsd(mode,sdl,bm,filcnt)
	int		mode;
	register sds	*sdl;
	register int	bm,
			filcnt;
	{

	lines = ((fcnt = filcnt) + 3) / 4;
	if (lines > (scrnsz - 2))
		lines = scrnsz - 2;
	buf = malloc(colsz * scrnsz);

	if (!mode)
		{
		clear(buf,colsz * scrnsz,' ');
		*(buf + (lines * colsz)) = 0;
		}

	lsttree(mode,sdl,bm);

	if (mode)
		{
		if (fc % 4)
			putchar('\n');
		}
	else
		{
		if (waiting)
			wait();
		bp = buf;
		while (lines--)
			{
			printf("\n%-79.79s",bp);
			bp += colsz;
			}
		putchar('\n');
		}
	if (fc > (4 * (scrnsz - 2)))
		{
		printf(" <more>\r");
		wait();
		}
	return(cnt);
	} /* end showsd */

/*			do the actually print function.
			print the tree recursivly.			*/

int	lsttree(mode,sdl,bm)
	int		mode;
	register sds	*sdl;
	register int	bm;

	{
	if (sdl)
		{
		lsttree(mode,sdl->lotsd,bm);
		tcnt = ((sdl->recs + bm) & ~bm) >> 3;
		cnt += tcnt;
		sprintf(tb,"%-8.8s.%-3.3s%4dk ",sdl->dfn,sdl->dfn+8,tcnt);
		if (mode)
			{
			if ((fc + 1) % 4)
				strcat(tb,"| ");
			else
				strcat(tb," \n");
			printf("%s",tb);
			}
		else
			{
			bp = buf + ((fc % lines) * colsz) + ((fc / lines) * 20);
			copy(bp,tb,18);
			if (((fc / lines) % 4) != 3)
				*(bp + 19) = '|';
			}
		if (++fc == (4 * (scrnsz - 2)))
			{
			fcnt -= fc;
			fc = 0;
			if (mode)
				{
				printf(" <more>\r");
				wait();
				}
			else
				{
				if (waiting)
					wait();
				bp = buf;
				while (lines--)
					{
					printf("\n%-79.79s",bp);
					bp += colsz;
					}
				printf("\n <more>\r");
				waiting = 1;
				lines = (fcnt + 3) / 4;
				if (lines > (scrnsz - 2))
					lines = scrnsz - 2;
				clear(buf,scrnsz * colsz,' ');
				*(buf + (lines * colsz)) = 0;
				}
			}
		lsttree(mode,sdl->hitsd,bm,fcnt);
		}
	} /* end lsttree */

/*			Wait for a key to be depressed			*/

int	wait()
	{
	while (!bdos(CONSTAT,0))
		;
	if (bdos(CONIN,0) == 0x03)
		exit(1);
	putchar('\r');
	} /* end wait */

/*			Copy does the same as a string copy but
			does not force a terminating null at the
			end of a string like some of the C compilers
			do. It also masks any high order bits.		*/

int	copy(d,s,l)
	register CHAR	*d,
			*s;
	register uns	l;
	{
	while (l--)
		*d++ = *s++ & 0x7F;
	}

/*			The following routines are required
			for CP/M -86 to handle missing functions
			and the segment regester requirements.		*/

#ifdef	C86

/*			Clear will set *s to value c for
			length l.					*/

int	clear(s,l,c)
	register CHAR	*s;
	register uns	l;
	register CHAR	c;
	{
	while (l--)
		*s++ = c;
	} /* end clear */

/*			getds will get the current data
			segment.					*/

int	getds()
	{
        struct
		{
		int	cs;
		int	ss;
		int	ds;
		int	es;
		} segs;

	segread(&segs);
	return(segs.ds);
	} /* end getds */

/*			getvec will get the allocation vector
			and move it to local storage for later
			use.						*/

CHAR	*getvec(vs)
	register int	vs;
	{
	register CHAR	*svec;
	struct
		{
		int	ax;
		int	bx;
		int	cx;
		int	dx;
		int	si;
		int	di;
		int	ds;
		int	es;
		} regs;

	regs.cx = GETVEC;
	regs.ds = getds();
	sysint(224,&regs,&regs);
	svec = malloc(vs);
	movblock(regs.bx,regs.es,svec,regs.ds,vs);
	return(svec);
	} /* end getvec */

/*			getdpb will get the dpb and move it to
			a local data area for later use.		*/

CHAR	*getdpb()
	{
	register CHAR	*sdpb;
	struct
		{
		int	ax;
		int	bx;
		int	cx;
		int	dx;
		int	si;
		int	di;
		int	ds;
		int	es;
		} regs;

	regs.cx = DRINFO;
	regs.ds = getds();
	sysint(224,&regs,&regs);
	sdpb = malloc(16);
	movblock(regs.bx,regs.es,sdpb,regs.ds,16);
	return(sdpb);
	} /* end getdpb */

#endif
