/****************************************************************************/
/*									    */
/*	                  S T A T   C O M M A N D                           */
/*	                  -----------------------                           */
/*	                                                                    */
/*	Common module for MP/M 2.0, MP/M-86 2.0, CP/M-80 2.2, CP/M-86 1.1   */
/*		    and Portable CP/M implementation (P-CP/M)		    */
/*									    */
/****************************************************************************/


/****************************************************************************/
/*									    */
/*            Copyright(C) 1975, 1976, 1977, 1978, 1979, 1980, 1981	    */
/*            Digital Research						    */
/*            Box 579							    */
/*            Pacific Grove, Ca						    */
/*            93950							    */
/*									    */
/*            Revised:							    */
/*              20 Jan  80  by Thomas Rolander				    */
/*              29 July 81  by Doug Huskey (for MP/M 2.0)		    */
/*              02 Sept 81  (for MP/M-86)				    */
/*              14 Nov  81  by Doug Huskey (for CP/M-86)		    */
/*	        21 Sept 82  by Zilog (translate to C)			    */
/*									    */
/*									    */
/* Modified 10/30/78 to fix the space computation			    */
/* Modified 01/28/79 to remove despool dependencies			    */
/* Modified 07/26/79 to operate under CP/M 2.0				    */
/* Modified 12/19/83 to fix C operator precedence bug			    */
/* Modified 12/21/83 to fix "value used before set" bug in display	    */
/*									    */
/****************************************************************************/

#include "copyrt.lit"

/****************************************************************************/
/*									    */
/*	Sign-on message to indicate release - PLEASE UPDATE!!!		    */
/*									    */
/****************************************************************************/

char	signonmsg[] = "Zilog CP/M-Z8000 STAT v1.0C 01/03/84\n";

/****************************************************************************/
/*									    */
/*	Note:  In an attempt to have a common source for all DRI O.S.	    */
/*	shared utilities the version is tested and a number of conditionally*/
/*	executed statements have been added for compatibility between MP/M 2*/
/*	and CP/M.							    */
/*									    */
/****************************************************************************/


/****************************************************************************/
/* Generation instructions                                                  */
/****************************************************************************/

/* To be provided later for Z8000 version				    */

/****************************************************************************/
/*									    */
/*	 H O W   I T   W O R K S   A N D   W H A T   I T   D O E S	    */
/*       ---------------------------------------------------------	    */
/*									    */
/*									    */
/* 	STAT gives information about disk drives, files and devices.  It    */
/*	can also change the attributes of files and disk drives, and the    */
/*	assignment of logical devices to physical devices.  STAT knows      */
/*	little about the extended file control information and disk	    */
/*	labels available under MP/M and CP/M 3.0, but is still required     */
/*	under these operating systems because many of its functions are     */
/*	not duplicated by other, more recent, utilities such as SET and     */
/*	SHOW.								    */
/*									    */
/*	The command takes several forms:				    */
/*									    */
/*	STAT VAL:							    */
/*		Display valid forms of the STAT command (ie a summary of    */
/*		the information set out below)				    */
/*									    */
/*	STAT								    */
/*		Display status of all checked drives (that is, drives	    */
/*		which have been explicitly or implicitly referenced since   */
/*		the last warm boot), saying how much free space is on	    */
/*		each and whether it is read/write or read-only		    */
/*									    */
/*	STAT d:								    */
/*		Display status as above for a specified disk drive d:	    */
/*									    */
/*	STAT DSK:							    */
/*	STAT d:DSK:							    */
/*		Same as first two cases above, except that considerably     */
/*		more information is displayed: capacity in 128 byte records,*/
/*		capacity in Kbytes, total number of directory entries,	    */
/*		number used, number of records allocated per directory	    */
/*		entry (physical extent), number of records per block and    */
/*		per track and the number of reserved tracks		    */
/*									    */
/*	STAT d:=RO							    */
/*		Set a specified disk drive to read-only status.  It retains */
/*		this status until the next warm boot or until...	    */
/*									    */
/*	STAT d:=RW							    */
/*		Set a specified disk drive to read-write status.  Cancels   */
/*		a previous STAT d:=RO					    */
/*									    */
/*	STAT USR:							    */
/*	STAT d:USR:							    */
/*		Lists the users who own files on the named disk drive.  If  */
/*		no drive is named, the current drive is assumed		    */
/*									    */
/*	STAT DEV:							    */
/*		Display the current logical to physical device assignments  */
/*		(Not applicable to MP/M)				    */
/*									    */
/*	STAT log:=phy:							    */
/*		Assign logical device log: to physical device phy:, for	    */
/*		example STAT CON:=TTY: (Not applicable to MP/M)		    */
/*									    */
/*	STAT filespec							    */
/*	STAT filespec SIZE						    */
/*		Display detailed information about the file or files	    */
/*		referenced by filespec (filespec may be ambiguous or	    */
/*		unambiguous.)  Information is number of physical records,   */
/*		physical size in Kbytes, number of FCB's (directory	    */
/*		entries) used for this file and attributes.  If the word    */
/*		SIZE follows filespec, the logical record count is also     */
/*		printed.  This may be greater than the physical record	    */
/*		count if the file is sparce (not all logical records used.) */
/*									    */
/*	STAT filespec RO | RW | SYS | DIR				    */
/*		Sets or clears attributes of specified file or files.  RO   */
/*		(read-only) and RW (read/write) are mutually exclusive, as  */
/*		are SYS (system file - does not normally appear in	    */
/*		directory listings) and DIR (directory file - can be seen   */
/*		in directory listings.)  Thus, no more than two attributes  */
/*		may be specified on the same command line.  The order in    */
/*		which attributes are specified is not important.	    */
/*									    */
/*									    */
/*	To find out how this program implements these functions, it is best */
/*	to start at the end of this listing, which defines the top-level    */
/*	function, and work backwards through progressively lower-level	    */
/*	functions as and when it becomes necessary.			    */
/*									    */
/*	For historical reasons, much of the communication between functions */
/*	uses global variables rather than parameters.  The main things to   */
/*	remember are that all global variables are defined at the top of    */
/*	the program before any function definitions, and that all globals   */
/*	NEED to be global because they are referenced by more than one	    */
/*	function.  A cross reference listing of some sort is useful in	    */
/*	tracking interdependencies caused by global variable usage.	    */
/*									    */
/*					Dominic Dunlop, Zilog Inc.  10/20/82*/
/*									    */
/****************************************************************************/

/****************************************************************************/
/*									    */
/*	   E X T E R N A L   A N D   B D O S   I N T E R F A C E            */
/*	   -----------------------------------------------------            */
/*									    */
/****************************************************************************/

#include "portab.h"				/* Portable C definitions   */
#include "bdos.h"				/* CP/M, MP/M call def's    */
#include "basepage.h"				/* CP/M basepage structure  */

/****************************************************************************/
/*		        External data and functions                         */
/****************************************************************************/

extern	char		*sbrk();		/* Memory allocation	    */
extern	char		*_break;		/* Top of allocated memory  */

struct 	fcbtab		*fcb;			/* Ptr to 1st basepage FCB  */
char			*buff;			/* Ptr to basepage DMA buff */


/****************************************************************************/
/*			    Version dependencies                            */
/****************************************************************************/

						/* Version number fields    */
#define	CPM		0x0000			/* Vanilla flavor CP/M	    */
#define MPM		0x1000			/* MP/M			    */
#define PCPM		0x2000			/* Portable CP/M	    */
#define ONE_X		0x00			/* Version 1.x		    */
#define TWO_X		0x20			/* Version 2.x		    */
#define THREE_X		0x30			/* Version 3.x		    */

#define VOID_GET_DPB(x)	(((x) & 0xff00) == PCPM)
#define HAS_GET_DFS(x)	((((x) & 0xff00) != CPM) || (((x) & 0xf0) >= THREE_X))
#define HAS_GSET_SCB(x)	(((x) & 0xf0) >= THREE_X)


/****************************************************************************/
/* Definitions                                                              */
/****************************************************************************/

						/* Absolute value (beware   */
						/*   side effects!)	    */
#define abs(a)	(((a) >= 0) ? (a) : -(a))

