[comp.sys.amiga] EXAMPLE DOSDEVICE V1.10

dillon@CORY.BERKELEY.EDU (Matt Dillon) (11/03/87)

	I fixed *many* bugs, and implemented a bit more of my sample RAM
disk (TEST: device):  It now timestamps files and works with the workbench.

	I would like to thank Steve Beats and all the people at Commodore
who helped me thrash out the problems!

	Those of you programmers who liked the original version are going
to love this one.

					-Matt



#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	DOSDEV.DOC
#	Makefile
#	bset.asm
#	device.c
#	device.uue
#	dos.h
#	mountlist
# This archive created: Mon Nov  2 18:43:24 1987
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'DOSDEV.DOC'" '(7852 characters)'
if test -f 'DOSDEV.DOC'
then
	echo shar: "will not over-write existing file 'DOSDEV.DOC'"
else
cat << \!Funky!Stuff! > 'DOSDEV.DOC'

			    DOS DEVICE DRIVER

		  DOS DEVICE DRIVER EXAMPLE FOR AZTEC C

			       A RAM DISK,

			    by Matthew Dillon

				  V1.10 (Works with the workbench!)

    Dedicated to all those people out there (including me!) who have been
    struggling with DOS devices.  Placed in PUBLIC DOMAIN.  I would like to
    give special thanks to Steve Beats for helping me get it to work with
    the workbench.  He saved me at least 24 manhours plus a couple of white
    hairs.

    Documentation is sorely lacking... even the RKM examples are sorely
    lacking.  What programmers need is a working example ... as full an
    implementation as possible to figure out all the little DOS quirks that
    the manuals don't tell you about... Little things like when the driver
    stores a string in a structure it must be in BCPL 'length first'
    format.  There are literally hundreds of these babies!

REQUIREMENTS:

    -Aztec C compiler

    -A precompiled symbol table of all sub-directory include's (*/*.H). i.e.
     one which does NOT include top level stuff like <stdio.h>.  Remember to
     compile with the +L option when generating the symbol table.

    -A tiny bit of background with BPTR's and how dos works in general.

     The LARGE data and code model will be used... It makes life *much*
     easier.  This will NOT effect performance as there are very few global
     elements anyway.

    *Alternately, no requirements if you just want to look at the source.


MOUNTLIST:

    You will want to change this to a more permanent file path, obviously,
    though to run the example you might as well leave the driver in RAM:


THE EXAMPLE:

    How 'bout a RAM disk?  RAM disks use most DOS packet types...  Since my
    RAM disk is not meant for normal usage, There will be a lot of minor
    items I will leave unimplimented:

	-I don't check out-of-memory conditions (remember! This is ONLY an
	 example!)

	-The ARCHIVE protection bit is not supported

    All packet types normally associated with a RAM disk are supported,
    Most especially LOCKS, which *many* people have not been able to figure
    out in the past.  There are also a number of compatibility issues,
    discussed below.

DEBUGGING:

    Since this is an example, FULL Debugging has been implemented.  Since
    the DOS device driver cannot make DOS library calls itself (confusion
    on the message port), CreatProc() is used to create a secondary process
    which will do the actual printing of the debug messages.  The messages
    are sent to the debug process via a dedicated message port.

    Since Debugging causes a huge efficiency decrease, and since you are
    going to want to fool around with the device, you can turn off debug
    error message output by:

    CD test:
    type debugoff	(will get an error, but debugging will be turned
			off)

    alternately, 'type debugon' will turn it back on.

    The debugging code itself is a good example.

    ---------------------------------------------------------------------

RESTRICTIONS:

    The Workbench assumes that locks are always sizeof(struct FileLock).
    Although DOS allows you to extend the structure however you like, if
    you want your driver to work with the workbench it MUST be a normal
    FileLock.  This isn't a big problem... simply use the fl_Link and
    fl_Key fields to point to other things.

    The Workbench checks the DOS Device List every second or so.  To make
    a disk icon appear (see the source), you must construct a VOLUME node.
    This is in addition to the DEVICE node that DOS already constructed
    for our device driver.

    If you do not intend to support the Workbench, you do not need to
    make a volume node, and can extend the FileLock structure however you
    want (beyond the required fields, that is).


