/*==============================================*/
/*		ASSIGN utility			*/
/*		main program			*/
/*==============================================*/
 
/*	This new version of assign will NOT support CPM 1.4 or MPM,
	as did the Z80 assembler assign					*/

#define ver 8
#define rev 2
#define mod 'a'
#define asm 'g'

#include "assign.os"
#include "c:switch.os"

/*	Change History:

	0.0a    	Utility written in C based on CPM-86 asm code.
	7.14a 12/19/83	Cosmetic changes added for cpm86 release.	
	7.14b 12/26/83	Changed help message and disallowed 86 port0, portf
	7.15a 01/03/84	Implement diskette, local hd assign, and 'w' option
	7.15b 02/02/84	Fixed assign master, save/restore, ownership checking
	7.15c 02/09/84	Fixed release ownership on U: & net error recovery
	7.15d 02/16/84	Fixed ^C, net recovery, floppy boot assignments
	8.0a  02/24/84	Optimized rec/send for z816
	8.1   02/29/84	z816 and ultra0 fix
	8.2   03/10/84	5" pw bug, release writes before reassign
	8.2a  03/12/84	IBM PC card

*/

#include "mpstruct.def"
#define nodpb
#include "cpmdata.def"

#define unassigned 0xff
#define standalone 0xff
#define MAXdrives 15
#define yes 1
#define no 0

struct	GetInfoStruct	getbuffer[ MAXdrives];

int fulldisp = yes;
int anydisp = yes;
int dowboot = no;
int numdrives;
int curdrive;

#ifdef msdos
CHAR firstdrive;
#else
#define firstdrive 'A'
#endif

CHAR	*prtopt[] =
{
/* i =	   0	    1        2        3        4        5        6        7  */
	"PORT0", "PORT3", "PORT2", "PORT1", "PORTP", "PORTF", "CUSTOM", "SPOOL"
};