#define SAFETY		0x400			/* Stack expansion space    */
						/*   after memory allocation*/
#define WIDTH		72			/* Default console width    */
#define	SPKSHF		3			/* log base 2 of sectors/K  */
#define DELETED		0xe5			/* Deleted file mark	    */
#define T_SIZE		4			/* Size of command token    */
#define NSIZE		(sizeof fcb->fname)	/* Size of fname	    */
#define	FNAM		(NSIZE + sizeof fcb->ftype)/* " + size of ftype	    */
#define	ROFILE		ftype[0]		/* Read-only file	    */
#define	SYSFILE		ftype[1]		/* "System" (invisible) file*/
#define	ARCHIV		ftype[2]		/* Archived  file	    */
#define HAS_XFCB	s1			/* Has extended FCB	    */
#define	ATTRB1		fname[0]		/* Attribute F1' 	    */
#define	ATTRB2		fname[1]		/* Attribute F2' 	    */
#define	ATTRB3		fname[2]		/* Attribute F3' 	    */
#define	ATTRB4		fname[3]		/* Attribute F4' 	    */


/****************************************************************************/
/* Miscellaneous global data                                                */
/****************************************************************************/

char			cdisk;			/* Current disk		    */
char			token[T_SIZE];		/* Parsed cmd line token    */
char			user_code;		/* Current user code	    */
BOOLEAN			sizeset	= FALSE;	/* TRUE if printing size    */
BOOLEAN			set_attribute = FALSE;	/* TRUE if setting attribute*/
BOOLEAN			error_free = TRUE;	/* No duplicate block error */  
BOOLEAN			word_blks;		/* FCB block addr's 16 bits */
int			kpb;			/* Kbytes per disk block    */
int			scase1, scase2;		/* Attributes required	    */
UWORD			ver;			/* OS version number	    */
UWORD			dcnt;			/* Current directory code   */
UWORD			rodisk;			/* Read only disk vector    */ 
UWORD			nfcbs  = 0;		/* Total number of FCB's    */

/****************************************************************************/
/* Data associated with disk allocation                                     */
/*									    */
/* NOTE: The local copy of the disk parameter block is used only in those   */
/*	 CP/M implementations where BDOS function 31 returns a copy of the  */
/*	 DPB.  Other implementations access the BIOS' own copy of the DPB.  */
/*									    */
/*	 The length of the undimensioned array ALLOC is fixed at runtime    */
/*	 according to the number of blocks on the disk being examined	    */
/****************************************************************************/

struct	dpbs		dpb;			/* Local copy of DPB	    */
struct	dpbs		*dpba = &dpb;		/* Ptr to disk parameter blk*/

BYTE			*alloc;			/* Disk map, one bit / block*/


/****************************************************************************/
/* Data associated with collecting and sorting filenames                    */
/*									    */
/* NOTE: The lengths of the two undimensioned arrays are fixed at runtime   */
/*	 according to the amount of free memory available		    */
/****************************************************************************/

UWORD			fcbn;			/* FCB's collected so far   */
UWORD			fcbmax;			/* Size of the arrays below */

struct	fcbhalf					/* First 16 bytes of FCB:   */
	{					/*   used in sorting files  */
		BYTE	drive;			/*   and gathering data	    */
		BYTE	fname[8];
		BYTE	ftype[3];
		BYTE	extent;
		BYTE	s1;
		UWORD	kcnt;			/* Kilobyte count ) Not from*/
		UWORD	rcnt;			/* Record count   ) FCB     */
	} *fcbs;				/* Base for collecting FCB's*/

struct	fcbhalf		**finx;			/* Array of FCB addresses   */
struct	fcbhalf		*fcbsa;			/* Index into FCB's	    */
struct	fcbtab		*bfcba;			/* Ptr to directory entry   */

struct	block_no				/* FCB pointer to disk block*/
	{					/* May be either byte or    */
		BYTE	first;			/*   8080-ordered word	    */
		BYTE	second;
	};

/****************************************************************************/
/* Messages to suit (almost) every occasion                                 */
/* (all these messages are used more than once - messages used only once    */
/*  are coded in line)							    */
/****************************************************************************/

char	drivename[]	= " Drive ";
char	readonly[]	= "Read Only (RO)";
char	readwrite[]	= "Read Write (RW)";
char	entries[]	= "  Directory Entries";
char	filename[]	= "d:filename.typ";
char	use[]		= "Use: STAT ";
char	invalid[]	= "Invalid Assignment";
char	set_to[]	= " set to ";
char	record_msg[]	= "128 Byte Record";
char	sattrib[]	= "[RO] [RW] [SYS] or [DIR]";


/****************************************************************************/
/* Valid first tokens on command line                                       */
/****************************************************************************/

char	devl[]		= "CON:AXI:AXO:LST:DEV:VAL:USR:DSK:";

#define L_SIZE		((sizeof devl - 1) / T_SIZE)/* Number of entries    */
#define	OPT_USR		7			/* Positions (starting at 1)*/
#define OPT_DSK		8			/*   of some options	    */


/****************************************************************************/
/* Valid attributes for files, disks                                        */
/****************************************************************************/

char	attribl[]	= "RO  RW  SIZESYS DIR ";

#define A_SIZE		((sizeof attribl - 1) / T_SIZE)/* Number of entries */

#define OPT_RO		1			/* Positions of attributes  */
#define OPT_RW		2			/* NOTE: do not change this */
#define OPT_SIZE	3			/*   order: error checking  */
#define OPT_SYS		4			/*   depends on it	    */
#define OPT_DIR		5


/****************************************************************************/
/* Valid names for physical devices                                         */
/****************************************************************************/

char	devr[] =
	"TTY:CRT:BAT:UC1:TTY:PTR:UR1:UR2:TTY:PTP:UP1:UP2:TTY:CRT:LPT:UL1:";

#define P_SIZE		((sizeof devr - 1) / T_SIZE)/* Number of entries    */

/****************************************************************************/
/*									    */
/*	     	     L O W _ L E V E L   F U N C T I O N S                  */
/*	             -------------------------------------                  */
/*									    */
/****************************************************************************/


		/********************************/
		/*		 		*/
		/*          B L A N K S         */
		/*		 		*/
		/********************************/

VOID						/* Print b blanks on the    */
blanks(b)					/*   console		    */
int	b;
{
	while (b--)
        	_conout(' ');
}


		/********************************/
		/*		 		*/
		/*        P R I N T X           */
		/*		 		*/
		/********************************/

VOID						/* Print a null-terminated  */
printx(a)					/*   string on the console  */
char	*a;
{
        while (*a) 
        	_conout(*a++);
}
  
  
		/********************************/
		/*		 		*/
		/*         N E W _ L N          */
		/*		 		*/
		/********************************/

VOID						/* Send carriage-return,    */
new_ln()					/*   line-feed to console   */
{
	_conout('\n');
	_conout('\r');
}


		/********************************/
		/*		 		*/
		/*   T E S T _ K B D _ E S C    */
		/*		 		*/
		/********************************/

VOID						/* If a character has been  */
test_kbd_esc()					/*   entered at the console,*/
{						/*   discard it and exit    */
	if (_constat())				/*   from STAT at once	    */
	{ 
		_conin();			/* Read & discard character */
		new_ln();
		printx("* Aborted *");
		_exit(1);
	}
}


		/********************************/
		/*		 		*/
		/*           C R L F            */
		/*		 		*/
		/********************************/

VOID						/* Send CR - LF sequence    */
crlf()						/*   then check if user	    */
{						/*   wants to abort (has    */
    new_ln();					/*   hit any key)	    */
    test_kbd_esc();
}


		/********************************/
		/*		 		*/
		/*         P R I N T            */
		/*		 		*/
		/********************************/

VOID						/* Print a null-terminated  */
print(a)					/*   string on a new line   */
char	*a;
{
	crlf();
	printx(a);
}


		/********************************/
		/*		 		*/
		/*       C O L U M N S          */
		/*		 		*/
		/********************************/