DOS IN GENERAL:
    DOS is the only part of the Amiga that was written in (ugghh) BCPL.
    BCPL has many strange notions.  Pointers in BCPL are always longword
    aligned and shifted right by 2 (divided by 4), so 0x0 would mean
    address 0, 0x1 would mean address 4, etc...

    To convert BPTR's to C pointers (CPTR's), simply shift left by 2. To
    convert the otherway, simply shift right by 2, but remembering that the
    original C pointer must be longword aligned.

    BCPL strings (BSTR's) are quite different.  Most commonly you have
    "BPTR's to BSTR's", which means you both have to convert the pointer
    to a C pointer, and then interpret it differently.	The first character
    in a BSTR is the length of the string (unsigned 0 to 255).	The
    actual string starts at ptr+1 (CPTR+1), and has NO TERMINATION
    CHARACTER.	See the BTOS() routine in DEVICE.C

    Most DOS structures which have string arrays are in BCPL format.  For
    example, the Name and Comment fields in FileInfoBlock are in BCPL
    format.  The DOS library from C converts these fields to normal C
    strings for you when you make Examine()/ExNext() calls, but are BCPL
    strings inside any device driver.

FILESYSTEMS:
    Beware that it is perfectly acceptable to CurrentDir(lock) a lock which
    is a file rather than a directory, then open "" to access that file.
    The workbench does this quite a bit.

    One major point not addressed well in my device driver is handling
    multiple writer's to the same file (or a reader and a writer).  It is
    also acceptable to open a file ACCESS_OLD (1005) and write to it.

    Another common problem is an incorrectly implemented Examine()/ExNext()
    function.	Keep in mind that *any* entry in the directory being
    scanned can be removed or moved out of that directory at anytime,
    including between ExNext() calls.  My RAM: disk accomplishes this by
    rescanning the directory at every ExNext() call (an admittedly
    inefficient method).

    Finally, you must properly implement shared access to files.  Remember
    that it *is* possible to have a reader AND a writer, or two writers, or
    N readers and N writers each with separate filehandles going to a
    single file.  I do not properly implement some of these cases in my
    example, but note the bug at the beginning of the source.o

    ACTION_INHIBIT is not addressed well in the device driver.	This has
    one argument, a Bool TRUE or FALSE.  If TRUE, it 'inhibits' access to
    the low level hardware handling the device.  This isn't very
    useful for a RAM disk.

    My example device does not handle small block sizes well in that they
    cause a large amount of overhead.  A really serious RAM disk would
    combine small blocks into larger ones (in terms of memory storage).
    This was actually a bug in the 1.1 RAM: disk.  I'm not sure how well
    the 1.2 RAM: disk fixes it.  Workbench has a nasty habit of writing 1
    and 2 byte blocks (somebody should really fix that!).


OTHER DOS PECULARITIES:

    NON FILE SYSTEMS:	Specifically, control terminals like CON: ..
    The CLI and other programs get a duplicate filehandle of the control
    terminal by openning the file '*'.  This is how the CLI is able to open
    two filehandles (Input()/Output()) to the same CON: device that would
    otherwise cause two invocations of a CON: device.

    This isn't to say you can simply Open("*",1005), you CAN'T!  You must
    open CON:blah, then extract the handler process ID from that
    filehandle, then manually send an OPEN_OLD packet with filename "*" to
    the handler.

    ACTION_DISK_INFO, when sent to a CONSOLE device, will return the window
    associated with the console as follows:

	    id_VolumeNode   =	console window
	    id_InUse	    =	console IO blvock

    There are probably many more.



					Matthew Dillon

					ARPA: dillon@ucbvax.berkeley.edu
					UUCP: ..!ihnp4!ucbvax!dillon
					SNAIL:	Matthew Dillon
						891 Regal Rd.
						Berkeley, Ca. 94708


!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'Makefile'" '(237 characters)'
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \!Funky!Stuff! > 'Makefile'

CFLAGS= +BCDL +Ivd0:include/symbols.m
OBJS= device.o bset.o

.c.o:
    cc $(CFLAGS) $*.c

all: $(OBJS)
    ln +Q $(OBJS) -lcl32 -O ram:device

flush:
    cc +L +Ivd0:include/symbols.m flush.c -o ram:flush.o
    ln +Q ram:flush.o -lc32

!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'bset.asm'" '(1076 characters)'
if test -f 'bset.asm'
then
	echo shar: "will not over-write existing file 'bset.asm'"
else
cat << \!Funky!Stuff! > 'bset.asm'

;BSET.ASM
;BZERO.ASM
;
;   Uses longword operations if data is aligned on a longword boundry
;   and the size is a mulitple of 4.  Otherwise, uses byte operations.

	xdef  _bset
	xdef  _bzero

_bzero
	clr.l	D1
	bra	begin
_bset
	move.b	15(A7),D1	;12(A7)-> msb . . lsb	(D1 = data)
begin
	move.l	4(A7),A0	;A0 = address
	move.l	8(A7),D0	;D0 = byte count
	andi.b	#3,11(A7)	;byte count on long word boundry?
	bne	drop
	andi.b	#3,7(A7)	;address on longword boundry?
	bne	drop
	bra	lwb
loop	move.b	D1,(A0)+	;BYTE SET LOOP
drop	dbf.w	D0,loop 	;remember, only effects lower word
	sub.l	#$10000,D0	;for buffers >65535
	bpl	loop		;branch to loop because D0.W now is FFFF
	rts

lwb	lsr.l	#2,D0		;byte count / 4 (longword chunks)
	tst.l	D1		;BZERO
	beq	dropl
	move.b	D1,14(A7)	;15(A7) already contains the byte
	move.w	14(A7),D1	;D1 0..15 set
	swap	D1
	move.w	14(A7),D1	;D1 16..31 set
	bra	dropl

loopl	move.l	D1,(A0)+	;BYTE SET LOOP
dropl	dbf.w	D0,loopl	;remember, only effects lower word
	sub.l	#$10000,D0	;for buffers >65535
	bpl	loopl		;branch to loop because D0.W now is FFFF
	rts



!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'device.c'" '(33049 characters)'
if test -f 'device.c'
then
	echo shar: "will not over-write existing file 'device.c'"
else
cat << \!Funky!Stuff! > 'device.c'

/*
 *  DOSDEVICE.C 	V1.10	2 November 1987
 *
 *  EXAMPLE DOS DEVICE DRIVER FOR AZTEC.C   PUBLIC DOMAIN.
 *
 *  By Matthew Dillon.
 *
 *  Debugging routines are disabled by simply attempting to open the
 *  file "debugoff", turned on again with "debugon".  No prefix may be
 *  attached to these names (you must be CD'd to TEST:).
 *
 *  See Documentation for a detailed discussion.
 *
 *  BUGS:
 *	Currently the only known bug is with the implementation of the
 *	RAM disk itself.  Specifically, if filehandle A is at the end of
 *	the file, and somebody appends to the file with another filehandle,
 *	B, filehandle A will get confused as to it's current position in
 *	the file.
 *
 *	I am probably not updating all the right timestamps.  This is
 *	easy to fix... All you have to do is fool with the floppy and
 *	see which datestamps get updated for certain operations.
 */

#include "dos.h"

/*
 *  Since this code might be called several times in a row without being
 *  unloaded, you CANNOT ASSUME GLOBALS HAVE BEEN ZERO'D!!  This also goes
 *  for any global/static assignments that might be changed by running the
 *  code.
 */

PROC	*DosProc;   /*	Our Process				    */
DEVNODE *DosNode;   /*	Our DOS node.. created by DOS for us	    */
DEVLIST *DevList;   /*	Device List structure for our volume node   */

void	*SysBase;   /*	EXEC library base			*/
DOSLIB	*DOSBase;   /*	DOS library base for debug process	*/
RAMFILE RFRoot;     /*	Directory/File structure    (root node) */
LIST	FHBase;     /*	Open Files				*/
LIST	LCBase;     /*	Open Locks				*/

long	TotalBytes; /*	total bytes of data in filesystem	*/


		    /*	DEBUGGING			*/
PORT *Dbport;	    /*	owned by the debug process	*/
PORT *Dback;	    /*	owned by the DOS device driver	*/
short DBDisable;
MSG DummyMsg;	    /*	Dummy message that debug proc can use	*/

/*
 *  Don't call the entry point main().  This way, if you make a mistake
 *  with the compile options you'll get a link error.
 */

void
noname()
{
    register PACKET *packet;
    register short   error;
    MSG     *msg;
    ubyte   notdone;
    ubyte   buf[256];
    void    *tmp;

    /*
     *	Initialize all global variables.  SysBase MUST be initialized before
     *	we can make Exec calls.  AbsExecBase is a library symbol
     *	referencing absolute memory location 4.  The DOS library is openned
     *	for the debug process only.
     */

    DBDisable = 0;				/*  Init. globals	*/
    Dbport = Dback = NULL;
    TotalBytes = 0;
    SysBase = AbsExecBase;
    DOSBase = OpenLibrary("dos.library",0);
    DosProc = FindTask(NULL);
    {
	WaitPort(&DosProc->pr_MsgPort); 	/*  Get Startup Packet	*/
	msg = GetMsg(&DosProc->pr_MsgPort);
	packet = (PACKET *)msg->mn_Node.ln_Name;

	/*
	 *  Loading DosNode->dn_Task causes DOS *NOT* to startup a new
	 *  instance of the device driver for every reference.	E.G. if
	 *  you were writing a CON device you would want this field to
	 *  be NULL.
	 */

	if (DOSBase) {
	    DOSINFO *di = BTOC(((ROOTNODE *)DOSBase->dl_Root)->rn_Info);
	    register DEVLIST *dl = dosalloc(sizeof(DEVLIST));

	    DosNode = BTOC(packet->dp_Arg3);

	    /*
	     *	Create Volume node and add to the device list.	This will
	     *	cause the WORKBENCH to recognize us as a disk.	If we don't
	     *	create a Volume node, Wb will not recognize us.  However,
	     *	we are a RAM: disk, Volume node or not.
	     */

	    DevList = dl;
	    dl->dl_Type = DLT_VOLUME;
	    dl->dl_Task = &DosProc->pr_MsgPort;
	    dl->dl_DiskType = ID_DOS_DISK;
	    dl->dl_Name = (void *)DosNode->dn_Name;
	    dl->dl_Next = di->di_DevInfo;
	    di->di_DevInfo = (long)CTOB(dl);

	    /*
	     *	Set dn_Task field which tells DOS not to startup a new
	     *	process on every reference.
	     */

	    DosNode->dn_Task = &DosProc->pr_MsgPort;
	    packet->dp_Res1 = DOS_TRUE;
	    packet->dp_Res2 = 0;
	} else {			    /*	couldn't open dos.library   */
	    packet->dp_Res1 = DOS_FALSE;
	    returnpacket(packet);
	    return;			    /*	exit process		    */
	}
	returnpacket(packet);
    }

    /*
     *	Initialize debugging code
     */

    dbinit();	    /* this can be removed  */

    /*	Initialize  RAM disk	*/

    {
	ubyte *ptr = BTOC(DosNode->dn_Name);
	short len = *ptr;

	NewList(&FHBase);			    /*	more globals	*/
	NewList(&LCBase);
	bzero(&RFRoot,sizeof(RFRoot));
	RFRoot.type = FILE_DIR; 		    /*	root directory	*/
	DateStamp(&RFRoot.date);		    /*	datestamp	*/
	NewList(&RFRoot.list);			    /*	sub dirs	*/
	RFRoot.name = AllocMem(len+1, MEMF_PUBLIC); /*	Root NAME	*/
	bmov(ptr+1,RFRoot.name,len);
	RFRoot.name[len] = 0;
	dbprintf("ROOT NAME: %ld '%s'\n", len, RFRoot.name);
    }

    /*
     *	Here begins the endless loop, waiting for requests over our
     *	message port and executing them.  Since requests are sent over
     *	our message port, this precludes being able to call DOS functions
     *	ourselves (that is why the debugging routines are a separate process)
     */

top:
    for (notdone = 1; notdone;) {
	WaitPort(&DosProc->pr_MsgPort);
	while (msg = GetMsg(&DosProc->pr_MsgPort)) {
	    register ubyte *ptr;
	    packet = (PACKET *)msg->mn_Node.ln_Name;
	    packet->dp_Res1 = DOS_TRUE;
	    packet->dp_Res2 = 0;
	    error = 0;
	    dbprintf("Packet: %3ld %08lx %08lx %08lx %10s ",
		packet->dp_Type,
		packet->dp_Arg1, packet->dp_Arg2,
		packet->dp_Arg3,
		typetostr(packet->dp_Type)
	    );

	    switch(packet->dp_Type) {
	    case ACTION_DIE:	    /*	attempt to die? 		    */
		notdone = 0;	    /*	try to die			    */
		break;
	    case ACTION_OPENRW:     /*	FileHandle,Lock,Name	    Bool    */
	    case ACTION_OPENOLD:    /*	FileHandle,Lock,Name	    Bool    */
	    case ACTION_OPENNEW:    /*	FileHandle,Lock,Name	    Bool    */
		{
		    register RAMFILE *ramfile;
		    RAMFILE *parentdir = getlockfile(packet->dp_Arg2);
		    char    *ptr;

		    btos(packet->dp_Arg3,buf);
		    dbprintf("'%s' ", buf);
		    if (strcmp(buf,"debugoff") == 0)
			DBDisable = 1;
		    if (strcmp(buf,"debugon") == 0)
			DBDisable = 0;
		    if (ramfile = searchpath(&parentdir,buf,&ptr)) {
			if (ramfile->type == FILE_DIR) {
			    error = ERROR_OBJECT_WRONG_TYPE;
			    goto openbreak;
			}
			if (ramfile->locks < 0) {
			    error = ERROR_OBJECT_IN_USE;
			    goto openbreak;
			}
			if (packet->dp_Type == ACTION_OPENOLD) {
			    ++ramfile->locks;
			} else {
			    if (ramfile->locks > 0) {
				error = ERROR_OBJECT_IN_USE;
			    } else {
				if (packet->dp_Type == ACTION_OPENNEW) {
				    freedata(ramfile);
				    ramfile->protection = 0;
				}
				--ramfile->locks;
			    }
			}
		    } else {
			if (!parentdir) {
			    error = ERROR_INVALID_COMPONENT_NAME;
			    goto openbreak;
			}
			if (packet->dp_Type == ACTION_OPENNEW) {
			    ramfile = createramfile(parentdir, FILE_FILE, ptr);
			    --ramfile->locks;
			} else {
			    error = ERROR_OBJECT_NOT_FOUND;
			}
		    }
		    if (!error) {
			register MYFH *mfh = AllocMem(sizeof(MYFH), MEMF_PUBLIC|MEMF_CLEAR);
			((FH *)BTOC(packet->dp_Arg1))->fh_Arg1 = (long)mfh;
			mfh->file = ramfile;
			mfh->fentry = GetHead(&ramfile->list);
			AddHead(&FHBase,mfh);
		    }
		}
	      openbreak:
		if (!GetHead(&FHBase) && !GetHead(&LCBase))
		    notdone = 0;
		break;
	    case ACTION_READ:	    /*	 FHArg1,CPTRBuffer,Length   ActLength  */
		{
		    register MYFH   *mfh = (MYFH *)packet->dp_Arg1;
		    register FENTRY *fen = mfh->fentry;
		    register ubyte  *ptr = (ubyte *)packet->dp_Arg2;
		    register long   left = packet->dp_Arg3;
		    register long   scr;

		    while (left && fen) {
			scr = fen->bytes - mfh->offset;
			if (left < scr) {
			    bmov(fen->buf + mfh->offset, ptr, left);
			    mfh->offset += left;
			    left = 0;
			} else {
			    bmov(fen->buf + mfh->offset, ptr, scr);
			    left -= scr;
			    ptr += scr;
			    mfh->base += fen->bytes;
			    mfh->offset = 0;
			    fen = NextNode(fen);
			}
		    }
		    mfh->fentry = fen;
		    packet->dp_Res1 = packet->dp_Arg3 - left;
		}
		break;
	    case ACTION_WRITE:	    /*	 FHArg1,CPTRBuffer,Length   ActLength  */
		{
		    register MYFH   *mfh = (MYFH *)packet->dp_Arg1;
		    register FENTRY *fen = (FENTRY *)mfh->fentry;
		    ubyte  *ptr = (ubyte *)packet->dp_Arg2;
		    long   left = packet->dp_Arg3;
		    long   scr;

		    /*
		     *	Doesn't work right if multiple readers/appenders.
		     */

		    while (left) {
			if (fen) {
			    dbprintf("FEN: %ld  left: %ld\n", fen->bytes, left);
			    scr = fen->bytes - mfh->offset;
			    if (left < scr) {
				if (fen->bytes < mfh->offset + left)
				    dbprintf("PANIC! AWR0\n");
				else
				    bmov(ptr, fen->buf + mfh->offset, left);
				mfh->offset += left;
				left = 0;
			    } else {
				if (fen->bytes < mfh->offset + scr)
				    dbprintf("PANIC! AWR1\n");
				else
				    bmov(ptr, fen->buf + mfh->offset, scr);
				ptr += scr;
				left -= scr;
				mfh->base += fen->bytes;
				mfh->offset = 0;
				fen = NextNode(fen);
			    }
			} else {
			    fen = AllocMem(sizeof(FENTRY), MEMF_PUBLIC);
			    if (fen->buf = AllocMem(left, MEMF_PUBLIC)) {
				fen->bytes = left;
				mfh->file->bytes += left;
				mfh->base  += left;
				mfh->offset = 0;
				TotalBytes += left;
				AddTail(&mfh->file->list, fen);
				dbprintf("NEWFEN: (%ld)\n", fen->bytes);
				bmov(ptr, fen->buf, left);
				left = 0;
			    } else {
				FreeMem(fen, sizeof(FENTRY));
				dbprintf("NEWFEN: ****** Unable to allocate buffer %ld\n", left);
				mfh->offset = 0;
				break;
			    }
			    fen = NULL;     /*	cause append	*/
			}
		    }
		    packet->dp_Res1 = packet->dp_Arg3 - left;
		    mfh->fentry = fen;
		}
		break;
	    case ACTION_CLOSE:	    /*	 FHArg1 		    Bool:TRUE  */
		{
		    register MYFH   *mfh = (MYFH *)packet->dp_Arg1;
		    register RAMFILE *file = mfh->file;

		    Remove(mfh);
		    FreeMem(mfh,sizeof(*mfh));
		    if (--file->locks < 0)
			file->locks = 0;
		}
		if (!GetHead(&FHBase) && !GetHead(&LCBase))
		    notdone = 0;
		break;
	    case ACTION_SEEK:	    /*	 FHArg1,Position,Mode	    OldPosition*/
		{
		    register MYFH *mfh = (MYFH *)packet->dp_Arg1;
		    register FENTRY *fen;
		    register long absseek;

		    packet->dp_Res1 = mfh->base + mfh->offset;
		    absseek = packet->dp_Arg2;
		    if (packet->dp_Arg3 == 0)
			absseek += mfh->base + mfh->offset;
		    if (packet->dp_Arg3 == 1)
			absseek = mfh->file->bytes + absseek;
		    if (absseek < 0 || absseek > mfh->file->bytes) {
			error = ERROR_SEEK_ERROR;
			break;
		    }
		    mfh->base = mfh->offset = 0;

		    /*
		     *	Stupid way to do it but....
		     */

		    for (fen = GetHead(&mfh->file->list); fen; fen = NextNode(fen)) {
			if (mfh->base + fen->bytes > absseek) {
			    mfh->offset = absseek - mfh->base;
			    break;
			}
			mfh->base += fen->bytes;
		    }
		    mfh->fentry = fen;
		}
		break;
	    /*
	     *	This implementation sucks.  The right way to do it is with
	     *	a hash table.  The directory must be searched for the file
	     *	name, then the next entry retrieved.  If the next entry is
	     *	NULL there are no more entries.  If the filename could not
	     *	be found we return the first entry, if any.
	     *
	     *	You can't simply keep a pointer around to the next node
	     *	because it can be moved or removed at any time.
	     */

	    case ACTION_EXAMINE_NEXT: /*   Lock,Fib		      Bool	 */
		{
		    register FIB *fib = BTOC(packet->dp_Arg2);
		    register RAMFILE *dir = getlockfile(packet->dp_Arg1);
		    register RAMFILE *file;

		    if (dir->type == FILE_FILE) {
			error = ERROR_OBJECT_WRONG_TYPE;
			break;
		    }
		    file = GetHead(&dir->list);
		    if (fib->fib_DiskKey) {
			register int len = *(ubyte *)fib->fib_FileName;
			for (; file; file = NextNode(file)) {
			    if (strlen(file->name) == len && nccmp(file->name, fib->fib_FileName+1, len))
				break;
			}
			if (file)
			    file = NextNode(file);
			else
			    file = GetHead(&dir->list);
		    }
		    fib->fib_DiskKey = 1;
		    error = -1;
		    if (!(tmp=file)) {
			error = ERROR_NO_MORE_ENTRIES;
			break;
		    }
		}
		/*  fall through    */
	    case ACTION_EXAMINE_OBJECT: /*   Lock,Fib			Bool	   */
		{
		    register FIB *fib;
		    register RAMFILE *file;
		    register RAMFILE *dummy;

		    fib = BTOC(packet->dp_Arg2);
		    if (error) {
			file = tmp;	/*  fall through from above */
		    } else {
			file = getlockfile(packet->dp_Arg1);
			fib->fib_DiskKey = 0;
		    }
		    error = 0;
		    fib->fib_DirEntryType = file->type;
		    strcpy(fib->fib_FileName+1, file->name);
		    fib->fib_FileName[0] = strlen(file->name);
		    fib->fib_Protection = file->protection;
		    fib->fib_EntryType = NULL;
		    fib->fib_Size = file->bytes;
		    fib->fib_NumBlocks = file->bytes >> 9;
		    fib->fib_Date = file->date;
		    if (file->comment) {
			strcpy(fib->fib_Comment+1, file->comment);
			fib->fib_Comment[0] = strlen(file->comment);
		    } else {
			fib->fib_Comment[0] = 0;
		    }
		}
		break;
	    case ACTION_INFO:	    /*	Lock, InfoData	  Bool:TRUE    */
		tmp = BTOC(packet->dp_Arg2);
		error = -1;
		/*  fall through    */
	    case ACTION_DISK_INFO:  /*	InfoData	  Bool:TRUE    */
		{
		    register INFODATA *id;

		    /*
		     *	Note:	id_NumBlocks is never 0, but only to get
		     *	around a bug I found in my shell (where I divide
		     *	by id_NumBlocks).  Other programs probably break
		     *	as well.
		     */

		    (error) ? (id = tmp) : (id = BTOC(packet->dp_Arg1));
		    error = 0;
		    bzero(id, sizeof(*id));
		    id->id_DiskState = ID_VALIDATED;
		    id->id_NumBlocks	 = (TotalBytes >> 9) + 1;
		    id->id_NumBlocksUsed = (TotalBytes >> 9) + 1;
		    id->id_BytesPerBlock = 512;
		    id->id_DiskType = ID_DOS_DISK;
		    id->id_VolumeNode = (long)CTOB(DosNode);
		    id->id_InUse = (long)GetHead(&LCBase);
		}
		break;
	    case ACTION_PARENT:     /*	 Lock			    ParentLock */
		{
		    register RAMFILE *file = getlockfile(packet->dp_Arg1);
		    if (file->type == FILE_FILE) {
			error = ERROR_OBJECT_NOT_FOUND;
			break;
		    }
		    if (file->locks < 0) {
			error = ERROR_OBJECT_IN_USE;
			break;
		    }
		    if (file->parent)
			packet->dp_Res1 = (long)CTOB(ramlock(file->parent, ACCESS_READ));
		    else
			error = ERROR_OBJECT_NOT_FOUND;
		}
		break;
	    case ACTION_DELETE_OBJECT: /*Lock,Name		    Bool       */
		{
		    RAMFILE *parentdir = getlockfile(packet->dp_Arg1);
		    RAMFILE *ramfile;

		    btos(packet->dp_Arg2, buf);
		    if (ramfile = searchpath(&parentdir,buf,NULL)) {
			if (ramfile->locks || ramfile == &RFRoot) {
			    error = ERROR_OBJECT_IN_USE;
			    break;
			}
			if (ramfile->type == FILE_DIR) {
			    if (GetHead(&ramfile->list))
				error = ERROR_DIRECTORY_NOT_EMPTY;
			} else {
			    freedata(ramfile);
			}
			if (!error) {
			    freeramfile(ramfile);
			    DateStamp(&parentdir->date);
			}
		    } else {
			if (!parentdir)
			    error = ERROR_INVALID_COMPONENT_NAME;
			else
			    error = ERROR_OBJECT_NOT_FOUND;
		    }
		}
		if (!GetHead(&FHBase) && !GetHead(&LCBase))
		    notdone = 0;
		break;
	    case ACTION_CREATE_DIR: /*	 Lock,Name		    Lock       */
		{
		    RAMFILE *parentdir = getlockfile(packet->dp_Arg1);
		    RAMFILE *ramfile;
		    char *ptr;

		    btos(packet->dp_Arg2, buf);
		    if (ramfile = searchpath(&parentdir,buf,&ptr)) {
			error = ERROR_OBJECT_EXISTS;
			break;
		    }
		    if (!parentdir) {
			error = ERROR_INVALID_COMPONENT_NAME;
			break;
		    }
		    ramfile = createramfile(parentdir, FILE_DIR, ptr);
		    packet->dp_Res1 = (long)CTOB(ramlock(ramfile, ACCESS_WRITE));
		}
		break;
	    case ACTION_LOCATE_OBJECT:	/*   Lock,Name,Mode		Lock	   */
		{
		    RAMFILE *parentdir = getlockfile(packet->dp_Arg1);
		    RAMFILE *ramfile;

		    btos(packet->dp_Arg2, buf);
		    dbprintf("'%s' %ld ", buf, packet->dp_Arg3);
		    if (ramfile = searchpath(&parentdir,buf,NULL)) {
			if (ramfile->locks < 0 || (ramfile->locks && packet->dp_Arg3 == ACCESS_WRITE)) {
			    error = ERROR_OBJECT_IN_USE;
			    break;
			}
			packet->dp_Res1 = (long)CTOB(ramlock(ramfile, packet->dp_Arg3));
		    } else {
			if (!parentdir)
			    error = ERROR_INVALID_COMPONENT_NAME;
			else
			    error = ERROR_OBJECT_NOT_FOUND;
		    }
		}
		break;
	    case ACTION_COPY_DIR:   /*	 Lock,			    Lock       */
		{
		    register RAMFILE *ramfile = getlockfile(packet->dp_Arg1);
		    if (ramfile->locks < 0)
			error = ERROR_OBJECT_IN_USE;
		    else
			packet->dp_Res1 = (long)CTOB(ramlock(ramfile, ACCESS_READ));
		}
		break;
	    case ACTION_FREE_LOCK:  /*	 Lock,			    Bool       */
		if (packet->dp_Arg1);
		    ramunlock(BTOC(packet->dp_Arg1));
		if (!GetHead(&FHBase) && !GetHead(&LCBase))
		    notdone = 0;
		break;
	    case ACTION_SET_PROTECT:/*	 -,Lock,Name,Mask	   Bool       */
		{
		    register RAMFILE *ramfile;
		    RAMFILE *parentdir = getlockfile(packet->dp_Arg2);
		    char *ptr;

		    btos(packet->dp_Arg3, buf);
		    if (ramfile = searchpath(&parentdir,buf,&ptr)) {
			ramfile->protection = packet->dp_Arg4;
		    } else {
			if (parentdir)
			    error = ERROR_OBJECT_NOT_FOUND;
			else
			    error = ERROR_INVALID_COMPONENT_NAME;
		    }
		}
		break;
	    case ACTION_SET_COMMENT:/*	 -,Lock,Name,Comment	   Bool       */
		{
		    register RAMFILE *ramfile;
		    RAMFILE *parentdir = getlockfile(packet->dp_Arg2);
		    char *ptr;

		    btos(packet->dp_Arg3, buf);
		    if (ramfile = searchpath(&parentdir,buf,&ptr)) {
			btos(packet->dp_Arg4, buf);
			if (ramfile->comment)
			    FreeMem(ramfile->comment,strlen(ramfile->comment)+1);
			ramfile->comment = AllocMem(strlen(buf)+1, MEMF_PUBLIC);
			strcpy(ramfile->comment, buf);
		    } else {
			if (parentdir)
			    error = ERROR_OBJECT_NOT_FOUND;
			else
			    error = ERROR_INVALID_COMPONENT_NAME;
		    }
		}
		break;
	    case ACTION_RENAME_OBJECT:/* SLock,SName,DLock,DName    Bool       */
		{
		    register RAMFILE *file1;
		    RAMFILE *sourcedir = getlockfile(packet->dp_Arg1);
		    RAMFILE *destdir   = getlockfile(packet->dp_Arg3);
		    char *ptr;

		    btos(packet->dp_Arg2,buf);
		    dbprintf("\nRENAME '%s' (%ld)  ", buf, strlen(buf));
		    if (file1 = searchpath(&sourcedir,buf,NULL)) {
			btos(packet->dp_Arg4,buf);
			dbprintf("TO '%s' (%ld)", buf, strlen(buf));
			if (searchpath(&destdir,buf,&ptr)) {
			    error = ERROR_OBJECT_EXISTS;
			} else {
			    if (destdir) {
				if (file1 == destdir) { /* moving inside self */
				    error = ERROR_OBJECT_IN_USE;
				    break;
				}
				dbprintf("REN '%s' %ld", ptr, strlen(ptr));
				DateStamp(&sourcedir->date);
				DateStamp(&destdir->date);
				/*FreeMem(file1->name, strlen(file1->name)+1);*/
				Remove(file1);
				file1->name = AllocMem(strlen(ptr)+1,MEMF_PUBLIC);
				file1->parent = destdir;
				strcpy(file1->name, ptr);
				AddHead(&destdir->list, file1);
			    } else {
				error = ERROR_INVALID_COMPONENT_NAME;
			    }
			}
		    } else {
			if (sourcedir)
			    error = ERROR_OBJECT_NOT_FOUND;
			else
			    error = ERROR_INVALID_COMPONENT_NAME;
		    }
		}
		break;
	    /*
	     *	A few other packet types which we do not support
	     */
	    case ACTION_INHIBIT:    /*	 Bool			    Bool       */
		/*  Return success for the hell of it	*/
		break;
	    case ACTION_RENAME_DISK:/*	 BSTR:NewName		    Bool       */
	    case ACTION_MORECACHE:  /*	 #BufsToAdd		    Bool       */
	    case ACTION_WAIT_CHAR:  /*	 Timeout, ticks 	    Bool       */
	    case ACTION_FLUSH:	    /*	 writeout bufs, disk motor off	       */
	    case ACTION_RAWMODE:    /*	 Bool(-1:RAW 0:CON)	    OldState   */
	    default:
		error = ERROR_ACTION_NOT_KNOWN;
		break;
	    }
	    if (packet) {
		if (error) {
		    dbprintf("ERR=%ld\n", error);
		    packet->dp_Res1 = DOS_FALSE;
		    packet->dp_Res2 = error;
		} else {
		    dbprintf("RES=%06lx\n", packet->dp_Res1);
		}
		returnpacket(packet);
	    }
	}
    }
    dbprintf("Can we remove ourselves? ");
    Delay(50);	    /*	I wanna even see the debug message! */
    Forbid();
    if (packetsqueued(DosProc) || GetHead(&FHBase) || GetHead(&LCBase)
      || GetHead(&RFRoot.list)) {
	Permit();
	dbprintf(" ..  not yet!\n");
	goto top;		/*  sorry... can't exit     */
    }

    /*
     *	Causes a new process to be created on next reference
     */

    DosNode->dn_Task = FALSE;

    /*
     *	Remove Volume entry.  Since DOS uses singly linked lists, we
     *	must (ugg) search it manually to find the link before our
     *	Volume entry.
     */

    {
	DOSINFO *di = BTOC(((ROOTNODE *)DOSBase->dl_Root)->rn_Info);
	register DEVLIST *dl;
	register void *dlp;

	dlp = &di->di_DevInfo;
	for (dl = BTOC(di->di_DevInfo); dl && dl != DevList; dl = BTOC(dl->dl_Next))
	    dlp = &dl->dl_Next;
	if (dl == DevList) {
	    *(BPTR *)dlp = dl->dl_Next;
	    dosfree(dl);
	} else {
	    dbprintf("****PANIC: Unable to find volume node\n");
	}
    }

    /*
     *	Remove debug process, closedown, fall of the end of the world
     *	(which is how you kill yourself if a PROCESS.  A TASK would have
     *	had to RemTask(NULL) itself).
     */

    dbuninit();
    CloseLibrary(DOSBase);
}


/*
 *  PACKET ROUTINES.	Dos Packets are in a rather strange format as you
 *  can see by this and how the PACKET structure is extracted in the
 *  GetMsg() of the main routine.
 */

void
returnpacket(packet)
register struct DosPacket *packet;
{
    register struct Message *mess;
    register struct MsgPort *replyport;

    replyport		     = packet->dp_Port;
    mess		     = packet->dp_Link;
    packet->dp_Port	     = &DosProc->pr_MsgPort;
    mess->mn_Node.ln_Name    = (char *)packet;
    mess->mn_Node.ln_Succ    = NULL;
    mess->mn_Node.ln_Pred    = NULL;
    PutMsg(replyport, mess);
}

/*
 *  Are there any packets queued to our device?
 */

packetsqueued()
{
    return ((void *)DosProc->pr_MsgPort.mp_MsgList.lh_Head !=
	    (void *)&DosProc->pr_MsgPort.mp_MsgList.lh_Tail);
}

/*
 *  DOS MEMORY ROUTINES
 *
 *  DOS makes certain assumptions about LOCKS.	A lock must minimally be
 *  a FileLock structure, with additional private information after the
 *  FileLock structure.  The longword before the beginning of the structure
 *  must contain the length of structure + 4.
 *
 *  NOTE!!!!! The workbench does not follow the rules and assumes it can
 *  copy lock structures.  This means that if you want to be workbench
 *  compatible, your lock structures must be EXACTLY sizeof(struct FileLock).
 */

void *
dosalloc(bytes)
register ulong bytes;
{
    register ulong *ptr;

    bytes += 4;
    ptr = AllocMem(bytes, MEMF_PUBLIC|MEMF_CLEAR);
    *ptr = bytes;
    return(ptr+1);
}

dosfree(ptr)
register ulong *ptr;
{
    --ptr;
    FreeMem(ptr, *ptr);
}

/*
 *  Convert a BSTR into a normal string.. copying the string into buf.
 *  I use normal strings for internal storage, and convert back and forth
 *  when required.
 */

void
btos(bstr,buf)
ubyte *bstr;
ubyte *buf;
{
    bstr = BTOC(bstr);
    bmov(bstr+1,buf,*bstr);
    buf[*bstr] = 0;
}

/*
 *  Some EXEC list handling routines not found in the EXEC library.
 */

void *
NextNode(node)
NODE *node;
{
    node = node->mln_Succ;
    if (node->mln_Succ == NULL)
	return(NULL);
    return(node);
}

void *
GetHead(list)
LIST *list;
{
    if ((void *)list->mlh_Head != (void *)&list->mlh_Tail)
	return(list->mlh_Head);
    return(NULL);
}

/*
 *  Compare two names which are at least n characters long each,
 *  ignoring case.
 */

nccmp(p1,p2,n)
register ubyte *p1, *p2;
register short n;
{
    while (--n >= 0) {
	if ((p1[n]|0x20) != (p2[n]|0x20))
	    return(0);
    }
    return(1);
}

/*
 *  Create a file or directory and link it into it's parent directory.
 */

RAMFILE *
createramfile(parentdir, type, name)
RAMFILE *parentdir;
char *name;
{
    register RAMFILE *ramfile;

    ramfile = AllocMem(sizeof(RAMFILE), MEMF_CLEAR|MEMF_PUBLIC);
    AddTail(&parentdir->list, ramfile);
    ramfile->parent = parentdir;
    ramfile->name = AllocMem(strlen(name)+1, MEMF_PUBLIC);
    strcpy(ramfile->name, name);
    ramfile->type = type;
    ramfile->protection = 0;
    NewList(&ramfile->list);
    DateStamp(&ramfile->date);
    DateStamp(&ramfile->parent->date);
    return(ramfile);
}

/*
 *  Free all data associated with a file
 */

void
freedata(ramfile)
RAMFILE *ramfile;
{
    FENTRY *fen;

    TotalBytes -= ramfile->bytes;
    while (fen = RemHead(&ramfile->list)) {
	dbprintf("FREE FEN: %08lx %08lx %ld\n", fen, fen->buf, fen->bytes);
	FreeMem(fen->buf, fen->bytes);
	FreeMem(fen, sizeof(*fen));
    }
    ramfile->bytes = 0;
    DateStamp(&ramfile->date);
    DateStamp(&ramfile->parent->date);
}

/*
 *  Unlink and remove a file.  Any data associated with the file or
 *  directory has already been freed up.
 */

void
freeramfile(ramfile)
RAMFILE *ramfile;
{
    Remove(ramfile);		/*  unlink from parent directory    */
    if (ramfile->name)
	FreeMem(ramfile->name,strlen(ramfile->name)+1);
    if (ramfile->comment)
	FreeMem(ramfile->comment,strlen(ramfile->comment)+1);
    FreeMem(ramfile,sizeof(*ramfile));
}

/*
 *  The lock function.	The file has already been checked to see if it
 *  is lockable given the mode.
 */

LOCK *
ramlock(ramfile, mode)
RAMFILE *ramfile;
{
    LOCK *lock = dosalloc(sizeof(LOCK));
    LOCKLINK *ln;

    if (mode != ACCESS_WRITE)
	mode = ACCESS_READ;
    ln = AllocMem(sizeof(LOCKLINK), MEMF_PUBLIC);
    AddHead(&LCBase,ln);
    ln->lock = lock;
    lock->fl_Link= (long)ln;
    lock->fl_Key = (long)ramfile;
    lock->fl_Access = mode;
    lock->fl_Task = &DosProc->pr_MsgPort;
    lock->fl_Volume = (BPTR)CTOB(DosNode);
    if (mode == ACCESS_READ)
	++ramfile->locks;
    else
	ramfile->locks = -1;
    return(lock);
}

void
ramunlock(lock)
LOCK *lock;
{
    RAMFILE *file = (RAMFILE *)lock->fl_Key;

    Remove(lock->fl_Link);			/* unlink from list */
    FreeMem(lock->fl_Link, sizeof(LOCKLINK));	/* free link node   */
    if (lock->fl_Access == ACCESS_READ) 	/* undo lock effect */
	--file->locks;
    else
	file->locks = 0;
    dosfree(lock);				/* free lock	    */
}

/*
 *  GETLOCKFILE(bptrlock)
 *
 *  Return the RAMFILE entry (file or directory) associated with the
 *  given lock, which is passed as a BPTR.
 *
 *  According to the DOS spec, the only way a NULL lock will ever be
 *  passed to you is if the DosNode->dn_Lock is NULL, but I'm not sure.
 *  In anycase, If a NULL lock is passed to me I simply assume it means
 *  the root directory of the RAM disk.
 */

RAMFILE *
getlockfile(lock)
void *lock;		/*  actually BPTR to LOCK */
{
    register LOCK *rl = BTOC(lock);

    if (rl)
	return((RAMFILE *)rl->fl_Key);
    return(&RFRoot);
}

/*
 *  Search the specified path beginning at the specified directory.
 *  The directory pointer is updated to the directory containing the
 *  actual file.  Return the file node or NULL if not found.  If the
 *  path is illegal (an intermediate directory was not found), set *ppar
 *  to NULL and return NULL.
 *
 *  *ppar may also be set to NULL if the search path IS the root.
 *
 *  If pptr not NULL, Set *pptr to the final component in the path.
 */

RAMFILE *
searchpath(ppar,buf,pptr)
RAMFILE **ppar;
char *buf;
char **pptr;
{
    RAMFILE *file = *ppar;
    RAMFILE *srch;
    short len;
    char *ptr;

    *ppar = NULL;
    for (;*buf && file;) {
	ptr = getpathelement(&buf,&len);
	if (buf[0] == ':') {    /*  go to root          */
	    ++buf;
	    file = &RFRoot;
	    continue;
	}
	if (*ptr == '/') {          /*  go back a directory */
	    if (!file->parent) {    /*	no parent directory */
		return(NULL);
	    }
	    file = file->parent;
	    continue;
	}
	if (file->type == FILE_FILE)
	    return(NULL);
	for (srch = GetHead(&file->list); srch; srch = NextNode(srch)) {
	    if (srch->type && strlen(srch->name) == len && nccmp(srch->name, ptr, len)) {
		file = srch;	    /*	element found	    */
		break;
	    }
	}
	if (srch == NULL) {
	    if (*buf == 0)	/*  Element not found.	If it was the final */
		*ppar = file;	/*  element the parent directory is valid   */
	    if (pptr)
		*pptr = ptr;
	    return(NULL);
	}
    }
    if (pptr)
	*pptr = ptr;
    *ppar = file->parent;
    return(file);
}

/*
 *  Return the next path element in the string.  The routine effectively
 *  removes any trailing '/'s, but treats ':' as part of the next component
 *  (i.e. ':' is checked and skipped in SEARCHPATH()).
 */

char *
getpathelement(pstr,plen)
char **pstr;
short *plen;
{
    char *base;
    register char *ptr = *pstr;
    register short len = 0;

    if (*(base = ptr)) {
	if (*ptr == '/') {
	    ++ptr;
	    ++len;
	} else {
	    while (*ptr && *ptr != '/' && *ptr != ':') {
		++ptr;
		++len;
	    }
	    if (*ptr == '/')
		++ptr;
	}
    }
    *pstr = ptr;
    *plen = len;
    return(base);
}


char *
typetostr(ty)
{
    switch(ty) {
    case ACTION_DIE:		return("DIE");
    case ACTION_OPENRW: 	return("OPEN-RW");
    case ACTION_OPENOLD:	return("OPEN-OLD");
    case ACTION_OPENNEW:	return("OPEN-NEW");
    case ACTION_READ:		return("READ");
    case ACTION_WRITE:		return("WRITE");
    case ACTION_CLOSE:		return("CLOSE");
    case ACTION_SEEK:		return("SEEK");
    case ACTION_EXAMINE_NEXT:	return("EXAMINE NEXT");
    case ACTION_EXAMINE_OBJECT: return("EXAMINE OBJ");
    case ACTION_INFO:		return("INFO");
    case ACTION_DISK_INFO:	return("DISK INFO");
    case ACTION_PARENT: 	return("PARENTDIR");
    case ACTION_DELETE_OBJECT:	return("DELETE");
    case ACTION_CREATE_DIR:	return("CREATEDIR");
    case ACTION_LOCATE_OBJECT:	return("LOCK");
    case ACTION_COPY_DIR:	return("DUPLOCK");
    case ACTION_FREE_LOCK:	return("FREELOCK");
    case ACTION_SET_PROTECT:	return("SETPROTECT");
    case ACTION_SET_COMMENT:	return("SETCOMMENT");
    case ACTION_RENAME_OBJECT:	return("RENAME");
    case ACTION_INHIBIT:	return("INHIBIT");
    case ACTION_RENAME_DISK:	return("RENAME DISK");
    case ACTION_MORECACHE:	return("MORE CACHE");
    case ACTION_WAIT_CHAR:	return("WAIT FOR CHAR");
    case ACTION_FLUSH:		return("FLUSH");
    case ACTION_RAWMODE:	return("RAWMODE");
    default:			return("---------UNKNOWN-------");
    }
}

/*
 *  DEBUGGING CODE.	You cannot make DOS library calls that access other
 *  devices from within a DOS device driver because they use the same
 *  message port as the driver.  If you need to make such calls you must
 *  create a port and construct the DOS messages yourself.  I do not
 *  do this.  To get debugging info out another PROCESS is created to which
 *  debugging messages can be sent.
 *
 *  You want the priority of the debug process to be larger than the
 *  priority of your DOS handler.  This is so if your DOS handler crashes
 *  you have a better idea of where it died from the debugging messages
 *  (remember that the two processes are asyncronous from each other).
 */

extern void debugproc();

dbinit()
{
    TASK *task = FindTask(NULL);

    Dback = CreatePort(NULL,NULL);
    CreateProc("DEV_DB", task->tc_Node.ln_Pri+1, CTOB(debugproc), 4096);
    WaitPort(Dback);				    /* handshake startup    */
    GetMsg(Dback);				    /* remove dummy msg     */
    dbprintf("Debugger running V1.10, 2 November 1987\n");
    dbprintf("Works with WORKBENCH!\n");
}

dbuninit()
{
    MSG killmsg;

    if (Dbport) {
	killmsg.mn_Length = 0;	    /*	0 means die	    */
	PutMsg(Dbport,&killmsg);
	WaitPort(Dback);	    /*	He's dead jim!      */
	GetMsg(Dback);
	DeletePort(Dback);

	/*
	 *  Since the debug process is running at a greater priority, I
	 *  am pretty sure that it is guarenteed to be completely removed
	 *  before this task gets control again.  Still, it doesn't hurt...
	 */

	Delay(50);		    /*	ensure he's dead    */
    }
}

dbprintf(a,b,c,d,e,f,g,h,i,j)
{
    char buf[256];
    MSG *msg;

    if (Dbport && !DBDisable) {
	sprintf(buf,a,b,c,d,e,f,g,h,i,j);
	msg = AllocMem(sizeof(MSG)+strlen(buf)+1, MEMF_PUBLIC|MEMF_CLEAR);
	msg->mn_Length = strlen(buf)+1;     /*	Length NEVER 0	*/
	strcpy(msg+1,buf);
	PutMsg(Dbport,msg);
    }
}

/*
 *  BTW, the DOS library used by debugmain() was actually openned by
 *  the device driver.	Note: DummyMsg cannot be on debugmain()'s stack
 *  since debugmain() goes away on the final handshake.
 */

debugmain()
{
    MSG *msg;
    short len;
    void *fh;

    Dbport = CreatePort(NULL,NULL);
    fh = Open("con:0/0/640/100/debugwindow", 1006);
    PutMsg(Dback, &DummyMsg);
    for (;;) {
	WaitPort(Dbport);
	msg = GetMsg(Dbport);
	len = msg->mn_Length;
	if (len == 0)
	    break;
	--len;			      /*  Fix length up   */
	Write(fh, msg+1, len);
	FreeMem(msg,sizeof(MSG)+len+1);
    }
    Close(fh);
    DeletePort(Dbport);
    PutMsg(Dback,&DummyMsg);	      /*  Kill handshake  */
}

/*
 *  The assembly tag for the DOS process:  CNOP causes alignment problems
 *  with the Aztec assembler for some reason.  I assume then, that the
 *  alignment is unknown.  Since the BCPL conversion basically zero's the
 *  lower two bits of the address the actual code may start anywhere around
 *  the label....  Sigh....  (see CreatProc() above).
 */

#asm
	public	_debugproc
	public	_debugmain

	cseg
	nop
	nop
	nop
_debugproc:
	nop
	nop
	movem.l D2-D7/A2-A6,-(sp)
	jsr	_debugmain
	movem.l (sp)+,D2-D7/A2-A6
	rts
#endasm

!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'device.uue'" '(15441 characters)'
if test -f 'device.uue'
then
	echo shar: "will not over-write existing file 'device.uue'"
else
cat << \!Funky!Stuff! > 'device.uue'
begin 644 device
M```#\P`````````#``````````(```FW````2`````$```/I```)MTY5_NI(
MYP\P0GD```"80KD```"V0KD```"R0KD```"N(_D````$````ID*G2'H0\$ZY
M```F;%!/(\````"J0J=.N0``)?Q83R/`````FB!Y````FDAH`%Q.N0``)LA8
M3R!Y````FDAH`%Q.N0``)D983RM`__P@;?_\)&@`"DJY````JF<``)@@>0``
M`*HB:``B("D`&.6`*T#^\DAX`"Q.NA),6$\F0"`J`!SE@"/`````GB/+````
MHB=\`````@`$(#D```":T+P```!<)T``""=\1$]3```@('D```">)V@`*``H
M(&W^\B:H``0@"^2`(&W^\B%```0@>0```)X@.0```)K0O````%PA0``()7S_
M____``Q"J@`08!1"J@`,+PI.NA%@6$],WPSP3EU.=2\*3KH14%A/3KH:+B!Y
M````GB`H`"CE@"M`_O(@;?[R<``0$#M`_O!(>0```+I.N0``)EI83TAY````
MQDZY```F6EA/2'@`.DAY````YDZY```>5%!/,_P``0```/Q(>0```11.N0``
M)%)83TAY```!"$ZY```F6EA/2'@``3`M_O!(P%*`+P!.N0``)=903R/`````
M\C`M_O!(P"\`+SD```#R(&W^\E*(+PA.N0``)+Y/[P`,,"W^\"!Y````\D(P
M```O.0```/(P+?[P2,`O`$AZ#R1.NAJ(3^\`#!M\``'_^R!Y````FDAH`%Q.
MN0``)LA83R!Y````FDAH`%Q.N0``)D983RM`__QG``WJ(&W__"1H``HE?/__
M__\`#$*J`!!X`"\J``A.NA9<6$\O`"\J`!PO*@`8+RH`%"\J``A(>@[)3KH:
M&$_O`!@@*@`(8``,O$(M__M@``U<+RH`&$ZZ%%A83RM`_O)(;?[[+RH`'$ZZ
M$))03TAM_OM(>@ZR3KH9W%!/2'H.KDAM_OM.N0``'JQ03TJ`9@@S_``!````
MF$AZ#IM(;?[[3KD``!ZL4$]*@&8&0GD```"82&W^[DAM_OM(;?[R3KH4%D_O
M``PJ`&=B($4,:``!`!9F"#@\`-1@``#6($5*:``8;`@X/`#*8```Q@RJ```#
M[0`(9@@@15)H`!A@+"!%2F@`&&\&.#P`RF`>#*H```/N``AF#B\%3KH10EA/
M($5"J``:($53:``88#A*K?[R9@8X/`#28'@,J@```^X`"&8>+RW^[DAX__\O
M+?[R3KH0<$_O``PJ`"!%4V@`&&`$.#P`S4I$9DA(>0`!``%(>``83KD``"76
M4$\L`"`J`!3E@"!`(48`)"!&(44`""!%2&@`(DZZ#\A83R!&(4``#"\&2'D`
M``"Z3KD``"7L4$](>0```+I.N@^F6$]*@&842'D```#&3KH/EEA/2H!F!$(M
M__M@``O6*BH`%"!%+"@`#"XJ`!@K:@`<_O)*K?[R9P``FDJ&9P``E"!&(D4@
M*``,D*D`%"M`_NX@+?[RL*W^[FPN+RW^\B\'($4B1BQH`!3=Z0`(+PY.N0``
M)+Y/[P`,($4@+?[RT:@`%$*M_O)@1B\M_NXO!R!%(D8L:``4W>D`""\.3KD`
M`"2^3^\`#"`M_NZ1K?[RWJW^[B!%(D8@*0`,T:@`$"!%0J@`%"\&3KH.O%A/
M+`!@`/]B($4A1@`,("H`')"M_O(E0``,8``+#"HJ`!0@12PH``PK:@`8_O(K
M:@`<_NY*K?[N9P`!ODJ&9P``\B\M_NX@1B\H``Q(>@QK3KH7?D_O``P@1B)%
M("@`#)"I`!0K0/[J("W^[K"M_NIL4"!&(D4@*0`4T*W^[B(H``RR@&0,2'H,
M1DZZ%T183V`@+RW^[B!%(D8L:``4W>D`""\.+RW^\DZY```DOD_O``P@12`M
M_N[1J``40JW^[F!L($8B12`I`!30K?[J(B@`#+*`9`Q(>@P#3KH6]%A/8"`O
M+?[J($4B1BQH`!3=Z0`(+PXO+?[R3KD``"2^3^\`#"`M_NK1K?[R("W^ZI&M
M_NX@12)&("D`#-&H`!`@14*H`!0O!DZZ#9)83RP`8```Q$AX``%(>``03KD`
M`"764$\L`$AX``$O+?[N3KD``"764$\@1B%```AG<B!&(6W^[@`,($4B:``(
M("W^[M&I`!X@12`M_N[1J``0($5"J``4("W^[M&Y````KB\&($4B:``(2&D`
M(DZY```DFE!/($8O*``,2'H+0$ZZ%B103R\M_NX@1B\H``@O+?[R3KD``"2^
M3^\`#$*M_NY@)$AX`!`O!DZY```F(%!/+RW^[DAZ"Q5.NA7J4$\@14*H`!1@
M!GP`8`#^/B`J`!R0K?[N)4``#"!%(48`#&``"1PJ*@`4($4L*``(+P5.N0``
M)JQ83TAX`!@O!4ZY```F(%!/($93:``82F@`&&P&($9":``82'D```"Z3KH,
MD%A/2H!F%$AY````QDZZ#(!83TJ`9@1"+?_[8``(P"HJ`!0@12)%("@`$-"I
M`!0E0``,+BH`&$JJ`!QF#B!%(D4@*``0T*D`%-Z`#*H````!`!QF"B!%(F@`
M"-ZI`!Y*AVT,($4B:``(OJD`'F,(.#P`VV``"&@@14*H`!0B14*I`!`@12)H
M``A(:0`B3KH,`%A/+`!@-B!%(D8@*``0T*D`#+"'8Q`@12)%(`>0J0`0(4``
M%&`:($4B1B`I``S1J``0+P9.N@NH6$\L`$J&9L8@12%&``Q@``@$("H`&.6`
M*@`O*@`43KH.^%A/+``@1@QH__\`%F8(.#P`U&``!]X@1DAH`").N@N&6$\N
M`"!%2I!G:B!%<``0*``(*T#^\F`Z($<O*``,3KD``"/R6$^PK?[R9APO+?[R
M($5(:``)(D<O*0`,3KH+9D_O``Q*@&8.+P=.N@L86$\N`$J'9L)*AV<,+P=.
MN@L&6$\N`&`.($9(:``B3KH+%EA/+@`@12"\`````7C_*T?^]F8(.#P`Z&``
M!T@@*@`8Y8`J`$I$9P8L+?[V8!`O*@`43KH.,EA/+``@14*0>``@1C`H`!9(
MP")%(T``!"!&+R@`#")%2&D`"4ZY```>W%!/($8O*``,3KD``"/R6$\@11%`
M``@@1B)%(V@`&@!T($5"J`!X($8B12-H`!X`?"!%(D8@*0`><@GBJ"%``(`@
M1='\````A")&T_P````N(-D@V2#9($9*J``09RH@1B\H`!`B14AI`)%.N0``
M'MQ03R!&+R@`$$ZY```C\EA/($410`"08`8@14(H`)!@``9R("H`&.6`*T#^
M]GC_2D1G!BHM_O9@""`J`!3E@"H`>`!(>``D+P5.N0``'E103R!%(7P```!2
M``@@12`Y````KG()XJ!2@"%```P@12`Y````KG()XJ!2@"%``!`@12%\```"
M```4($4A?$1/4P``&"`Y````GN2`($4A0``<2'D```#&3KH)G%A/($4A0``@
M8``%WB\J`!1.N@S:6$\J`"!%#&C__P`69@@X/`#-8``%P"!%2F@`&&P(.#P`
MRF``!;`@14JH``AG&$AX__X@12\H``A.N@N(4$_D@"5```Q@!#@\`,U@``6(
M+RH`%$ZZ#(183RM`_O)(;?[[+RH`&$ZZ"+Y03T*G2&W^^TAM_O).N@R(3^\`
M#"M`_NYG;B!M_NY*:``89@Y#^0```.8L;?[NO<EF"#@\`,I@``4T(&W^[@QH
M``$`%F88(&W^[DAH`").N@C.6$]*@&<$.#P`V&`*+RW^[DZZ";A83TI$9AHO
M+?[N3KH*:%A/(&W^\DAH`"Y.N0``)%)83V`02JW^\F8&.#P`TF`$.#P`S4AY
M````NDZZ"'Y83TJ`9A1(>0```,9.N@AN6$]*@&8$0BW_^V``!*XO*@`43KH+
MJEA/*T#^\DAM_OLO*@`83KH'Y%!/2&W^ZDAM_OM(;?[R3KH+K$_O``PK0/[N
M9P@X/`#+8``$<$JM_O)F"#@\`-)@``1B+RW^ZDAX``$O+?[R3KH(9D_O``PK
M0/[N2'C__R\M_NY.N@HL4$_D@"5```Q@``0R+RH`%$ZZ"RY83RM`_O)(;?[[
M+RH`&$ZZ!VA03R\J`!Q(;?[[2'H&!TZZ$*Y/[P`,0J=(;?[[2&W^\DZZ"QY/
M[P`,*T#^[F<\(&W^[DIH`!AM%")M_NY*:0`89Q(,JO____\`'&8(.#P`RF``
M`\0O*@`<+RW^[DZZ":903^2`)4``#&`02JW^\F8&.#P`TF`$.#P`S6```YHO
M*@`43KH*EEA/*@`@14IH`!AL!C@\`,I@$DAX__XO!4ZZ"6103^2`)4``#&``
M`VI*J@`4("H`%.6`+P!.N@H"6$](>0```+I.N@<`6$]*@&842'D```#&3KH&
M\%A/2H!F!$(M__M@``,P+RH`&$ZZ"BQ83RM`_O)(;?[[+RH`'$ZZ!F903TAM
M_NY(;?[[2&W^\DZZ"BY/[P`,*@!G"B!%(6H`(``:8!!*K?[R9P8X/`#-8`0X
M/`#28``"WB\J`!A.N@G:6$\K0/[R2&W^^R\J`!Q.N@844$](;?[N2&W^^TAM
M_O).N@G<3^\`#"H`9VQ(;?[[+RH`($ZZ!>Y03R!%2J@`$&<@($4O*``03KD`
M`"/R6$]2@"\`($4O*``03KD``"8@4$](>``!2&W^^TZY```C\EA/4H`O`$ZY
M```EUE!/($4A0``02&W^^R!%+R@`$$ZY```>W%!/8!!*K?[R9P8X/`#-8`0X
M/`#28``"*B\J`!1.N@DF6$\K0/[R+RH`'$ZZ"1A83RM`_NY(;?[[+RH`&$ZZ
M!5)03TAM_OM.N0``(_)83R\`2&W^^TAZ`_%.N@Z.3^\`#$*G2&W^^TAM_O).
MN@C^3^\`#"H`9P`!`DAM_OLO*@`@3KH%#E!/2&W^^TZY```C\EA/+P!(;?[[
M2'H#PDZZ#DI/[P`,2&W^ZDAM_OM(;?[N3KH(N$_O``Q*@&<(.#P`RV```+1*
MK?[N9P``J+JM_NYF"#@\`,I@``%H+RW^ZDZY```C\EA/+P`O+?[J2'H#?$ZZ
M#?9/[P`,(&W^\DAH`"Y.N0``)%)83R!M_NY(:``N3KD``"126$\O!4ZY```F
MK%A/2'@``2\M_NI.N0``(_)83U*`+P!.N0``)=903R!%(4``#"!%(6W^[@`(
M+RW^ZB!%+R@`#$ZY```>W%!/+P4@;?[N2&@`(DZY```E[%!/8`0X/`#28!!*
MK?[R9P8X/`#-8`0X/`#28```MF```+(X/`#18```JEN`9P#S0E>`9P#\;E.`
M9^A=@&<`_2Q3@&<`^PA3@&<`_F!3@&?24X!G`/SF4X!GR%.`9P#]1E.`9P#[
MPE.`9P#Y(E.`9P#X8%.`9P#Y^%.`9P#YYE.`9Z!3@&<`_7!3@&<`^FI5@&>,
MD+P````S9P#T9%N`9P#U*)"\```#BV<`_WB0O`````IG`/+$4X!G`/*^4X!G
M`/*X4X!G`/;R4X!G`/=(8`#_4B`*9SA*1&<>,`1(P"\`2'H")TZZ#)103T*J
M``PP!$C`)4``$&`.+RH`#$AZ`A1.N@QX4$\O"DZZ`F183V``\@!*+?_[9@#Q
MYDAZ`@%.N@Q:6$](>``R3KD``"1@6$].N0``)A`O.0```)I.N@)P6$]*@&8P
M2'D```"Z3KH#-EA/2H!F($AY````QDZZ`R983TJ`9A!(>0```0A.N@,66$]*
M@&<43KD``":$2'H!N4ZZ"_A83V``\7`@>0```)Y"J``(('D```"J(F@`(B`I
M`!CE@"M`_O(J+?[R6(4@;?[R("@`!.6`)D!@""H+(!/E@"9`(`MG"+?Y````
MHF;LM_D```"B9@X@12"3+PM.N@(D6$]@"DAZ`5Y.N@N.6$].N@LL+SD```"J
M3KD``"2J6$]@`/`.9&]S+FQI8G)A<GD`4D]/5"!.04U%.B`E;&0@)R5S)PH`
M4&%C:V5T.B`E,VQD("4P.&QX("4P.&QX("4P.&QX("4Q,',@`"<E<R<@`&1E
M8G5G;V9F`&1E8G5G;VX`1D5..B`E;&0@(&QE9G0Z("5L9`H`4$%.24,A($%7
M4C`*`%!!3DE#(2!!5U(Q"@!.15=&14XZ("@E;&0I"@!.15=&14XZ("HJ*BHJ
M*B!5;F%B;&4@=&\@86QL;V-A=&4@8G5F9F5R("5L9`H`)R5S)R`E;&0@``I2
M14Y!344@)R5S)R`H)6QD*2`@`%1/("<E<R<@*"5L9"D`4D5.("<E<R<@)6QD
M`$524CTE;&0*`%)%4STE,#9L>`H`0V%N('=E(')E;6]V92!O=7)S96QV97,_
M(``@+BX@(&YO="!Y970A"@`J*BHJ4$%.24,Z(%5N86)L92!T;R!F:6YD('9O
M;'5M92!N;V1E"@``3E4``$CG"#`D;0`(*"H`!"92(#D```":T+P```!<)4``
M!"=*``I"DT*K``0O"R\$3KD``":.4$],WPP03EU.=4Y5```@>0```)HB>0``
M`)K3_````'0L:`!PO<EG!'`!8`)P`$Y=3G5.50``2.<(("@M``A8A$AY``$`
M`2\$3KD``"764$\D0"2$(`I8@$S?!!!.74YU3E4``"\*)&T`"%F*+Q(O"DZY
M```F(%!/)%].74YU3E4``"`M``CE@"M```@@;0`(<``0$"\`+RT`#")M``A2
MB2\)3KD``"2^3^\`#"!M``AP`!`0(FT`#$(Q"`!.74YU3E4``"!M``@K4``(
M(&T`"$J09@9P`$Y=3G4@+0`(8/9.50``(&T`"%B((FT`"+'19PH@;0`((!!.
M74YU<`!@^$Y5``!(YP@P)&T`""9M``PX+0`24T1*1&TD<``0,D``",``!7(`
M$C-```C!``6P@6<*<`!,WPP03EU.=6#6<`%@\DY5```O"DAY``$``4AX`#I.
MN0``)=903R1`+PH@;0`(2&@`(DZY```DFE!/)6T`"``(2'@``2\M`!!.N0``
M(_)83U*`+P!.N0``)=903R5```PO+0`0+RH`#$ZY```>W%!/-6T`#@`60JH`
M&DAJ`").N0``)EI83TAJ`"Y.N0``)%)83R!J``A(:``N3KD``"126$\@"B1?
M3EU.=4Y5__P@;0`(("@`'I&Y````KB!M``A(:``B3KD``":>6$\K0/_\9TH@
M;?_\+R@`#")M__PO*0`(+RW__$AZ`&1.N@>V3^\`$"!M__PO*``,(FW__"\I
M``A.N0``)B!03TAX`!`O+?_\3KD``"8@4$]@H"!M``A"J``>(&T`"$AH`"Y.
MN0``)%)83R!M``@B:``(2&D`+DZY```D4EA/3EU.=492144@1D5..B`E,#AL
M>"`E,#AL>"`E;&0*``!.50``+RT`"$ZY```FK%A/(&T`"$JH``QG)"!M``@O
M*``,3KD``"/R6$]2@"\`(&T`""\H``Q.N0``)B!03R!M``A*J``09R0@;0`(
M+R@`$$ZY```C\EA/4H`O`"!M``@O*``03KD``"8@4$](>``Z+RT`"$ZY```F
M(%!/3EU.=4Y5__A(>``43KK]#%A/*T#__`RM_____P`,9P@K?/____X`#$AX
M``%(>``,3KD``"764$\K0/_X+RW_^$AY````QDZY```E[%!/(&W_^"%M__P`
M""!M__P@K?_X(&W__"%M``@`!"!M__PA;0`,``@@;?_\(#D```":T+P```!<
M(4``#"`Y````GN2`(&W__"%``!`,K?____X`#&8*(&T`"%)H`!A@"B!M``@Q
M?/__`!@@+?_\3EU.=4Y5__P@;0`(*V@`!/_\(&T`""\03KD``":L6$](>``,
M(&T`""\03KD``"8@4$\@;0`(#*C____^``AF"B!M__Q3:``88`@@;?_\0F@`
M&"\M``A.NOPV6$].74YU3E4``"\*("T`".6`)$`@"F<*("H`!"1?3EU.=4'Y
M````YB`(8/!.5?_R(&T`""M0__P@;0`(0I!@``$&2&W_]DAM``Q.N@$L4$\K
M0/_R(&T`#`P0`#IF$E*M``Q!^0```.8K2/_\8```V"!M__(,$``O9AX@;?_\
M2J@`"&8&<`!.74YU(&W__"MH``C__&```+`@;?_\#&C__P`69@1P`&#>(&W_
M_$AH`").NOP"6$\K0/_X8%H@;?_X2F@`%F="(FW_^"\I``Q.N0``(_)83S(M
M__9(P;"!9B@P+?_V2,`O`"\M__(@;?_X+R@`#$ZZ^]Y/[P`,2H!G""MM__C_
M_&`4+RW_^$ZZ^X983RM`__A*K?_X9J!*K?_X9B0@;0`,2A!F""!M``@@K?_\
M2JT`$&<((&T`$""M__)P`&``_T(@;0`,2A!G"$JM__QF`/[N2JT`$&<((&T`
M$""M__(@;?_\(FT`""*H``@@+?_\8`#_$$Y5__Q(YP@@(&T`""10>``K2O_\
M2A)G*@P2`"]F!E**4D1@'DH29Q(,$@`O9PP,$@`Z9P92BE)$8.H,$@`O9@)2
MBB!M``@@BB!M``PPA"`M__Q,WP003EU.=4Y5```@+0`(8```_$'Z`:0@"$Y=
M3G5!^@&>(`A@]$'Z`9X@"&#L0?H!GR`(8.1!^@&@(`A@W$'Z`9T@"&#40?H!
MFR`(8,Q!^@&9(`A@Q$'Z`98@"&"\0?H!FR`(8+1!^@&?(`A@K$'Z`9P@"&"D
M0?H!GB`(8)Q!^@&@(`A@E$'Z`9\@"&",0?H!H2`(8(1!^@&>(`A@`/]\0?H!
MG"`(8`#_<D'Z`9L@"&``_VA!^@&<(`A@`/]>0?H!G2`(8`#_5$'Z`9H@"&``
M_TI!^@&8(`A@`/]`0?H!FB`(8`#_-D'Z`9L@"&``_RQ!^@&?(`A@`/\B0?H!
MFR`(8`#_&$'Z`9D@"&``_PY;@&<`_P)7@&<`_W93@&>T78!G`/]^4X!G`/]6
M4X!GD%.`9ZI3@&<`_V!3@&>J4X!G`/]J4X!G`/]`4X!G`/\24X!G`/\$4X!G
M`/\64X!G`/\(4X!GC%.`9P#_3%.`9P#_"%6`9P#_5)"\````,V<`_KA;@&<`
M_KJ0O````XMG`/]LD+P````*9P#^AE.`9P#^B%.`9P#^BE.`9P#^G%.`9P#^
MGF``_U!@`/YB1$E%`$]014XM4E<`3U!%3BU/3$0`3U!%3BU.15<`4D5!1`!7
M4DE410!#3$]310!3145+`$5804U)3D4@3D585`!%6$%-24Y%($]"2@!)3D9/
M`$1)4TL@24Y&3P!005)%3E1$25(`1$5,151%`$-214%4141)4@!,3T-+`$15
M4$Q/0TL`1E)%14Q/0TL`4T544%)/5$5#5`!3151#3TU-14Y4`%)%3D%-10!)
M3DA)0DE4`%)%3D%-12!$25-+`$U/4D4@0T%#2$4`5T%)5"!&3U(@0TA!4@!&
M3%532`!205=-3T1%`"TM+2TM+2TM+55.2TY/5TXM+2TM+2TM``!.5?_\0J=.
MN0``)?Q83RM`__Q"IT*G3KD``"324$\CP````+9(>!``0?H"KB`(Y(`O`"!M
M__P2*``)2(%(P5*!+P%(>@!`3KD``"0\3^\`$"\Y````MDZY```FR%A/+SD`
M``"V3KD``"9&6$](>@`=3KH`MEA/2'H`/$ZZ`*Q83TY=3G5$159?1$(`1&5B
M=6=G97(@<G5N;FEN9R!6,2XQ,"P@,B!.;W9E;6)E<B`Q.3@W"@!7;W)K<R!W
M:71H(%=/4DM"14Y#2"$*``!.5?_L2KD```"R9TQ";?_^2&W_["\Y````LDZY
M```FCE!/+SD```"V3KD``";(6$\O.0```+9.N0``)D983R\Y````MDZY```E
M:EA/2'@`,DZY```D8%A/3EU.=4Y5_OQ*N0```+)G``"B2GD```"89@``F"\M
M`"PO+0`H+RT`)"\M`"`O+0`<+RT`&"\M`!0O+0`0+RT`#"\M``A(;?\`3KD`
M`![L3^\`+$AY``$``4AM_P!.N0``(_)83R!`2&@`%4ZY```EUE!/*T#^_$AM
M_P!.N0``(_)83U*`(&W^_#%``!)(;?\`(&W^_$AH`!1.N0``'MQ03R\M_OPO
M.0```+).N0``)HY03TY=3G5.5?_V0J="ITZY```DTE!/(\````"R2'@#[DAZ
M`,!.N0``)&Y03RM`__9(>0```-(O.0```+9.N0``)HY03R\Y````LDZY```F
MR%A/+SD```"R3KD``"9&6$\K0/_\(&W__#MH`!+_^DIM__IG/%-M__HP+?_Z
M2,`O`"!M__Q(:``4+RW_]DZY```DA$_O``PP+?_Z2,`@0$AH`!4O+?_\3KD`
M`"8@4$]@E"\M__9.N0``)"A83R\Y````LDZY```E:EA/2'D```#2+SD```"V
M3KD``":.4$].74YU8V]N.C`O,"\V-#`O,3`P+V1E8G5G=VEN9&]W`$YQ3G%.
M<4YQ3G%(YS\^3KK^^$S??/Q.=4*!8`02+P`/(&\`!"`O``@"+P`#``MF#`(O
M``,`!V8$8!`0P5'(__R0O``!``!J\DYUY(A*@6<2'T$`#C(O``Y(03(O``Y@
M`B#!4<C__)"\``$``&KR3G4P/'__8`0P+P`.4T!K%"!O``0B;P`(L0EF#%-(
M2AA7R/_V<`!.=6,$<`%.=7#_3G4@;P`$(`@B;P`($-EF_$YU3E4``$CG.``C
M[0`(````E$AM`!`O+0`,2'H`($ZY```?WD_O``PH`"!Y````E$(0(`1,WP`<
M3EU.=4Y5``!(YS``('D```"44KD```"4$"T`"Q"`2(!(P,"\````_TS?``Q.
M74YU3E4``$CG."`D;0`0#*T````$`!1F""!M``@H$&`42JT`#&\((&T`""@0
M8`8@;0`(*!!"K0`42JT`#&P21*T`#$J$;`I$A"M\`````0`4(BT`#"`$3KD`
M`".,0?D`````4XH4L`@`(BT`#"`$3KD``".8*`!FV$JM`!1G!E.*%+P`+2`*
M3-\$'$Y=3G5.5?\42.<X,"1M``@F;0`,0JW_^"MM`!#__"!+4HL0$$B`2,`H
M`&<``SRXO````"5F``,60BW_(BM\`````?_T*WP````@__`K?```)Q#_["!+
M4HL0$$B`2,`H`+"\````+6800JW_]"!+4HL0$$B`2,`H`+B\````,&84*WP`
M```P__`@2U*+$!!(@$C`*`"XO````"IF&B!M__Q8K?_\*U#_Z"!+4HL0$$B`
M2,`H`&`X0JW_Z&`D<@H@+?_H3KD``"0$T(20O````#`K0/_H($M2BQ`02(!(
MP"@`0?D````3"#```D@`9LZXO````"YF9B!+4HL0$$B`2,`H`+"\````*F8:
M(&W__%BM__PK4/_L($M2BQ`02(!(P"@`8#A"K?_L8"1R"B`M_^Q.N0``)`30
MA)"\````,"M`_^P@2U*+$!!(@$C`*`!!^0```!,(,``"2`!FSBM\````!/_D
MN+P```!L9A8@2U*+$!!(@$C`*``K?`````3_Y&`4N+P```!H9@P@2U*+$!!(
M@$C`*``@!&```((K?`````C_X&`<*WP````*_^!@$BM\````$/_@8`@K?/__
M__;_X"\M_^1(;?\B+RW_X"\M__Q.NOVD3^\`$"M`_]P@+?_DT:W__&!<(&W_
M_%BM__PK4/_<+RW_W$ZY```C\EA/*T#_Y&!*(&W__%BM__PH$$'M_R$K2/_<
M$(1@*)"\````8V?B4X!GDI"\````"V<`_VQ9@&>R58!G`/]L5X!G`/]P8,Q!
M[?\BD>W_W"M(_^0@+?_DL*W_[&\&*VW_[/_D2JW_]&=P(&W_W`P0`"UG"B)M
M_]P,$0`K9C0,K0```##_\&8J4ZW_Z"!M_]Q2K?_<$!!(@$C`+P!.DEA/L+S_
M____9@IP_TS?#!Q.74YU8!@O+?_P3I)83["\_____V8$</]@XE*M__@@+?_H
M4ZW_Z+"M_^1NVD*M_^!@)"!M_]Q2K?_<$!!(@$C`+P!.DEA/L+S_____9@1P
M_V"J4JW_X"!M_]Q*$&<*("W_X+"M_^QMRB`M_^#1K?_X2JW_]&8J8!I(>``@
M3I)83["\_____V8&</]@`/]P4JW_^"`M_^A3K?_HL*W_Y&[88!@O!$Z26$^P
MO/____]F!G#_8`#_2%*M__A@`/RX("W_^&``_SA(YT@`0H1*@&H$1(!21$J!
M:@9$@0I$``%A/DI$9P)$@$S?`!)*@$YU2.=(`$*$2H!J!$2`4D1*@6H"1(%A
M&B`!8-@O`6$2(`$B'TJ`3G4O`6$&(A]*@$YU2.<P`$A!2D%F($A!-@$T`$)`
M2$"`PR(`2$`R`H+#,`%"04A!3-\`#$YU2$$F`2(`0D%(04A`0D!T#]"`TX&V
M@6($DH-20%'*__),WP`,3G4@;P`$(`A*&&;\D<`@"%.`3G5(YW``-`'$P"8!
M2$/&P$A#0D/4@TA`P,%(0$)`T(),WP`.3G5.^0``)"XB+P`$+'D```"J3N[_
MW"\$3.\`'@`(+'D```"J3J[_=B@?3G4B+P`$+'D```"J3N[_0"(O``0L>0``
M`*I.[O\Z3OD``"1T3.\`!@`$+'D```"J3N[_XD[Y```DBDSO``X`!"QY````
MJD[N_]!,[P,```0L>0```*9.[O\*3OD``"2P(F\`!"QY````ID[N_F),[P,`
M``0@+P`,+'D```"F3N[]D$Y5``!(YS@@2'C__TZY```ER%A/*`"PO/____]F
M"G``3-\$'$Y=3G5(>0`!``%(>``B3KD``"764$\D0$J`9@XO!$ZY```F.%A/
M<`!@TB5M``@`"A5M``\`"15\``0`"$(J``X51``/0J=.N0``)?Q83R5``!!*
MK0`(9PPO"DZY```ENEA/8`Q(:@`43KD``"9:6$\@"F"(3E4``$CG,"`D;0`(
M2JH`"F<*+PI.N0``)KI83Q5\`/\`""5\_____P`4<``0*@`/+P!.N0``)CA8
M3TAX`"(O"DZY```F(%!/3-\$#$Y=3G4B;P`$+'D```"F3N[^GB`O``0L>0``
M`*9.[OZV3OD``"7<3.\``P`$+'D```"F3N[_.DSO`P``!"QY````ID[N_Q!.
M^0``)@(B;P`$+'D```"F3N[^VD[Y```F%BQY````ID[N_WQ.^0``)B8B;P`$
M("\`""QY````ID[N_RX@+P`$+'D```"F3N[^L$[Y```F3"!O``0L>0```*9.
M[OZ,(&\`!""(6)!"J``$(4@`"$YU3OD``"9R+'D```"F(F\`!"`O``A.[OW8
M+'D```"F3N[_=DSO`P``!"QY````ID[N_I(@;P`$+'D```"F3N[^_B)O``0L
M>0```*9.[O\$(F\`!"QY````ID[N_IA.^0``)LX@;P`$+'D```"F3N[^@```
M`^P```"&`````````#(```!"````6@```&P```%B```!<````8(```&8```!
MI@```;P```'@```"(@```C0```+$```"X````[(```/H```$:```!)8```5L
M```%O```!?X```80```&7```!H(```:8```&W@``!NP```@X```(Y```"/(`
M``E4```)8@``":(```K^```-8```#7(```V"```-C@``#:8```WR```.-@``
M#HH```ZL```.O```#L8```[6```.X@``#P(```\4```00@``$$H``!"0```1
M%```$K@``!,&```3+@``$V```!00```4)```%#H``!1&```46@``%'```!1\
M```4C```%+8``!3T```5!```%1X``!4R```58@``%7P``!60```5J@``%;X`
M`!7.```6!@``%AP``!:H```6N@``%[@``!MR```;@@``&[(``!O"```;T```
M'%(``!Q@```<;@``''P``!R(```<V```'.P``!SZ```="@``'2@``!TZ```=
M3@``'60``!U\```=B@``'9@``!W,```=Y@``'?0``!X"```>%@``'PH``!^H
M```?P```()X``"$,```AU```)"H``"1P```DA@``)*P``"3@```E!@``)18`
M`"4^```E4@``)6```"6````EG@``):P``"78```E_@``)A(``"8B```F2```
M)FX``";*````=`````$````*````$````!8````<````)@```#H```!*````
M4````&(```"`````B@```+````"V````Q````-P```#\```!`@```4````%<
M```!:@```7P```&,```!D@```:````'$```!T@```>X```'X```"&````BH`
M``+2```"[````^(```/P```$````!DH```<&```'%@``";8```G(```)[```
M"?H```JD```+&```"R@```R6```,I@``$%```!!@```0<```$(```!"D```0
MK@``$.(``!#J```1#@``$IH``!+,```2T@``%*@``!86```63@``%EX``!<,
M```73```&XH``!N\```;R@``'#P``!Q,```<6@``'&@``!QV```<F```'*(`
M`!TT```=5@``'7```!UV```=A```'9(``!W\```>"@``'A```![X```?%@``
M'S```!\V```?K@``(+P``"$J```D-```)$8``"18```D9@``)'P``"22```D
MH@``)+8``"3*```EP```)<X``"7D```E]```)@@``"88```F,```)CX``"92
M```F=```)H8``":6```FI```)K(``";````FU`````````/R```#Z@```"4P
M,3(S-#4V-S@Y86)C9&5F````("`@("`@("`@,#`P,#`@("`@("`@("`@("`@
M("`@(""00$!`0$!`0$!`0$!`0$!`#`P,#`P,#`P,#$!`0$!`0$`)"0D)"0D!
M`0$!`0$!`0$!`0$!`0$!`0$!`4!`0$!`0`H*"@H*"@("`@("`@("`@("`@("
<`@("`@("0$!`0"`````#\@```^L````!```#\@H*
`
end
!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'dos.h'" '(3268 characters)'
if test -f 'dos.h'
then
	echo shar: "will not over-write existing file 'dos.h'"
else
cat << \!Funky!Stuff! > 'dos.h'

/*
 *  DOS.H
 */

#ifdef NOTDEF
#include "exec/types.h"
#include "exec/memory.h"
#include "libraries/dos.h"
#include "libraries/dosextens.h"
#include "libraries/filehandler.h"
#endif NOTDEF

/*
 *  ACTIONS which do not exist in dosextens.h but which indeed exist on
 *  the Amiga.
 */

#define ACTION_OPENRW	    1004
#define ACTION_OPENOLD	    1005
#define ACTION_OPENNEW	    1006
#define ACTION_CLOSE	    1007
#define ACTION_SEEK	    1008
#define ACTION_RAWMODE	    994
#define ACTION_MORECACHE    18
#define ACTION_FLUSH	    27

#define CTOB(x) (void *)(((long)(x))>>2)    /*	BCPL conversion */
#define BTOC(x) (void *)(((long)(x))<<2)

#define bmov(ss,dd,nn) CopyMem(ss,dd,nn)    /*	my habit	*/

#define DOS_FALSE   0
#define DOS_TRUE    -1

#define RAMFILE     struct _RAMFILE	    /*	less restrictive typedefs   */
#define FENTRY	    struct _FENTRY
#define LOCKLINK    struct _LL
#define MYFH	    struct _MYFH

typedef unsigned char	ubyte;		    /*	unsigned quantities	    */
typedef unsigned short	uword;
typedef unsigned long	ulong;

typedef struct Interrupt	INTERRUPT;
typedef struct Task		TASK;
typedef struct FileLock 	LOCK;	    /*	See LOCKLINK	*/
typedef struct FileInfoBlock	FIB;
typedef struct DosPacket	PACKET;
typedef struct Process		PROC;
typedef struct DeviceNode	DEVNODE;
typedef struct DeviceList	DEVLIST;
typedef struct DosInfo		DOSINFO;
typedef struct RootNode 	ROOTNODE;
typedef struct FileHandle	FH;
typedef struct MsgPort		PORT;
typedef struct Message		MSG;
typedef struct MinList		LIST;
typedef struct MinNode		NODE;
typedef struct DateStamp	STAMP;
typedef struct InfoData 	INFODATA;
typedef struct DosLibrary	DOSLIB;

#define FILE_DIR    1
#define FILE_FILE   -1


RAMFILE {
    NODE    node;
    RAMFILE *parent;
    char    *name;
    char    *comment;
    short   flags;
    short   type;	/*  -1 = file,	1 = dir, 0 = dummy entry    */
    short   locks;	/*  <0:exclusive 0:none >0:shared	    */
    ulong   protection;
    ulong   bytes;
    LIST    list;	/*  list of FENTRY's or RAMFILE's   */
    STAMP   date;
};

FENTRY {
    NODE    node;
    ubyte   *buf;
    ulong   bytes;
};

/*
 *  We use this structure to link locks together in a list for internal
 *  usage.  I could have use the link field in the lock structure as a
 *  real linked list, but didn't want to have to sequentially search the
 *  list to remove a node.
 *
 *  NOTE:   You CANNOT simply extend the FileLock (LOCK) structure.  Some
 *  programs assume it is sizeof(LOCK) big and break.  I found this out the
 *  hard way.
 */

LOCKLINK {
    NODE    node;
    LOCK    *lock;
};

MYFH {
    NODE    node;
    RAMFILE *file;	/*  file header     */
    FENTRY  *fentry;
    long    base;	/*  base of FENTRY	*/
    long    offset;	/*  offset into FENTRY	*/
};

/*
 *  (void *)  in Aztec C means 'pointer to anything'.  I use it
 *  extensively.
 */

extern void *AbsExecBase;

extern void *AllocMem(), *RemHead(), *CreatePort(), *GetMsg();
extern void *FindTask(), *Open(), *OpenLibrary();

extern void   *dosalloc(), *NextNode(), *GetHead();
extern void   freedata(), freeramfile(), ramunlock(), btos(), returnpacket();
extern LOCK *ramlock();
extern RAMFILE *searchpath(), *createramfile(), *getlockfile();

extern char *getpathelement();
extern char *typetostr();

!Funky!Stuff!
fi  # end of overwriting check
echo shar: "extracting 'mountlist'" '(89 characters)'
if test -f 'mountlist'
then
	echo shar: "will not over-write existing file 'mountlist'"
else
cat << \!Funky!Stuff! > 'mountlist'

TEST:	   Handler = ram:device
	   Stacksize = 2048
	   Priority = 5
	   GlobVec  = 1
#

!Funky!Stuff!
fi  # end of overwriting check
exit 0
#	End of shell archive