/*==================================================================*/
/*page*/
main( argc, argv)
int 	argc;
CHAR	*argv[];
{
	displays( "Assign Utility -- version $" );
	printf( "%d.%d%c%c", ver, rev, mod, asm );
	displays( " for Separated Boot\r\n$" );
	listUpper( argc, argv); /* convert cmd line args to upper case */
	readassign();		/* get current assignments */
	if ( argc > 1)
	{
		doassign( argc, argv); /* make any changes specified */
		readassign();	/* get new assignments */
	}
#ifdef cpm86
	if( dowboot) DiskReset();
#endif
	if( anydisp)
		showassign(numdrives); /* display new assignments */
#ifdef cpm80
	return 0;	/* don't abort submit file */
#endif
}
/*======================================================================*/
/*		Determine current assignments				*/
/*		and count number of valid drives			*/
/*======================================================================*/
readassign()
{
#ifdef msdos
	firstdrive = 'A';
	while( GetAssign( firstdrive - 'A', &getbuffer[ 0] ) )
		++firstdrive;
	numdrives = 1;
#else
	numdrives = 0;
#endif
	while( !GetAssign( numdrives + firstdrive - 'A', 
	    &getbuffer[ numdrives] ) )
		++numdrives;
}
/*==================================================================*/
/*page*/
doassign( argc, argv)
int	argc;
CHAR	*argv[];
{
	switch( *argv[1])
	{
		case 'Q':	/* quick display */
			fulldisp = no; /* suppress all but the essential */
			break;

		case 'R':	/* restore assignments from file */
			if( argc == 3)
			{	restoreassign( argv[2]);
				break;
			}
			else
			{	reject();	
				return;
			}

		case 'S':	/* save assignments in file */
			if( argc == 3)
			{	saveassign( argv[2]);
				break;
			}
			else
			{	reject();	
				return;
			}

		case 'W':	/* do a write grant */
			if( argc > 2)
			{	firstwrite( argc, argv);
				break;
			}
			else
			{	reject();	
				return;
			}

		case 'P':	/* assign printer port */
			if( argc == 3)
			{	prtassign( argv[2]);
				break;
			}
			else
			{	reject();	
				return;
			}

		case '?':	/* help requested */
			helpmsg();
			anydisp = no; /* suppress current assignments */
			break;

#ifdef cpm80
		case 'T':	/* Temporary for Transfer */
			MSAssign(); 
			drvassign( *argv[2] - 'A', argv[3] );
			break;
#endif

		default:	/* assign drive letter */
			if( argc > 2)
				drvassign( *argv[1] - firstdrive, argv[2]);
			else
				/* allow no space after drive letter for Doug*/
				drvassign( *argv[1] - firstdrive, &argv[1][1]);

			/* allow immediate write ownership if W specified */
			if( argc > 3 && *argv[3] == 'W' )
			{	readassign(); /* update assignments */
				firstwrite( 3, --argv );
			}

			break;
	}
}
/*==================================================================*/
/*page*/
/*======================================================================*/
/*		show current assignments				*/
/*									*/
/*	note: noname and namdisp display 20 characters each, so that    */
/*		quick display will show 4 to a line			*/
/*======================================================================*/
showassign(num)
int	num;
{
	displays(     "\r\n                Current Drive Assignments\r\n$");
	if( fulldisp)
		displays( "                -------------------------\r\n$");
	displays("\n$");
	curdrive = 0;
	while( num--)
	{
		drvdisp( curdrive + firstdrive);
		if( getbuffer[ curdrive].GetUnit == unassigned)
			noname();
		else
		{
			namdisp();
			sizdisp();
			if( fulldisp)
			{	typdisp();
				owndisp();
			}
		}
		if( fulldisp)
			displays("\r\n$");
		curdrive++;
	}
	showprint();
}
/*==================================================================*/
/*page*/
/*======================================================================*/
/*		show current printer assignments			*/
/*									*/
/*	if IObyte values or size of the printer field should change	*/
/*	this routine should be changed					*/
/*======================================================================*/
showprint()
{
int	iobyte;

static CHAR	*printer[] =
{"PORT0 (serial port 0)\r\n$",
 "PORT3 (serial port 3)\r\n$",
 "PORT2 (serial port 2)\r\n$",
 "PORT1 (serial port 1)\r\n$",
 "PORTP (parallel port 2)\r\n$",
 "PORTF (parallel port 1 for FOX with NO HARD DISK)\r\n$",
 "CUSTOM (special printer driver)\r\n$",
 "SPOOL (HiNet print spooler)\r\n$" 
};

	iobyte = GetIObyte();
	displays( "\r\nPrinter assigned to $" );
	displays( printer[ getbits( iobyte, 7, 3)] );
}
/*==================================================================*/
/*page*/
/*======================================================================*/
/*		store current assignment and IObyte into file.		*/
/*									*/
/*	file format:							*/
/*		1 byte version number (currently 1)			*/
/*		1 byte drive number ( 0 = A )				*/
/*		2 byte device type ( U: N: H: D: )			*/
/*		8 byte partition name					*/
/*		terminated by:						*/
/*		1 byte 'P'						*/
/*		1 byte IObyte						*/
/*======================================================================*/
saveassign( filename)
CHAR	*filename;
{
CHAR	assignlist[ 1+8*(1+2+8)+1+1], *assignptr;
int	len, fp;
#define savever 1

	curdrive = 0;
	assignptr = assignlist;
	*assignptr++ = savever;
	len = 1;
	while( curdrive < numdrives)
	{	*assignptr++ = curdrive;
		if( getbuffer[ curdrive].GetUnit == unassigned)
			blockmv( assignptr, "U:        ", 10);
		else
		{	switch( getbuffer[ curdrive].GetType )
			{	case NETmedia:
					blockmv( assignptr,"N:",2);
					break;
				case HD15media:
				case hardmedia:
					blockmv( assignptr,"H:",2);
					break;
				default:
					blockmv( assignptr,"D:",2);
					break;
			}
			blockmv( assignptr + 2, getbuffer[ curdrive].GetName, 
				LENpart);
		}
		assignptr += LENpart + 2;
		len += (LENpart+3);
		curdrive++;
	}
	*assignptr++ = 'P';
	*assignptr = GetIObyte();
	len += 2;
	if( (fp = zopen( filename, "wb") ) == EOF )
		{anydisp = 0; printe( "File open error"); return; }
	zwrite( fp, assignlist, len);
	zclose( fp);
	displays( "Assignments saved into file as follows:\r\n$");
	fulldisp = no;
}
/*==================================================================*/
/*page*/
/*======================================================================*/
/*		retrieves old assignments from file and restores them	*/
/*									*/
/*	Reads entire file before reassignments are done.  If passwords	*/
/*	are needed it will stop to ask.  Redundant assignments are	*/
/*	skipped, saving to re-request a possible password.		*/
/*======================================================================*/
restoreassign( filename)
CHAR	*filename;
{
CHAR	part[ 2+LENpart];
CHAR	iobyte;
CHAR	oldbyte;
CHAR	answer[ 5];
CHAR	drive;
int	len;
int	fp;
CHAR	buff[ ((1+2+8)*8)+2 ], *buf;
CHAR	savedver;

	if( (fp = zopen( filename, "rb") ) == EOF )
	{	printe("Filename not found");
		anydisp = no;
		return FALSE;
	}
	zread( fp, &savedver, 1);
	if( savedver != savever)
	{
		printe( "Incompatible saved assignments version");
		return FALSE;
	}
	buf = buff;
	zread( fp, buf, 1);
	while( *buf++ != 'P')
	{
		zread( fp, buf, 2+LENpart+1);
		buf += 2+LENpart;
	}
	zread( fp, &iobyte, 1);
	zclose( fp); 

/* now that file has been preread we can reassign the drive the file was on */

	buf = buff;
	while( (drive = *buf++) != 'P')
	{
		blockmv( part, buf, 2+LENpart);
		buf += 2+LENpart;
		if( drive >= numdrives ) continue;
		if( getbuffer[ drive].GetUnit == unassigned ||
		    blkcmp( part+2, getbuffer[ drive].GetName, LENpart) )
		{
			if( getbuffer[ drive].GetUnit == unassigned &&
			    *part == 'U' ) continue; /* to next while */
			printf( "\nAssign drive %c as partition %10.10s", 
				drive + firstdrive, part );
			if( drvassign( drive, part) )
				displays( "    *** not done ***$");
			dowboot = yes;
		}
	}
	iobyte = ( (oldbyte = GetIObyte() ) & 0x1f) + (iobyte & 0xe0);
	if( getbits( oldbyte, 7, 3) != getbits( iobyte, 7, 3) )
	{	printf( "\nChange printer assignment from $" );
		printf( "%s to %s?  ", prtopt[ getbits( oldbyte, 7, 3) ],
			prtopt[ getbits( iobyte, 7, 3) ] );
		input( answer,3);
	}
	if( *answer == 'Y' )
		SetIObyte( iobyte);
	displays( "\r\n\nAssignments restored from file as follows:\r\n$" );
	return TRUE;
}
/*==================================================================*/
/*page*/
prtassign( printer)
CHAR	*printer;
{
int	i;
int	iobyte;

	i = find( prtopt, printer, 8); /* i = 8 if option not found */
	iobyte = GetIObyte();

/*	if assigning portp check the console part of the IOBYTE
	if the console is parallel (5086), do not assign to parallel port 2 */

	if( i == 4 && (iobyte & 0x03) == 0x02 ) 
		i = 8;

#ifdef cpm80

/*	The following needs to be implemented on the 80 side:

	disallow assigning portp on a 1280
	if the console is parallel interrogate the crt controller with esc v
	if esc v returns 5000 assign to parallel port 2
	if esc v returns Fox ask if they have a hard disk and reject if so

	if assigning port0 and console is serial, ask the user which
		terminal emulation to use (as per ENABLE)
*/
	/* disallow     port1     if not standalone */
	if( i == 8 || ( i == 3 && userno() != standalone) )
#else
	/* disallow   port 1,   port 0,   port f,   custom    on 86 */
	if( i == 8 || i == 3 || i == 0 || i == 5 || i == 6 )
#endif
 	{
		printe("Invalid printer option");
		reject();
		return;
	}
	iobyte = ( iobyte & 0x1f) + (i << 5) ;
	SetIObyte( iobyte );
}
/*==================================================================*/
/*page*/
find( opt, str, num)
CHAR	*opt[];
CHAR	*str;
int	num;
{
int	i;

	for( i=0; i < num; i++)
		if( !strcmp( str, opt[ i] ) )
			break;
	return i;
}
/*=================================================================*/
CHAR	*movetiln(d,s,e,n)
CHAR	*s,*d;
CHAR	e;
int	n;
{
	while( (*s != e) && (n--) )
		*d++ = *s++;
	return s;
}
/*==================================================================*/