int						/* Returns the number of    */
columns()					/*   print columns on the   */
{						/*   console (gives default */
	static struct scbpb	colpb =		/*   if not BDOS 3)	    */
	{
		0x1a,				/* (Gives offest of con_w)  */
		GET
	};

	if (HAS_GSET_SCB(ver))
#ifdef HILO					/* _gset_scb returns a word */
		return (_gset_scb(&colpb) >> 8);/* We want more significant */
#else						/*   byte on HILO machines, */
		return (_gset_scb(&colpb));	/*   less significant on    */
#endif						/*   LOHI		    */
	else
		return (WIDTH);
}
		

		/********************************/
		/*		 		*/
		/*        S E L E C T           */
		/*		 		*/
		/********************************/

VOID						/* Select the disk d, making*/
select(d)					/*   it the current disk    */
int	d;
{
	rodisk = _get_ro();			/* Get current Read-Only vec*/
	cdisk = d;
	_sel_disk(d);
}


		/********************************/
		/*		 		*/
		/*       S E T _ K P B          */
		/*		 		*/
		/********************************/

VOID						/* Calculate the size of a  */
set_kpb()					/*   disk block in kilobytes*/
{						/* Also decide whether disk */
						/*   block addresses are 8  */
						/*   or 16 bits long	    */

/****************************************************************************/
/*									    */
/* We want kpb (kbytes per block) so that each time we find		    */
/*   a block address we can add kpb k to the kilobyte accumulator	    */
/*   for file size.  We derive kpb from bls - the base 2 logarithm of the   */
/*   number of 128-byte records per block - as follows:			    */
/*									    */
/*	 BLS  	      RECS/BLK	      K/BLK	    BLS - 3		    */
/*	  3               8             1              0		    */
/*	  4              16             2              1		    */
/*	  5              32             4              2		    */
/*	  6              64             8              3		    */
/*	  7             128            16              4		    */
/*									    */
/****************************************************************************/

	if (VOID_GET_DPB(ver))			/* Get DPB (just how depends*/
	{					/*   on CP/M implementation)*/
		_get_dpb(&dpb);
	}
	else
	{
		dpba = (struct dpbs *) _get_dpa();
	}
	kpb = 1 << (dpba->bls - SPKSHF); 	/* Calculate K per block    */
	word_blks = (dpba->mxa > 255);
}


		/********************************/
		/*		 		*/
		/*    S E L E C T _ D I S K     */
		/*		 		*/
		/********************************/

VOID						/* Select a disk and	    */
select_disk(d)					/*   calculate how many	    */
int	d;					/*   kbytes/block it has    */
{
	select(d);
	set_kpb();
}


		/********************************/
		/*		 		*/
		/*          C O U N T           */
		/*		 		*/
		/********************************/

UWORD						/* Returns the number of    */
count()						/*   KBYTES remaining on    */
{						/*   current disk	    */
	register UWORD	k, all_blks;
	register char	*all_vec, *all_end;
	LONG		maxall;

	if (HAS_GET_DFS(ver))			/* BDOS may do most of the  */
	{					/*   work for us	    */
		maxall = 0;
		_setdma(&maxall);
		_get_dfs(cdisk);
		return(maxall >> SPKSHF);	/* Convert from records to  */
						/*   K (3 places)	    */
	}
						/****************************/
						/*			    */
						/* BEWARE!  _get_alloc()    */
						/* does not work well on    */
						/* systems with bank-switch,*/
						/* segmented and/or protec- */
						/* ted memory.  Such systems*/
						/* MUST use _get_dfs() above*/
						/*			    */
						/****************************/

	all_vec = (char *) _get_alloc();	/* Find where allocation vec*/
	all_end = &all_vec[dpba->mxa / 8];	/*   starts and ends	    */

	for (all_blks = 0; all_vec <= all_end; all_vec++)
	{					/* Count up number of bits  */
		k = *all_vec;			/*   set in vector (use	    */
		do				/*   incrementing pointer   */
		{				/*   because quicker than   */
			all_blks += k & 1;	/*   incrementing index)    */
		} while (k >>= 1);		/* At end, we have count of */
	}					/*   allocated blocks	    */

	if ((maxall = dpba->mxa) <= all_blks)	/* If disk is full, return 0*/
		return (0);
	
	return (kpb * (maxall - all_blks + 1));	/* Not full: multiply blocks*/
}						/*   free by kbytes/block   */


		/********************************/
		/*		 		*/
		/*          F I L L             */
		/*		 		*/
		/********************************/

VOID						/* Fill the string s with   */
fill(s,f,c)					/*   the character f for c  */
register char	*s;				/*   positions		    */
register int	f;
register UWORD	c;
{
	while (c--)
		*s++ = f;
}

/****************************************************************************/
/*									    */
/*	            M E M O R Y   A L L O C A T I O N                       */
/*	            ---------------------------------                       */
/*									    */
/*	A word about how memory is allocated (a picture is worth a thousand,*/
/*	so it is rumored):						    */
/*									    */
/*	+-------------------------------+  <--- Address zero		    */
/*	|				|				    */
/*	|	     S T A T		|	Only this area is allocated */
/*	|				|	when STAT is initially	    */
/*	|  C O D E ,  D A T A,  B S S	|	loaded by CP/M (STAT is     */
/*	|				|	very approx 8k in length)   */
/*	|				|				    */
/*	+-------------------------------+  <--- alloc points here	    */
/*	|				|				    */
/*	|	DISK ALLOCATION MAP	|	Allocated by all_map()	    */
/*	|				|				    */
/*	+-------------------------------+  <--- finx points here	    */	/*	|  				|				    */
/*	|  ARRAY OF POINTERS INTO fcbs	|	Allocated by all_fcb	    */
/*	|				|				    */
/*	+-------------------------------+  <--- fcbs points here	    */
/*	|				|				    */
/*	| ARRAY OF FILE CONTROL BLOCKS	|	Allocated by all_fcb	    */
/*	|				|				    */
/*	+-------------------------------+				    */
/*	|				|				    */
/*	|      UNALLOCATED MEMORY	|				    */
/*									    */
/*	STAT only grows beyond its load size if asked to examine files.	    */
/*	The disk allocation map is typically very short.  The array of	    */
/*	pointers and the array of file control blocks take up between them  */
/*	the rest of available memory or enough to be able to accomodate the */
/*	maximum number of directory entries possible on the disk under	    */
/*	scrutiny, whichever is smaller.  This typically means that, taken   */ 
/*	together, they total about 60% of the length of a disk track.	    */
/*	Note that only 18 bytes (not 32 or 36) are stored for each file	    */
/*	(not for each extent.)						    */
/*									    */
/****************************************************************************/


		/********************************/
		/*		 		*/
		/*        A L L _ M A P         */
		/*		 		*/
		/********************************/

char *						/* Allocate memory to hold  */
all_map()					/*   disk allocation map    */
{						/* Delivers the address of  */
	register char 	*addr;			/*   the map		    */
						/* NOTE: must be called	    */
						/*   BEFORE all_fcb	    */

						/* Disk has mxa blocks.     */
						/* Take a bit for each	    */
	if ((int) (addr = sbrk(dpba->mxa / 8 + 1) ) == -1)
	{
		print("Insufficient Memory For Allocation Map");
		_exit(1);
	}
	fill(addr, 0, dpba->mxa / 8);		/* Clear space out	    */
	return (addr);
}


		/********************************/
		/*		 		*/
		/*        A L L _ F C B         */
		/*		 		*/
		/********************************/

UWORD						/* Allocate memory to hold  */
all_fcb()					/*   FCB's and FCB pointers */
{
	register UWORD	dimension;		/* Sets pointers to base of */
						/*   each array.  Returns   */
						/*   no. of array elements  */

						/* How much room between    */
						/*   start of free memory   */
						/*   and stack end (leave   */
						/*   a safety margin)?	    */

	dimension = ((UWORD) _base->freelen - SAFETY) /
		      (sizeof *finx + sizeof (struct fcbhalf));
	if (dimension == 0)
	{
		print("Insufficient Memory for FCB's");
		_exit(1);
	}
	dimension = (dpba->dmx + 1 < dimension ) ?/* We actually only need  */
		      dpba->dmx + 1 : dimension;/*   enough for maximum no. */
						/*   of directory entries   */

						/* Allocate the space	    */
	finx = (struct fcbhalf **) sbrk((sizeof *finx) * dimension);
	fcbs = fcbsa = (struct fcbhalf *)
		sbrk((sizeof (struct fcbhalf)) * dimension);

						/* Clear it out		    */
	fill((char *) finx, 0, dimension *
		      (sizeof *finx + sizeof (struct fcbhalf)));
	return (dimension);
}

/****************************************************************************/
/*									    */
/*	        C O M M A N D   L I N E   S C A N N I N G                   */
/*	        -----------------------------------------                   */
/*									    */
/****************************************************************************/


		/********************************/
		/*		 		*/
		/*            S C A N           */
		/*		 		*/
		/********************************/

VOID						/* Put the next input value */
scan()						/*   into the token accum-  */
{						/*   ulator		    */
	static int	ibp = 1;		/* Input buffer pointer	    */
	int		b, scandex;

	while (buff[ibp++] == ' ');		/* Skip leading spaces	    */

	if (buff[--ibp] == '[') 		/* Back up to first char    */
		ibp++ ;				/*   which is not ' ' or '['*/

	scandex = 0;				/* Initialize accum index   */
	fill(token, ' ', T_SIZE);		/*   & clear accumulator    */

	while ((b = buff[ibp]) > 1)		/* Get char: exit if 0 or 1 */
	{
		switch (b)			/* Does character terminate */
		{				/*   the token?		    */
		  case ' ':
		  case ',':
		  case ':':
		  case '[':
		  case '=':
			buff[ibp] = 1;		/* Yes: set termination	    */
			break;			/*   condition		    */
		
		  default:			/* No (unless it's a control*/
			if (b < ' ')		/*   character)		    */
				buff[ibp] = 1;
			else
				ibp++ ;		/* Point to next char	    */
		}
		
		switch (b)			/* Add char to accumulator  */
		{				/*   unless it's one of	    */
		  case '/':			/*   these		    */
		  case '_':
		  case ']':
		  case ',':
			break;
		
		  default:			/* Put next character of    */
			if (scandex < T_SIZE)	/*   token (which my be no  */
				token[scandex] = b;/*longer than 4 chars)   */
			scandex++;		/*   into accumulator	    */
		}
	}

	if (b != 0)  				/* At end of command line?  */
		ibp++;				/* No: bump pointer	    */
}


		/********************************/
		/*		 		*/
		/*   P A R S E _ A S S I G N    */
		/*		 		*/
		/********************************/

BOOLEAN						/* Parse an assignment into */
parse_assign()					/*   the accumulator	    */
{
	scan();					/* Get a token		    */
	if (token[0] != '=') 			/* Is it '='?		    */
		return (FALSE);			/* No: not an assignment    */
	scan(); 				/* Yes: get token to be	    */
	return (TRUE);				/*   assigned		    */
}


		/********************************/
		/*		 		*/
		/*     P A R S E _ N E X T      */
		/*		 		*/
		/********************************/

BOOLEAN						/* Parse next item into	    */
parse_next()					/*   accumulator	    */
{
	scan();
	if (token[0] == ' ')			/* Was it just a delimiter? */
	{
		scan();				/* Yes: try again	    */
		if (token[0] == ' ') 		/* Another delimiter (or    */
			return (FALSE);		/*   line end): give up	    */
	}
	return (TRUE);
}


		/********************************/
		/*		 		*/
		/*          M A T C H           */
		/*		 		*/
		/********************************/

int						/* Try to match 4-character */
match(va, vl)					/*   token in accumulator to*/
register char	*va;				/*   entry in table of vl   */
int	vl;					/*   entries at va	    */
{						/* Return index into table  */
						/*   (> 0) if match found   */
	register int	j, k, sync;		/*   else 0		    */
	BOOLEAN		found;

	j = 0;					/* j indexes devices table  */
	for (sync = 1; sync <= vl; sync++)	/* sync counts devices tried*/
	{
		found = TRUE;			/* Be optimistic!	    */
		for (k = 0; k < T_SIZE; ) 	/* Compare characters	    */
		{
			if ((va[j] == ' ') 	/* Don't attempt to match   */
			   && found)		/*   trailing blanks on	    */
				break;		/*   table entry - match OK */

			if (va[j++] != token[k++])/* Make comparison	    */
				found = FALSE;	/* It failed.  Stay in inner*/
		}				/*   loop to skip to next   */
						/*   possible value	    */
		if (found)			/* Success!		    */
			return (sync);
	}
	return (0);				/* Failure		    */
}

/****************************************************************************/
/*									    */
/*	             D I S P L A Y   F U N C T I O N S                      */
/*	             ---------------------------------                      */
/*									    */
/****************************************************************************/


		/********************************/
		/*		 		*/
		/*       P D E C I M A L        */
		/*		 		*/
		/********************************/

VOID						/* Print long unsigned value*/
pdecimal(v, prec, zerosup)			/*   to set precision	    */
						/*   1 = 1 place, 10 = 2    */
						/*   places etc.)	    */
UWORD	v;					/* Value to print	    */
UWORD	prec;					/* Precision		    */
BOOLEAN	zerosup;				/* Zero suppression flag    */
{
	int	d;				/* Current decimal digit    */

	while (prec != 0)
	{
		d = v / prec;			/* Get next digit	    */
		v %= prec;			/* Get remainder back to v  */
		if (((prec /= 10) != 0)		/* Is this a supressed	    */
		   && zerosup) {		/*   leading zero?	    */
			if (d == 0)
				_conout(' ');	/* Yes: print space	    */
			else
			{			/* No: print digit	    */
				zerosup = FALSE;
				_conout('0' + d);
			}
		}
		else	_conout('0' + d);
	}
}


		/********************************/
		/*		 		*/
		/*        P _  L O N G          */
		/*		 		*/
		/********************************/

VOID						/* Print an unsigned long   */
p_long(value)					/*   value to seven digit   */
LONG	value;					/*   precision, with commas */
{						/*   every three digits     */

	UWORD	thous[4];			/* 3-digit chunks of value  */
	register int j;
	register int zerosup;

	zerosup = TRUE;
	for (j = sizeof thous / sizeof (UWORD); --j >= 0;)
						/* Break the number up into */
	{					/*   3 digit chunks (most   */
		thous[j] = value % 1000;	/*   significant thous[0])  */
		value /= 1000;
	}
	for (j = 0; j < (sizeof thous / sizeof (UWORD)); j++)
						/* Print out chunks, most   */
	{					/*   significant first	    */
		switch(j)			/* Handle special cases (all*/
		{				/*   cases are special!)    */
		  case 0:
			break;			/* Billions: don't print    */

		  case 1:			/* Millions: just one digit */
			if (thous[j] == 0)	/*   or spaces if zero	    */
				printx("  ");
			else
			{
				pdecimal(thous[j], 1, zerosup);
				_conout(',');
				zerosup = FALSE;
			}
			break;
		
		  case ((sizeof thous / sizeof (UWORD)) - 1):
						/* Hundreds, tens, units:   */	
				pdecimal(thous[j], 100, zerosup);
						/*  always print   */
				zerosup = FALSE;
				break;

		  default:			/* The rest: print zero as  */
			if (thous[j])		/*   spaces, follow digits  */
			{			/*   with comma		    */
				pdecimal(thous[j], 100, zerosup);
				_conout(',');
				zerosup = FALSE;
			}
			else
				printx("    ");
		}
	}
}


		/********************************/
		/*		 		*/
		/*         P _ U N L            */
		/*		 		*/
		/********************************/

VOID						/* Print unsigned long value*/
p_unl(value)					/*   on a new line followed */
LONG	value;					/*   by ": "		    */
{
	crlf();
	p_long(value);
	printx(": ");
}
	

		/********************************/
		/*		 		*/
		/*        S H O W _ D V         */
		/*     S H O W _ D R I V E      */
		/*		 		*/
		/********************************/

VOID						/* Display name (A: - P:)   */
show_dv()					/*   of current drive	    */
{
	_conout(cdisk + 'A');
	_conout(':');
}

VOID						/* Same as above, followed  */
show_drive()					/*   by space		    */
{
	show_dv();
	_conout(' ');
}


		/********************************/
		/*		 		*/
		/*        S H O W _ U S R       */
		/*		 		*/
		/********************************/