reject()
{
	displays( "\r\n***********************\r\n$");
	displays("** Assignment Denied **  (enter ASSIGN ? for help screen)$");
	displays( "\r\n***********************\r\n$");
	anydisp = no;
}
/*==================================================================*/
printe( msg)
CHAR	*msg;
{
	printf( "\n*** Error: %s\n", msg);
}
/*==================================================================*/
/*page*/
drvassign( drive, part)
CHAR	*part;
CHAR	drive;
{
CHAR	partf[ LENpart+2];
int	retcode;
struct	WMstruct wm;

	if( ( drive >= numdrives ) | ( drive < 0 ) )
	{	printe( "Invalid Drive Designation.");
		reject();
		return;
	}
#ifdef cpm86
	if( strcmp( part, "ULTRA0" ) == 0 ) part = "M:";
#endif
	fill( partf, ' ', LENpart+2);
	movetiln( partf, part, 0, LENpart+2);
#ifdef cpm80
	if( getbuffer[ drive].GetUnit != unassigned
	    && getbuffer[ drive].GetType == NETmedia )
	{	wm.wmsend.WMcmd = WMrelease;
		wm.wmsend.WMvol = getbuffer[ drive].GetVol;
		wm.wmsend.WMunit = getbuffer[ drive].GetUnit;
		wm.wmsend.WMuserno = ( wm.wmsend.WMvalue = userno() );
		wm.logdrive = drive + firstdrive - 'A';
		writemode( &wm);
	}
#endif
	if( !(retcode = PWAssign( drive + firstdrive - 'A', partf) ) )
		dowboot = yes;
	else
	{
		switch( retcode) 
		{	case 2:
				break;
			case 3:
				printe("Insufficient RAM for ultradrive");
				break;
			case 4:
				printe("Ultradrive already assigned");
				break;
			case 5:
				printe("Invalid Drive Designation");
				break;
			case 6:
				printe(
			"Assignment to different OS partition type not allowed");
				break;
			default:
				printe("Drive not assigned");
				break;
		}
		reject();
	}
	return retcode;
}
/*==================================================================*/
/*page*/
firstwrite( argc, argv)
int	argc;
CHAR	*argv[];
{
struct	WMstruct wm;
int	i;
#define BEEP 7

	i = 2;
	while( --argc >= 2)
	{
		if( !( (argv[i][1] == ':') | (argv[i][1] == 0) ) )
		{	printe( "Must specify drive letter" );
			return FALSE;
		}
		curdrive = *argv[ i++] - firstdrive;
		wm.wmsend.WMcmd = WMgrant;
		wm.wmsend.WMvol = getbuffer[ curdrive].GetVol;
		wm.wmsend.WMunit = getbuffer[ curdrive].GetUnit;
		wm.wmsend.WMuserno = (wm.wmsend.WMvalue = userno() );
		wm.logdrive = curdrive + firstdrive - 'A';
#ifdef dbg
	printf( "\nvol: %x, unit: %x, value: %x, userno: %x", wm.wmsend.WMvol,
		wm.wmsend.WMunit, wm.wmsend.WMvalue, userno() );
#endif
		if( getbuffer[ curdrive].GetType != NETmedia )
		    wm.wmrec.WMstatus = 0xf0;
		else
		    if( writemode( &wm ) )
		    { 	printe( "Write enable error.");
			return FALSE;
		    }
#ifdef dbg
	printf("... ack: %x, status: %x\n", wm.wmrec.WMack, wm.wmrec.WMstatus);
#endif
		if( wm.wmrec.WMstatus != userno() )
		    if( wm.wmrec.WMstatus < 0xf0 )
		    {	printf( "\n%c*** Warning: ", BEEP );
			printf( "partition on drive %c is already owned by %8.8s ***\n",
				curdrive + firstdrive, wm.wmrec.WMuser );
		    }
		    else
		    {
			if( wm.wmrec.WMstatus == 0xfb )
				printf( "You already own partition on drive %c on another drive",
				    curdrive + firstdrive);
			else
				printf("%c*** Error: partition on drive %c is not ownable\n"
				    , BEEP, curdrive + firstdrive );
		    }
		/* end if owned somewhere else */
	} /* end while there are more arguments to process */
	return TRUE;
}
/*==================================================================*/
/*page*/
drvdisp( driveletter) /* must be 4 characters */
CHAR	driveletter;
{
	printf(" %c: ", driveletter);
}
/*==================================================================*/