VOID						/* Display current user	    */
show_usr(user)					/*   number		    */
char	user;
{
	printx("User :");
	pdecimal((UWORD) user, 100, TRUE);
}


		/********************************/
		/*		 		*/
		/*    D R I V E S T A T U S     */
		/*		 		*/
		/********************************/

VOID						/* Display status of current*/
drivestatus()					/*   drive		    */
{
	LONG	space;

	print("        ");
	show_drive();
	printx("Drive Characteristics");	/* 128 byte records	    */
	space = (LONG) (dpba->mxa + 1) * kpb;
	p_unl(space * 8);
	printx(record_msg);
	printx(" Capacity");
	p_unl(space);				/* = Kbytes		    */
	printx("Kilobyte Drive  Capacity");
	p_unl((LONG) dpba->dmx + 1);		/* Directory slots	    */
	printx("32 Byte");
	printx(entries);
	p_unl((LONG) dpba->cks * 4);		/* Slots checked to find if */
	printx("Checked");			/*   disk has been changed  */
	printx(entries);
	p_unl(((LONG) dpba->exm + 1) * 128);	/* Records allocated / slot */
	printx(record_msg);
	printx("s / Directory Entry");
	p_unl((LONG) 1 << dpba->bls);		/* Records / block	    */
	printx(record_msg);
	printx("s / Block");
	p_unl((LONG) dpba->spt);		/* Records / track	    */
	printx(record_msg);
	printx("s / Track");
	p_unl((LONG) dpba->ofs);		/* Reserved tracks	    */
	printx("Reserved  Tracks");
	crlf();
}


		/********************************/
		/*		 		*/
		/*     U S E R S T A T U S      */
		/*		 		*/
		/********************************/

VOID						/* Display active user no   */
userstatus()					/*   and user no's which    */
{						/*   own files on current   */
	register UWORD	i;			/*   drive		    */
	BOOLEAN		user[16];

	crlf();					/* Show current user & drive*/
	show_drive();
	printx("Active ");
	show_usr(user_code);
	crlf();
	show_drive();
	printx("Active Files:");		/* Find out who owns files  */
	for (i = 0 ; i < sizeof user / sizeof (BOOLEAN);
		user[i++] = FALSE);		/* (Assume nobody at start) */
	_setdma(buff);
	dcnt = _srch_1st("?");			/* Find first FCB on disk   */

	while ( dcnt != 255 )			/* While more FCB's	    */
	{					/* This expression gets user*/
						/*   no of current file from*/
						/*   directory buffer	    */
		if ((i = ((struct fcbtab *) &buff[(dcnt & 3) * 32])->drive &
		     0xff) != DELETED)		/* Not deleted entry	    */
			user[i & 0x0f] = TRUE;	/* User has at least 1 file */
		dcnt = _srch_next();		/* Find next FCB	    */
	}
						/* Print users with files   */
	for (i = 0; i < sizeof user / sizeof (BOOLEAN); i++)
		if (user[i])
			pdecimal(i, 100, TRUE);
	crlf();
}


		/********************************/
		/*		 		*/
		/*     D I S K S T A T U S      */
		/*		 		*/
		/********************************/

VOID						/* Display status of logged */
diskstatus()					/*   in disk drives	    */
{
	int		d;
	register UWORD	login;			/* *UNSIGNED* login vector  */
						/*    (ensure logical shift)*/

	login = _ret_login();			/* Which disks logged in?   */
	d = 0;

	do					/* While more logged drives */
	{
		if (login & 1)			/* Bit zero shows this	    */
		{				/*   drive is logged	    */
			select_disk(d);
			drivestatus();		/* Tell user about it	    */
		}
		d++ ;				/* Try next drive	    */
	} while (login >>= 1);
}


		/********************************/
		/*		 		*/
		/*         P R N A M E          */
		/*		 		*/
		/********************************/

VOID
prname(a)					/* Print the device name at */
register char	*a;				/*   a.  ':' terminates name*/
{
	do
		_conout(*a);
	while (*a++ != ':');
}


		/********************************/
		/*		 		*/
		/*      D E V S T A T U S       */
		/*		 		*/
		/********************************/

VOID						/* Print logical - physical */
devstatus()					/*   device mapping (from   */
{						/*   iobyte)		    */
	register UWORD	iobyte;			/* Iobyte needs unsigned var*/
	register int	j, k;

	iobyte = _get_iob();
	j = 0;					/* j indexes phys dev group */

	for (k = 0; k < 4; k++)			/* Iobyte maps four logical */
	{					/*   devices (2 bits each)  */
		prname(&devl[k * 4]);		/* Display logical dev name */
		printx(" is ");			/* Each maps to one of four */
						/*   physical devices	    */
		prname(&devr[((iobyte & 3) * 4) + j]);
		j += 16;			/* Index next phys group    */
		iobyte >>= 2;			/*   & next logical device  */
		crlf();
	}
}


		/********************************/
		/*		 		*/
		/*         V A L U E S          */
		/*		 		*/
		/********************************/

VOID						/* Tell the user what STAT  */
values()					/*   can do		    */
{
	register int j, k;

	printx("STAT 2.2");
	crlf();
	print("File Status   : ");
	printx(filename);
	printx(" [SIZE]");
	print("Read Only Disk: d:=RO");
	print("Set Attribute : "); 
	printx(filename);
	printx(sattrib);
	print("Disk Status   : DSK: d:DSK:");
	print("User Status   : USR: d:USR:");
	print("Iobyte Value  : DEV:");
	print("Iobyte Assign :");

	for (j = 0; j < 4; j++)			/* Print four lines, each   */
	{					/*   showing one logical    */
		crlf();				/*   device and four phys-  */
		prname(&devl[j * 4]);		/*   ical devices	    */
		printx(" =");
		for (k = 0; k <= 12; k += 4)
		{
			_conout(' ');
			prname(&devr[(j * 16) + k]);
		}
	}
	crlf();
}


		/********************************/
		/*		 		*/
		/*       P R C O U N T          */
		/*		 		*/
		/********************************/

VOID						/* Print the amount of space*/
prcount()					/*   remaining on the	    */
{						/*   current disk	    */

	LONG	free;				/* The no of free sectors   */

	free = 0;
	if (HAS_GET_DFS(ver))			/* BDOS can return free     */
	{					/*   space into curent DMA  */
		_setdma(&free);			/*   buffer		    */
		_get_dfs(cdisk);		/* Covert from record count */
		free >>= SPKSHF;		/*   to kbytes (8 secs/k)   */
	}
	else					/* Not BDOS 3: we must	    */
	{					/*   get the info from DPB  */
		free = count();
	}
	p_long(free);
	_conout('k');
}


		/********************************/
		/*		 		*/
		/*        P R A L L O C         */
		/*		 		*/
		/********************************/

VOID						/* Print allocation for the */
pralloc()					/*   current disk	    */
{
	crlf();
	show_drive();				/* Is current disk read-only*/
	printx(((rodisk >> cdisk) & 1) ? "RO" : "RW");
	printx(", Free Space: ");
	prcount();
}


		/********************************/
		/*		 		*/
		/*       P R S T A T U S        */
		/*		 		*/
		/********************************/

VOID						/* Print the status of the  */
prstatus()					/*   disk system	    */
{
	register UWORD	login;
	register int	d;

	login = _ret_login();			/* Login vector set	    */
	d = 0;					/* Start on drive A	    */

	while (login)				/* While more drives	    */
	{
		if (login & 1)			/* This disk is logged	    */
		{ 
			select_disk(d);		/* Find out about it	    */
			pralloc();		/* Tell the user about it   */
			login -= 1;		/* clear the bit            */
		}
		login >>= 1; 			/* Next disk to login lsb   */
		d++;
	}
	crlf();
}


		/********************************/
		/*		 		*/
		/*          D O T S             */
		/*		 		*/
		/********************************/

VOID						/* Output a line of i dots  */
dots(i)
register int i;
{
	crlf();
	while (i--)
		_conout('.');
}


		/********************************/
		/*		 		*/
		/*       P R I N T F N          */
		/*		 		*/
		/********************************/

VOID						/* Print current file name  */
printfn(a)					/*   (pointed to by a)	    */
struct fcbtab	*a;
{
	register int	k;

	show_dv();				/* Drive preceeds file name */
	for (k = 0; k < FNAM; k++)		/* Print name in form	    */
	{					/*   "NNNNNNNN.EEE"	    */
		if (k == NSIZE)
			_conout('.');
		_conout(a->fname[k] & 0x7f);
	}
}

/****************************************************************************/
/*									    */
/*	               F I L E   H A N D L I N G                            */
/*	               -------------------------                            */
/*									    */
/****************************************************************************/


		/********************************/
		/*		 		*/
		/*      A T T R I B U T E       */
		/*		 		*/
		/********************************/

						/* Return TRUE if the file  */
						/*   described by fcbsa has */
						/*   attribute a set	    */
						/* NOTE: this is a macro    */

#define attribute(a) (fcbsa->a & 0x80)


		/********************************/
		/*		 		*/
		/*       A L L O C A T E        */
		/*		 		*/
		/********************************/

BOOLEAN						/* Make an allocation vector*/
allocate(block_ptr)				/*   and check for duplicate*/
struct	block_no *block_ptr;			/*   blocks in directory    */
{						/* Return TRUE if allocation*/
						/*   consitent (no duplic-  */
						/*   ates), FALSE otherwise */
						/* Builds an allocation map,*/
						/*   one bit per disk block */
						/*   in memory starting at  */
						/*   alloc, setting bits as */
						/*   it finds blocks alloc- */
						/*   ated		    */
	UWORD	block, vbyte, amask;

	if (word_blks)				/* Block addresses are 16   */
	{					/*   bits long		    */
#ifdef HILO					/* Processor is Z8000, 68K..*/
		block = ((UWORD) block_ptr->second << 8) +
			 ( (UWORD) block_ptr->first & 0xff) ;
#else						/* It's 8080, 8086, PDP11...*/
		block = *(UWORD *) block_ptr;
#endif
	}
	else					/* 8-bit block addresses    */
		block = (block_ptr->first) & 0xff;  /* to prevent sign-ext  */
	vbyte = block / 8;			/* Index into alloc vector  */
	amask = 1 << (block % 8);		/* Bit in vector byte	    */

	if (amask & alloc[vbyte])		/* Block already allocated? */
	{					/* Yes			    */
		if (error_free)			/* Disk previously innocent?*/
		{				/* Yes: show its guilt	    */
			error_free = FALSE;
			print("Bad Directory on ");
			show_dv();
			print("Space Allocation Conflict:");
		}
		crlf();				/* Tell user which file is  */
		show_usr(bfcba->drive);		/*   corrupt (drive field   */
		blanks(8);			/*   in FCB holds user #)   */
		printfn(bfcba);
		return (FALSE);
	}
	alloc[vbyte] |= amask; 			/* Block not previously used*/
	return (TRUE);				/*   mark it allocated	    */
}


		/********************************/
		/*		 		*/
		/*      N A M E _ D I F F       */
		/*		 		*/
		/********************************/
int
name_diff(a ,b)					/* Check for matching file  */
register struct fcbtab *a;			/*   names.  Returns -1 if  */
register struct fcbhalf *b;			/*   name of file a lexico- */
{						/*   graphically before that*/
	register int	i, c_a, c_b;		/*   of b, 0 (FALSE) if     */
						/*   same, 1 if after	    */
						/* Wildcards ('?') in name  */
	for (i = 0 ; i < FNAM; i++)		/*   a match any character  */
	{					/*   in name b		    */
		if ((c_a = a->fname[i] & 0x7f) == '?')
			continue;
		if (c_a < (c_b = b->fname[i] & 0x7f)) 
			return (-1);
		if (c_a > c_b)
			return (1);
	}
	return (0);				/* Names NOT different (ie  */
}						/*   they ARE the same)	    */


		/********************************/
		/*		 		*/
		/*   I N D _ N A M E _ D I F F  */
		/*		 		*/
		/********************************/

int 						/* Return ordering info for */
ind_name_diff(p_a, p_b)				/*   for two FCB's (see	    */
struct fcbhalf	**p_a, **p_b;			/*   name_diff above) given */
{						/*   the addresses of two   */
						/*   pointers to FCB's	    */
	return (name_diff((struct fcbtab *) *p_a, *p_b));
}						/* Note: double indirection */
						/* Used by qsort function   */


		/********************************/
		/*		 		*/
		/*     C O U N T _ B L K S      */
		/*		 		*/
		/********************************/

VOID						/* Either check extent at   */
count_blks(allo)				/*   bfcba for consistent   */
BOOLEAN	allo;					/*   allocation (allo TRUE) */
{						/* Or add length of extent  */
	register int	i, mb;			/*   in Kbytes to *fcbma    */

	i = sizeof bfcba->resvd;		/* Start at end of block    */
	while ((i -= (word_blks) ? 2 : 1) >= 0)	/*   pointers and work	    */
	{					/*   towards beginning	    */
		mb = bfcba->resvd[i];		/* Is the current single    */
		if (word_blks)			/*   or double length	    */
			mb |= bfcba->resvd[i + 1];/*  pointer zero?	    */
		if (mb != 0)
		{				/* No: block is allocted    */
			if (allo) 		/* See if consistent	    */
			{
				if (! allocate((struct block_no *)
				     (&bfcba->resvd[i])))
					return;	/*   (give up if not)	    */
			}
			else			/*   or tot up size in	    */
				fcbsa->kcnt += kpb;/*  Kbytes		    */
		}
	}
}


		/********************************/
		/*		 		*/
		/*     C H E C K _ U S E R      */
		/*		 		*/
		/********************************/

VOID						/* Find next file matching  */
check_user()					/*   current name and user  */
{						/* Check all intervening    */
						/*   directory entries for  */
						/*   consistent allocation  */
	while (dcnt != 255)			/* Until directory end	    */
	{					/* Get pointer to this FCB  */
						/*   in default DMA buffer  */
		bfcba = (struct fcbtab *) &buff[dcnt * 32];
						/* Not deleted file or XFCB?*/
						/*   (beware sign extension)*/
		if ((UWORD) (bfcba->drive & 0xff) < 0x20)
		{
			count_blks(TRUE);	/* Check allocation legal   */
			if ((! name_diff(fcb, bfcba)) /* Name & user match? */
			   && ((bfcba->drive & 0x0f) == user_code))
				return;		/* Yes: return		    */
		}
		dcnt = _srch_next();		/* Try next directory entry */
	}
}


		/********************************/
		/*		 		*/
		/*     S E T F S T A T U S      */
		/*		 		*/
		/********************************/

BOOLEAN						/* Parse file attribute	    */
setfstatus()					/*   assignment.  Return    */
{						/*   TRUE if valid assign   */
						/*   found.		    */
	if (! parse_next()) 			/* No more tokens?	    */
		return (FALSE);
	if (token[0] == '=')  			/* Skip optional '='	    */
		scan();
						/* STAT filename SIZE ?	    */
	if ((scase1 = match(attribl, A_SIZE)) == OPT_SIZE) 
	{					/* Yes: not an attribute    */
		sizeset = TRUE;			/*   assignment		    */
		return (FALSE);
	}
	if (scase1 != 0)  			/* If valid attribute does  */
	{					/*   another follow?	    */
		if (parse_next())  		/* If so, is it reasonable? */
		{				/* RO RO, SYS DIR etc are   */
						/*   rejected		    */
			if (((scase2 = match(attribl, A_SIZE)) != 0) 
			   && (abs(scase2 - scase1) > 1)) 
				return (TRUE);	/* Two good attributes	    */
		}
		else
			return (TRUE);		/* One good attribute	    */
	}
	print(invalid);				/* Something wrong.  Print  */
	print(use);				/*   the bad news	    */
	printx(filename);
	printx(" [SIZE] ");
	printx(sattrib);
	_exit(1);				/* User screwed up: abort   */
}


		/********************************/
		/*		 		*/
		/*     S E T _ S A T T R I B    */
		/*		 		*/
		/********************************/