noname() /* must be 16 characters long for quick display */
{
	displays( "** unassigned **$");
}
/*==================================================================*/

namdisp() /* must be 8 characters */
{
	switch( getbuffer[ curdrive].GetType )
		{
		case SDmedia:
			*getbuffer[ curdrive].GetName = 'S';
			getbuffer[ curdrive].GetName[1] =
				getbuffer[ curdrive].GetUnit + '0';
			break;
		case DDmedia:
			*getbuffer[ curdrive].GetName = 'D';
			getbuffer[ curdrive].GetName[1] =
				getbuffer[ curdrive].GetUnit + '0';
			break;
		case MINI1media:
		case MINI2media:
			*getbuffer[ curdrive].GetName = 'M';
			getbuffer[ curdrive].GetName[1] =
				getbuffer[ curdrive].GetUnit + '0';
			break;
		default:
			break;
		}
	printf( "%-8.8s", getbuffer[ curdrive].GetName );
}
/*==================================================================*/
/*page*/
typdisp()
{
static	CHAR	*typmsg[] =
	{
	"8\" single density   $",
	"8\" double density   $",
	"8\" hard disk        $",
	"HiNet partition     $",
	"5\" single sided     $",
	"5\" double sided     $",
	"5\" hard disk        $",
	"RAM disk            $"
	};
	displays( typmsg[ getbuffer[ curdrive].GetType >> 5 ] );
}
/*==================================================================*/
sizdisp()	/* must be 8 characters */
{
#ifdef cpm80
	if( getbuffer[ curdrive].GetSize > 1023)
		printf( " %2dM    ", getbuffer[ curdrive].GetSize >> 10);
	else
		printf( " %3dK   ", getbuffer[ curdrive].GetSize );
	
#else
static	CHAR	*sizmsg[] =
	{
	"        $",
	" 256K   $",
	" 512K   $",
	"  1M    $",
	"  2M    $",
	"  4M    $",
	"  8M    $",
	" 342K   $" /* don't know why, but it's in the asm86 version */
	};

	displays( sizmsg[ getbuffer[ curdrive].GetSizeNo] );
#endif
}
/*==================================================================*/
/*page*/
#define LENuser 8