VOID						/* Set/reset selected att-  */
set_sattrib(scase)				/*   ributes of current file*/
int	scase;
{
	switch(scase)
	{
	  case OPT_RO:				/* Read-only		    */
		fcbsa->ROFILE |= 0x80;
		printx(readonly);
		break;
		
	  case OPT_RW:				/* Read-write		    */
		fcbsa->ROFILE &= 0x7f;
		printx(readwrite);
		break;

	  case OPT_SYS:				/* "System" (does not appear*/
		fcbsa->SYSFILE |= 0x80;		/*   in directory listing   */
		printx("System (Sys)");
		break;

	  case OPT_DIR:				/* "Directory" (appears in  */
		fcbsa->SYSFILE &= 0x7f;		/*   directory listing	    */
		printx("Directory (Dir)");
		break;
	   
	  default:
		print(invalid);
	}
}


		/********************************/
		/*		 		*/
		/*     C O M P A R E _ F C B	*/
		/*		 		*/
		/********************************/

BOOLEAN						/* Check whether current FCB*/
compare_fcb()					/*   refers to a file we've */
{						/*   already encountered    */
	register UWORD	i;			/* Return index of matching */
						/*   entry or free slot	    */
	fcbsa = fcbs;
	for (i = 0; i < fcbn; fcbsa++, i++)	/* Points fcbsa at the	    */
	{					/*   matching name, or at   */
		if (! name_diff(fcbsa, bfcba))  /*   next empty slot	    */
			break;
	}

	return (i);
}


		/********************************/
		/*		 		*/
		/*      C O P Y _ F C B         */
		/*		 		*/
		/********************************/

VOID						/* Add the current directory*/
copy_fcb()					/*   entry to the list of   */
{						/*   known files (used when */
						/*   entry has name not	    */
						/*   previously encountered)*/

	fcbn++;					/* Increment count	    */
	if (fcbn > fcbmax)			/* Too many files?	    */
	{ 
		print("Too Many Files");
		_exit(1);			/* Fatal error		    */
	}
	finx[fcbn - 1] = fcbsa;			/* Save index for later sort*/
	*fcbsa = *((struct fcbhalf *) bfcba);	/* Copy FCB.  Clear extent, */
	fcbsa->extent = fcbsa->kcnt = 		/*   byte and record count  */
		fcbsa->rcnt = 0;
}


		/********************************/
		/*		 		*/
		/*   A D D _ F C B _ B L K S    */
		/*		 		*/
		/********************************/

VOID						/* Update the statistics of */
add_fcb_blks()					/*   current file with the  */
{						/*   information from the   */
	register int	kb;			/*   current FCB	    */

	if (bfcba->drive <  0x10)		/* Drive field holds user no*/
	{     
		nfcbs++;			/* Increment fcb count	    */
		for (kb = 0; kb < FNAM; kb++)	/* Turn on any attributes   */ 
		{				/*   that are set in case   */
						/*   missing in previous    */
						/*   extents		    */
			if (bfcba->fname[kb] & 0x80)
				fcbsa->fname[kb] |= 0x80;
		}
		if (bfcba->ARCHIV & 0x80)	/* Turn of archiving if any */
		fcbsa->ARCHIV &= 0x7f;		/*   extent not archived    */
		fcbsa->extent++;		/* Prepare for next extent  */
						/* Tot up logical file size */
						/*   (beware sign extension)*/
		fcbsa->rcnt += (bfcba->reccnt & 0xff) +	
			    (bfcba->extent & dpba->exm) * 128;
		count_blks(FALSE);		/*    & physcal blocks used */
	} 
	else if (bfcba->drive < 0x20)		/* This is an extended FCB  */
	{
		fcbsa->s1 |= 0x80;		/* Set XFCB exists flag	    */
	}
						/* If bfcba->drive >= 0x20, */
						/*    directory entry free  */
}


		/********************************/
		/*		 		*/
		/*        D I S P L A Y         */
		/*		 		*/
		/********************************/

VOID				  		/* Display file details	    */
display()					/*   (STAT afn [SIZE])	    */
{
	register int	add, sizecols;		/* Layout variables	    */
	UWORD		kblks;			/* Total number of 1k blks  */
	UWORD		tall;			/* Total allocation	    */
	BOOLEAN		wide, xfcbfound;
	register int	l;

	add = sizecols = 0; 			/* Calculate layout for     */
	if ((wide = (columns() > 48))) 		/*   displayed data	    */
		add = 7;
	if (sizeset)  				/* Printing physical size?  */
		add += (sizecols = 10);
	print(drivename);			/* Show the drive name	    */
	show_drive();
	blanks(17 + add);
	show_usr(user_code);			/*   and user code	    */
	if (sizeset) 				/* Print appropriate header */
		print("     Size "); 		/*   according to data	    */
	else					/*   requested and screen   */
		crlf();				/*   width		    */
	printx(" Recs  Bytes FCBs Attrib");
	if (wide)  
		printx("utes   ");
	printx("   Name");
	tall = kblks = 0;			/* kfg - added in response  */
						/*	to lint complaints. */
	for (l = 0; l < fcbn; l++)		/* For each matched file    */
	{
						/*Move FCB to full-size FCB*/
		fcbsa = finx[l];
		*((struct fcbhalf *) fcb) = *fcbsa;
		crlf();

		fcb->drive = 0;
		if (sizeset)			/* Need to print size?	    */
		{ 
			_filsiz(fcb);	 	/* Have BDOS calculate	    */
						/*   virtual file size	    */
#ifdef HILO
			p_long((LONG) fcb->record & 0xffffff);
#else						/* On LOHI machine, shift   */
			p_long((LONG) fcb->record >> 8);/* result to low  */
#endif						/*   end of long word	    */
			_conout(' ');
		}
						/* Following expression gets*/
						/*   index in size table by */
						/*   finding index of curr- */
						/*   ent FCB in name table  */
		pdecimal(fcbsa->rcnt, 10000, TRUE);
						/* Display physical record  */
		_conout(' ');			/*   count		    */
		
		kblks += (fcbsa->rcnt + 7) / 8;	/* Tot up total Kbytes,	    */
						/*   rounding fractions up  */
						/* Print size in Kbytes	    */
		pdecimal((fcbsa->rcnt + 7) / 8, 10000, TRUE);	
		tall += fcbsa->kcnt;
		printx("k ");

		xfcbfound = attribute(HAS_XFCB);/* Does this file have XFCB?*/
		fcbsa->s1 &= 0x7f;		/* Now we know, clear flag  */
						/* === Why? ===		    */
						/* Print no of FCB's	    */
		pdecimal((UWORD) fcbsa->extent, 1000, TRUE);
						/* Display attributes	    */
		printx((attribute(SYSFILE)) ? " Sys " : " Dir ");
		printx((attribute(ROFILE)) ? "RO " : "RW ");

		if (wide)			/* Enough room for more?    */
		{				/* Yes: show more attributes*/
			_conout((xfcbfound) ? 'X' : ' ');
			_conout((attribute(ARCHIV)) ? 'A' : ' ');
			_conout((attribute(ATTRB1)) ? '1' : ' ');
			_conout((attribute(ATTRB2)) ? '2' : ' ');
			_conout((attribute(ATTRB3)) ? '3' : ' ');
			printx((attribute(ATTRB4)) ? "4 " : "  ");
		}
		printfn(fcbsa);			/* At last, the filename!   */
	}
						/* All file info printed:   */
						/*   now do totals	    */
	dots(39 + add);				/* Line up columns neatly   */
	print("Total:");
	blanks(sizecols);
	pdecimal(tall, 10000, TRUE);		/* Total kbytes		    */
	_conout('k');
	pdecimal(nfcbs, 10000, TRUE);		/* Total number of FCB's    */
	printx(" (");				/*   of files		    */
	pdecimal(fcbn, 1000, TRUE);
	printx((fcbn == 1) ? " file" : " files");
	if (wide)				/* Room for more?	    */
	{
		printx((fcbn == 1) ? ", " : ",");
		pdecimal(kblks, 10000, TRUE);	/* Print kbytes used	    */
		printx("-1k blocks");
	}
	_conout(')');
}


		/********************************/
		/*		 		*/
		/*        S E T F A T T         */
		/*		 		*/
		/********************************/