owndisp()
{
struct	WMstruct wm;

	wm.wmsend.WMcmd = WMquery;
	wm.wmsend.WMvol = getbuffer[ curdrive].GetVol;
	wm.wmsend.WMunit = getbuffer[ curdrive].GetUnit;
	wm.wmsend.WMuserno = ( wm.wmsend.WMvalue = userno() );
	wm.logdrive = curdrive + firstdrive - 'A';
#ifdef dbg
	printf( "\nvol: %x, unit: %x, value: %x, userno: %x", wm.wmsend.WMvol,
		wm.wmsend.WMunit, wm.wmsend.WMvalue, userno() );
#endif
	if( getbuffer[ curdrive].GetType != NETmedia )
		wm.wmrec.WMstatus = 0xf0;
	else
		if( writemode( &wm ) ) return;
#ifdef dbg
	printf("... ack: %x, status: %x\n", wm.wmrec.WMack, wm.wmrec.WMstatus);
#endif
	switch( wm.wmrec.WMstatus )
		{
		case 0xff:
			displays( "Ownable$");
			break;
		case 0xfe:
			displays( "Read/Write$");
			break;
		case 0xfd:
			displays( "Read-Only$");
			break;
		case 0xfc:
			displays( "Shared$");
			break;
		case 0xfb:
			displays( "Owned by you on another drive$");
			break;
		case 0xfa:
			displays( "Unknown state$");
			break;
		case 0xf0:
			displays( "Local$");
			break;
		default:
			displays( "Owned by $");
			if( wm.wmrec.WMstatus != userno() )
				printf("%8.8s user #%x", wm.wmrec.WMuser,
				    wm.wmrec.WMstatus );
			else
				displays( "you$");
			break;
		}
}
/*==================================================================*/