VOID						/* Set the attribtes of all */
setfatt()					/*   matched files to those */
{						/*   requested		    */
	register int	l;

	for (l = 0; l < fcbn; l++)		/* For each matched file    */
	{
		crlf();
		printfn(fcbsa = finx[l]);	/* Print its name	    */
		printx(set_to);			/*   and what its attributes*/
		set_sattrib(scase1);		/*   will be		    */
		if (scase2 != 0)
		{
			printx(", ");
			set_sattrib(scase2);
		}
		fcbsa->drive = 0;		/* Clear user no. (would be */
						/*   interpreted as drive)  */
		_set_att(fcbsa);		/* Go fix attributes	    */
	}
}


		/********************************/
		/*		 		*/
		/*       G E T F I L E          */
		/*		 		*/
		/********************************/

VOID						/* Process request involving*/
getfile()					/*   (possibly ambiguous)   */
{						/*   filename.  The CCP has */
						/*   parsed name and put it */
						/*   in basepage FCB for us */

	if ((set_attribute = setfstatus())  	/* Find out what to do from */
	   && (scase1 == 0))			/*   command line.  Give up */
		return;				/*   on error		    */

	alloc = all_map();			/* Allocate disk map space  */
	fcbmax = all_fcb();			/*   and fcb space	    */

	fcbn = fcb->drive = 0;			/* Search for first file on */
	fcb->extent = fcb->s2 = '?';		/*   current drive 	    */
	dcnt = _srch_1st("?");			/* Read first directory	    */
						/*   sector from current drv*/
	check_user();				/* Do we have a match?	    */

	while (dcnt != 255)			/* While more directory FCBs*/
	{
						/* Get address of this FCB  */
						/*   in directory buffer    */
		bfcba = (struct fcbtab *) &buff[(dcnt & 0x3) * 32];

		if (compare_fcb() >= fcbn)	/* Is this an extent of a   */
			 			/*   file we already met?   */
			copy_fcb();		/* Yes: add name to list    */

		add_fcb_blks();			/* Adjust file's block count*/
		dcnt = _srch_next();		/* Find next directory entry*/
		check_user();
		test_kbd_esc();			/* Give user chance to abort*/
	}

	if (! error_free)			/* Allocation inconsitent?  */
		_exit(1);			/* Yes: user must clean up! */

	if (fcbn == 0)  			/* Did we find any files?   */
	{
		print("File Not Found");	/* No: tell user	    */
		return;
	}

	if (set_attribute)			/* Are we setting file 	    */
	{					/*   attributes?	    */
		setfatt();			/* Yes			    */
		return;
	}

						/* User must want display of*/
						/*   collected data.  First */
						/*   sort names then display*/

	qsort(finx, fcbn, sizeof (struct fcbhalf *), ind_name_diff);
	display();
	pralloc();
}


		/********************************/
		/*		 		*/
		/*        P R D R I V E         */
		/*		 		*/
		/********************************/

VOID						/* Show current drive and   */
prdrive(a)					/*   status to be assigned  */
char	*a;
{
	print(&drivename[1]);			/* print("Drive "),	    */
	show_dv();				/*   name,		    */
	printx(set_to);
	printx(a);				/*   and status		    */
}


		/********************************/
		/*		 		*/
		/*  S E T D R I V E S T A T U S */
		/*		 		*/
		/********************************/

VOID						/* Set current drive to RO  */
setdrivestatus()				/*   or RW status	    */
{
	switch (match(attribl, A_SIZE))
	{
	  case OPT_RO:
		_wr_protd();
		prdrive(readonly);
		break;

	  case OPT_RW:
		if ((_rs_drive(1 << cdisk)) != 0)
			print("Disk Reset Denied");
		else
			prdrive(readwrite);
		break;

	  default:
		print(invalid);
		print(use);
		printx("d:=RO");
	}
}

/****************************************************************************/
/*									    */
/*	          C O M M A N D   P R O C E S S I N G                       */
/*	          -----------------------------------                       */
/*									    */
/****************************************************************************/



		/********************************/
		/*		 		*/
		/*       P A R S E _ I T        */
		/*		 		*/
		/********************************/

VOID						/* Process command line in  */
parse_it()					/*   one of three ways:	    */
{
	switch (match(devl, L_SIZE)) 
	{
	  case OPT_USR:				/* Request user status	    */
		userstatus();
		break;
	
	  case OPT_DSK:				/* Set drive status	    */
		drivestatus();
		break;

	  default:				/* Set/display file details */
		getfile();
	}
}


		/********************************/
		/*		 		*/
		/*         D E V R E Q          */
		/*		 		*/
		/********************************/

BOOLEAN						/* Process a device request */
devreq()					/* Return true if valid	    */
{
	register UWORD	iomask;
	BOOLEAN 	first;
	register int	j, k;

	first = TRUE;
	FOREVER					/* Process each arg in turn */
	{					/* Is this a device name?   */
		if ((j = match(devl, L_SIZE)) == 0)
		{				/* No: error unless first   */
			if (! first) 		/*   token		    */
				goto error;
			return (FALSE);		/* Not a device request	    */
		}
		first = FALSE;			/* Found first/next item    */

		switch (j)			/* What did we find?	    */
		{
		  case 5:			/* Device status request    */
			devstatus();
			break;

		  case 6:			/* List possible assignment */
			values();
			break;
		
		  case 7:			/* List user status values  */
			userstatus();		/* Exit when done (search   */
			_exit(0);		/*   zaps rest of cmd line) */

		  case 8:			/* Show the disks' status   */
			diskstatus();
			break;

						/****************************/
						/*	B E W A R E !	    */
						/* Many hard coded constants*/
						/*   below.  Unless iobyte  */
						/*   changes size, they're  */
						/*   OK			    */
						/****************************/
		  default:			/* Logical-physical device  */
						/*   assignment		    */
		  				/* Scan table item[j - 1]   */
			k = --j * 16;		/* Index to valid devices   */
			if (! parse_assign()) 	/*   for assignment	    */
				goto error;	/* Not assignment: error    */
			if ((k = match(&devr[k], 4) - 1) < 0) 
				goto error;	/* Not valid phys device    */

			iomask = ~3;		/* Mask has two bits clear  */
			while (j--)		/* Find correct iobyte field*/
			{			/* (shift left, 1's fill)   */
				iomask = (iomask << 2) | 3;
				k <<= 2;	/* k holds required mapping */
			}
						/* Replace designated iobyte*/
						/*   field with value in k  */
			_set_iob((_get_iob() & iomask) | k);

		}				/* end of switch	    */

		if (! parse_next()) 		/* If no more tokens, return*/
			return (TRUE);
	}					/* End of forever	    */

error:						/* Invalid command line	    */
	print(invalid);
	return (TRUE);
}


/****************************************************************************/
/*									    */
/*	           S T A T :    M A I N   F U N C T I O N                   */
/*	           --------------------------------------                   */
/*									    */
/****************************************************************************/


VOID
_main()
{

	printx(signonmsg);			/* Sign on with release #   */
	crlf();

	ver = _version();			/* Get CP/M version number  */
	fcb = &_base->fcb1;			/* Set up pointers to first */
						/*   basepage FCB	    */
	buff = _base->buff;			/*   and to DMA buffer	    */
	cdisk = _ret_cdisk();			/* Find out current drive   */
	user_code = _gset_ucode(0xff);		/*   and the user	    */

	if (! parse_next())  			/* If command line empty,   */
	{
		prstatus();			/*   print status	    */
		_exit(0);			
	}

	if (token[1] == ':')			/* Not empty: drive name?   */
	{
		select_disk(token[0] - 'A');	/* Yes: select it	    */

		if (! parse_next())  		/* Was that all?	    */
		{
			pralloc();		/* Yes: display allocation  */
			_exit(0);
		}

		if (token[0] == '=')		/* No.  Perhaps attribute   */
		{				/*   assignment?	    */
			scan();			/* Yes: get parameter	    */
			setdrivestatus();	/*   and assign		    */
			_exit(0);
		}

		parse_it();			/* Neither of above.  Choose*/
		_exit(0);			/*   from: drive status	    */
						/*	   user status	    */
	}					/*	   file operation   */

	set_kpb();				/* Get current disk details */
	if (! devreq()) 			/* Try to perform device    */
						/*   request.  If that fails*/
		getfile();			/*   must be file request on*/
						/*   current drive	    */
}
