/*-
 * $Id: pack.c,v 1.3 90/02/03 17:02:05 Rhialto Exp $
 * $Log:	pack.c,v $
 * Revision 1.3  90/02/03  17:02:05  Rhialto
 * Add error checking wrt dosalloc()
 * 
 * Revision 1.2  89/12/17  23:06:54  Rhialto
 * Add ACTION_SET_PROTECT
 *
 * Revision 1.1  89/12/17  19:53:24  Rhialto
 * Initial revision
 *
 *
 *  Originally:
 *
 *	DOSDEVICE.C	    V1.10   2 November 1987
 *
 *	EXAMPLE DOS DEVICE DRIVER FOR AZTEC.C	PUBLIC DOMAIN.
 *
 *	By Matthew Dillon.
 *
 *  This has been stripped and refilled with messydos code
 *  by Olaf Seibert.
 *
 *  This code is (C) Copyright 1989 by Olaf Seibert. All rights reserved. May
 *  not be used or copied without a licence.
 *
 *  Please note that we are NOT pure, so if you wish to mount
 *  multiple MSDOS units, you must use different copies of this driver.
 *
 *  This file forms the interface between the actual handler code and all
 *  AmigaDOS requirements. It shields it from ugly stuff like BPTRs, BSTRs,
 *  FileLocks, FileHandles and VolumeNodes (in the form of DeviceLists).
 *  Also, most protection against non-inserted disks is done here.
-*/

#include "dos.h"
#include "han.h"

#ifdef DEBUG
#   define	debug(x)  dbprintf x
#else
#   define	debug(x)
#endif

/*
 * 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.
 */

PORT	       *DosPort;	/* Our DOS port... */
DEVNODE        *DevNode;	/* Our DOS node.. created by DOS for us */
DEVLIST        *VolNode;	/* Device List structure for our volume
				 * node */

void	       *SysBase;	/* EXEC library base */
DOSLIB	       *DOSBase;	/* DOS library base for debug process */
long		PortMask;	/* The signal mask for our DosPort */
long		WaitMask;	/* The signal mask to wait for */
short		DiskChanged;	/* Set by disk change interrupt */
short		Inhibited;	/* Are we inhibited (ACTION_INHIBIT)? */
long		UnitNr; 	/* From */
char	       *DevName;	/*   the */
ulong		DevFlags;	/*     mountlist */
PACKET	       *DosPacket;	/* For the SystemRequest pr_WindowPtr */

void ChangeIntHand(), DiskChange();
void NewVolNodeName(), NewVolNodeDate();

struct Interrupt ChangeInt = {
    { 0 },			/* is_Node */
    0,				/* is_Data */
    ChangeIntHand,		/* is_Code */
};

/*
 * 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
messydoshandler()
{
    register PACKET *packet;
    MSG 	   *msg;
    byte	    notdone;
    long	    OpenCount;	    /* How many open files/locks there are */

    /*
     * Initialize all global variables.  SysBase MUST be initialized
     * before we can make Exec calls.  AbsExecBase is a library symbol
     * referencing absolute memory location 4.
     */

    SysBase = AbsExecBase;
    DOSBase = OpenLibrary("dos.library", 0L);

#ifdef DEBUG
    /*
     * Initialize debugging code as soon as possible. Only SysBase and
     * DOSBase are required.
     */

    dbinit();
#endif				/* DEBUG */

    DosPort = &((struct Process *)FindTask(NULL))->pr_MsgPort;

    WaitPort(DosPort);      /* Get Startup Packet  */
    msg = GetMsg(DosPort);
    packet = (PACKET *) msg->mn_Node.ln_Name;


    DevNode = BTOC(PArg3);
    {
	struct FileSysStartupMsg *fssm;
	ulong *environ;

	DevName = "messydisk.device";
	UnitNr = 0;
	DevFlags = 0;

	MaxCache = 5;
	BufMemType = MEMF_PUBLIC;
	Disk.nsides = MS_NSIDES;
	Disk.spt = MS_SPT;
	Disk.bps = MS_BPS;
	Disk.lowcyl = 0;

	if (fssm = (struct FileSysStartupMsg *)BTOC(DevNode->dn_Startup)) {
				    /* Same as BTOC(packet->dp_Arg2) */
	    UnitNr = fssm->fssm_Unit;
	    if (fssm->fssm_Device)
		DevName = (char *)BTOC(fssm->fssm_Device)+1;
	    DevFlags = fssm->fssm_Flags;

	    if (environ = BTOC(fssm->fssm_Environ)) {
		debug(("environ size %ld\n", environ[0]));
#define get(xx,yy)  if (environ[0] >= yy) xx = environ[yy];

		get(MaxCache, DE_NUMBUFFERS);
		get(BufMemType, DE_MEMBUFTYPE);
		get(Disk.nsides, DE_NUMHEADS);
		get(Disk.spt, DE_BLKSPERTRACK);
		get(Disk.bps, DE_SIZEBLOCK);
		Disk.bps *= 4;
		debug(("Disk.bps %d\n", Disk.bps));
		get(Disk.lowcyl, DE_LOWCYL);
#undef get
	    }
	}
	Disk.lowcyl *= (long)MS_BPS * Disk.spt * Disk.nsides;
    }

    if (DOSBase && HanOpenUp()) {
	/*
	 * Loading DevNode->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.
	 */

	DevNode->dn_Task = DosPort;

	PRes1 = DOSTRUE;
	PRes2 = 0;
    } else {		    /* couldn't open dos.library  */
	PRes1 = DOSFALSE;
	PRes2 = ERROR_NO_FREE_STORE;	/* no better message available */
	returnpacket(packet);
	goto exit;		/* exit process    */
    }
    returnpacket(packet);

    /* Initialize some more global variables	*/

    PortMask = 1L << DosPort->mp_SigBit;
    VolNode = NULL;
    OpenCount = 0;
    Inhibited = 0;

    /* Get the first real packet       */
    WaitPort(DosPort);
    msg = GetMsg(DosPort);
    notdone = 1;
    WaitMask = PortMask | (1L << DiskReplyPort->mp_SigBit);
    TDAddChangeInt(&ChangeInt);
    DiskInserted(WhichDiskInserted());

    goto entry;

    /*
     * Here begins the endless loop, waiting for requests over our message
     * port and executing them.  Since requests are sent over the message
     * port in our device and volume nodes, we must not use our Process
     * message port for this: this precludes being able to call DOS
     * functions ourselves.
     */

top:
    for (notdone = 1; notdone;) {
	Wait(WaitMask);
	if (DiskChanged)
	    DiskChange();
	while (msg = GetMsg(DosPort)) {
	    byte	    buf[256];	/* Max length of BCPL strings is
					 * 255 + 1 for \0. */

    entry:
	    if (DiskChanged)
		DiskChange();
	    packet = (PACKET *) msg->mn_Node.ln_Name;
	    PRes1 = DOSFALSE;
	    PRes2 = 0;
	    error = 0;
	    debug(("Packet: %3ld %08lx %08lx %08lx %10s\n",
		     PType, PArg1, PArg2, PArg3, typetostr(PType)));

	    DosPacket = packet; 	/* For the System Requesters */
	    switch (PType) {
	    case ACTION_DIE:		/* attempt to die?  */
		notdone = 0;		/* try to die	 */
		break;
	    case ACTION_CURRENT_VOLUME: /* -		      VolNode,UnitNr */
		PRes1 = (long) CTOB(VolNode);
		PRes2 = UnitNr;
		break;
	    case ACTION_LOCATE_OBJECT:	/* Lock,Name,Mode	Lock	     */
		{
		    register struct FileLock *newlock;
		    struct FileLock *lock;
		    struct MSFileLock *msfl;
		    long	    lockmode;

		    lock = BTOC(PArg1);
		    if (CheckRead(lock))
			break;
		    btos(PArg2, buf);
		    if ((lockmode = PArg3) != EXCLUSIVE_LOCK)
			lockmode = SHARED_LOCK;
		    msfl = MSLock(lock ? lock->fl_Key : NULL,
				  buf,
				  lockmode);
		    if (msfl) {
			if (newlock = NewFileLock(msfl, lock)) {
			    newlock->fl_Access = lockmode;
			    PRes1 = (long) CTOB(newlock);
			    OpenCount++;
			} else
			    MSUnLock(msfl);
		    }
		}
		break;
	    case ACTION_RENAME_DISK:	/* BSTR:NewName 	   Bool      */
		if (CheckWrite(NULL))
		    break;
		btos(PArg1, buf);
		buf[31] = '\0';
		if (PRes1 = MSRelabel(buf))
		    NewVolNodeName();
		break;
	    case ACTION_FREE_LOCK:	/* Lock 		   Bool      */
		{
		    struct FileLock *lock;
		    struct MSFileLock *msfl;

		    PRes1 = DOSTRUE;
		    lock = BTOC(PArg1);
		    if (lock == NULL)
			break;

		    msfl = (struct MSFileLock *)lock->fl_Key;
		    FreeFileLock(lock); /* may remove last lock on volume */
		    MSUnLock(msfl);     /* may call MayFreeVolNode */
		    OpenCount--;
		}
		break;
	    case ACTION_DELETE_OBJECT:	/* Lock,Name		Bool	     */
		{
		    struct FileLock *lock;

		    lock = BTOC(PArg1);
		    if (CheckWrite(lock))
			break;
		    btos(PArg2, buf);
		    PRes1 = MSDeleteFile(lock ? lock->fl_Key : NULL,
					 buf);
		}
		break;
	    case ACTION_RENAME_OBJECT:	/* SLock,SName,DLock,DName   Bool    */
		{
		    struct FileLock *slock, *dlock;
		    char	     buf2[256];

		    slock = BTOC(PArg1);
		    dlock = BTOC(PArg3);
		    if (CheckWrite(slock) || CheckWrite(dlock))
			break;
		    btos(PArg2, buf);
		    btos(PArg4, buf2);
		    PRes1 = MSRename(slock ? slock->fl_Key : NULL,
				     buf,
				     dlock ? dlock->fl_Key : NULL,
				     buf2);
		}
		break;
	    case ACTION_MORECACHE:	/* #BufsToAdd		   Bool      */
		if ((MaxCache += (short) PArg1) <= 0) {
		    MaxCache = 1;
		} else
		    PRes1 = DOSTRUE;
		debug(("Now %d cache sectors\n", MaxCache));
		break;
	    case ACTION_COPY_DIR:	/* Lock 		   Lock      */
		{
		    register struct FileLock *newlock;
		    struct FileLock *lock;
		    struct MSFileLock *msfl;

		    lock = BTOC(PArg1);

		    msfl = MSDupLock(lock ? lock->fl_Key : NULL);

		    if (msfl) {
			if (newlock = NewFileLock(msfl, lock)) {
			    newlock->fl_Access =
				lock ? lock->fl_Access : SHARED_LOCK;
			    PRes1 = (long) CTOB(newlock);
			    OpenCount++;
			} else
			    MSUnLock(msfl);
		    }
		}
		break;
	    case ACTION_SET_PROTECT:	/* -,Lock,Name,Mask	   Bool      */
		{
		    struct FileLock *lock;

		    lock = BTOC(PArg2);
		    if (CheckWrite(lock))
			break;
		    btos(PArg3, buf);
		    PRes1 = MSSetProtect(lock ? lock->fl_Key : NULL, buf, PArg4);
		}
		break;
	    case ACTION_CREATE_DIR:	/* Lock,Name		Lock	     */
		{
		    register struct FileLock *newlock;
		    struct FileLock *lock;
		    struct MSFileLock *msfl;

		    lock = BTOC(PArg1);
		    if (CheckWrite(lock))
			break;
		    btos(PArg2, buf);

		    msfl = MSCreateDir(lock ? lock->fl_Key : NULL, buf);

		    if (msfl) {
			if (newlock = NewFileLock(msfl, lock)) {
			    newlock->fl_Access = SHARED_LOCK;
			    PRes1 = (long) CTOB(newlock);
			    OpenCount++;
			} else
			    MSUnLock(msfl);
		    }
		}
		break;
	    case ACTION_EXAMINE_OBJECT: /* Lock,Fib	       Bool	     */
		{
		    struct FileLock *lock;

		    lock = BTOC(PArg1);
		    if (CheckRead(lock))
			break;
		    PRes1 = MSExamine(lock ? lock->fl_Key : NULL, BTOC(PArg2));
		}
		break;
	    case ACTION_EXAMINE_NEXT:	/* Lock,Fib	       Bool	     */
		{
		    struct FileLock *lock;

		    lock = BTOC(PArg1);
		    if (CheckRead(lock))
			break;
		    PRes1 = MSExNext(lock ? lock->fl_Key : NULL, BTOC(PArg2));
		}
		break;
	    case ACTION_DISK_INFO:	/* InfoData	       Bool:TRUE     */
		PRes1 = MSDiskInfo(BTOC(PArg1));
		break;
	    case ACTION_INFO:	/* Lock,InfoData	       Bool:TRUE     */
		if (CheckRead(BTOC(PArg1)))
		    break;
		PRes1 = MSDiskInfo(BTOC(PArg2));
		break;
	    case ACTION_FLUSH:		/* writeout bufs, disk motor off     */
		MSUpdate(1);
		break;
/*	    case ACTION_SET_COMMENT:	/* -,Lock,Name,Comment	   Bool      */
	    case ACTION_PARENT: /* Lock 		       ParentLock    */
		{
		    register struct FileLock *newlock;
		    struct FileLock *lock;
		    struct MSFileLock *msfl;
		    long mode;

		    lock = BTOC(PArg1);

		    msfl = MSParentDir(lock ? lock->fl_Key : NULL);

		    if (msfl) {
			if (newlock = NewFileLock(msfl, lock)) {
			    newlock->fl_Access = SHARED_LOCK;
			    PRes1 = (long) CTOB(newlock);
			    OpenCount++;
			} else
			    MSUnLock(msfl);
		    }
		}
		break;
	    case ACTION_INHIBIT:	/* Bool 		   Bool      */
		if (Inhibited = PArg1) {
		    DiskRemoved();
		} else { /* Fall through to ACTION_DISK_CHANGE: */
	    case ACTION_DISK_CHANGE:	/* ?			   ?	     */
		    DiskChange();
		}
		PRes1 = DOSTRUE;
		break;
	    case ACTION_SET_DATE: /* -,Lock,Name,CPTRDateStamp	   Bool      */
		{
		    struct FileLock *lock;

		    lock = BTOC(PArg2);
		    if (CheckWrite(lock))
			break;
		    btos(PArg3, buf);
		    PRes1 = MSSetDate(lock ? lock->fl_Key : NULL,
				      buf,
				      PArg4);
		}
		break;
	    case ACTION_READ:	/* FHArg1,CPTRBuffer,Length	  ActLength  */
		if (CheckRead(NULL)) {
		    PRes1 = -1;
		} else
		    PRes1 = MSRead(PArg1, PArg2, PArg3);
		break;
	    case ACTION_WRITE:	/* FHArg1,CPTRBuffer,Length	  ActLength  */
		if (CheckWrite(NULL)) {
		    PRes1 = -1;
		} else
		    PRes1 = MSWrite(PArg1, PArg2, PArg3);
		break;
	    case ACTION_OPENNEW:	/* FileHandle,Lock,Name    Bool      */
		{
		    struct MSFileHandle *msfh;
		    struct FileHandle *fh;
		    struct FileLock *lock;

		    if (CheckWrite(BTOC(PArg2)))
			break;
	    case ACTION_OPENRW: 	/* FileHandle,Lock,Name    Bool      */
	    case ACTION_OPENOLD:	/* FileHandle,Lock,Name    Bool      */

		    fh = BTOC(PArg1);
		    lock = BTOC(PArg2);
		    if (CheckRead(lock))
			break;
		    btos(PArg3, buf);
		    debug(("'%s' ", buf));
		    msfh = MSOpen(lock ? lock->fl_Key : NULL,
				  buf,
				  PType);
		    if (msfh) {
			fh->fh_Arg1 = (long) msfh;
			PRes1 = DOSTRUE;
			OpenCount++;
		    }
		}
		break;
	    case ACTION_CLOSE:	/* FHArg1			  Bool:TRUE  */
		MSClose(PArg1);
		PRes1 = DOSTRUE;
		OpenCount--;
		break;
	    case ACTION_SEEK:	/* FHArg1,Position,Mode 	 OldPosition */
		if (CheckRead(NULL)) {
		    PRes1 = -1;
		} else
		    PRes1 = MSSeek(PArg1, PArg2, PArg3);
		break;
		/*
		 * A few other packet types which we do not support
		 */
/*	    case ACTION_WAIT_CHAR:	/* Timeout, ticks	   Bool      */
/*	    case ACTION_RAWMODE:	/* Bool(-1:RAW 0:CON)      OldState  */
	    default:
		error = ERROR_ACTION_NOT_KNOWN;
		break;
	    } /* end switch */
	    if (packet) {
		if (error) {
		    debug(("ERR=%d\n", error));
		    PRes2 = error;
		}
#ifdef DEBUG
		else {
		    debug(("RES=%06lx\n", PRes1));
		}
#endif
		returnpacket(packet);
		DosPacket = NULL;
	    }
#ifdef DEBUG
	    else {
		debug(("NOREP\n"));
	    }
#endif
	} /* end while (GetMsg()) */

	/*
	 *  Now check for an other cause of events: timer IO.
	 *  Unfortunately we cannot be sure that we always get a signal
	 *  when the timeout has elapsed, since the same message port is
	 *  used for other IO.
	 */
	if (CheckIO(TimeIOReq)) {   /* Timer finished? */
	    debug(("TimeIOReq is finished\n"));
	    if (DelayState != DELAY_OFF) {
		MSUpdate(0);    /* Also may switch off motor */
	    }
	}
    } /* end for (;notdone) */

#ifdef DEBUG
    debug(("Can we remove ourselves? "));
    Delay(50L);                 /* I wanna even see the debug message! */
#endif				/* DEBUG */
    Forbid();
    if (OpenCount || packetsqueued()) {
	Permit();
	debug((" ..  not yet!\n"));
	goto top;		/* sorry... can't exit     */
    }
    debug((" .. yes!\n"));

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

    DevNode->dn_Task = NULL;
    TDRemChangeInt();
    DiskRemoved();
    HanCloseDown();
    debug(("HanCloseDown returned. dbuninit in 2 seconds:\n"));

    /*
     * Remove debug window, closedown, fall of the end of the world.
     */
exit:
#ifdef DEBUG
    Delay(100L);                /* This is dangerous! */
    dbuninit();
#endif				/* DEBUG */

#if 1
    UnLoadSeg(DevNode->dn_SegList);     /* This is real fun. We are still */
    DevNode->dn_SegList = NULL; 	/* Forbid()den, fortunately */
#endif

    CloseLibrary(DOSBase);

    /* Fall off the end of the world. Implicit Permit(). */
}

void
ChangeIntHand()
{
/* INDENT OFF */
#asm
    move.l  a6,-(sp)
#endasm
    DiskChanged = 1;
    Signal(DosPort->mp_SigTask, PortMask);
#asm
    move.l  (sp)+,a6
#endasm
/* INDENT ON */
}

/*
 *  Make a new struct FileLock, for DOS use. It is put on a singly linked
 *  list, which is attached to the same VolumeNode the old lock was on.
 *
 *  Also note that we must ALWAYS be prepared to UnLock() or DupLock()
 *  any FileLocks we ever made, even if the volume in question has been
 *  removed and/or inserted into another drive with another FileSystem
 *  handler!
 *
 * 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). Also, it sometimes uses uninitialized values for the lock mode...
 */

struct FileLock *
NewFileLock(msfl, fl)
struct MSFileLock *msfl;
struct FileLock *fl;
{
    struct FileLock *newlock;
    DEVLIST *volnode;

    if (fl) {
	volnode = BTOC(fl->fl_Volume);
    } else {
	volnode = VolNode;
    }

    if (newlock = dosalloc((ulong)sizeof (*newlock))) {
	newlock->fl_Key = (ulong) msfl;
	newlock->fl_Task = DosPort;
	newlock->fl_Volume = (BPTR) CTOB(volnode);
	Forbid();
	newlock->fl_Link = volnode->dl_LockList;
	volnode->dl_LockList = (BPTR) CTOB(newlock);
	Permit();
    } else
	error = ERROR_NO_FREE_STORE;

    return newlock;
}

/*
 *  This should be called before MSUnLock(), so that it may call
 *  MayFreeVolNode() which then calls FreeVolNode(). A bit tricky,
 *  I'm sorry for that.
 */

long
FreeFileLock(lock)
struct FileLock *lock;
{
    register struct FileLock *fl;
    register struct FileLock **flp;
    DEVLIST	   *volnode;

    volnode = (DEVLIST *)BTOC(lock->fl_Volume);
    flp = (struct FileLock **) &volnode->dl_LockList;
    for (fl = BTOC(*flp); fl && fl != lock; fl = BTOC(fl->fl_Link))
	flp = (struct FileLock **)&fl->fl_Link;

    if (fl == lock) {
	*(BPTR *)flp = fl->fl_Link;
	dosfree(fl);
	return DOSTRUE;
    } else {
	debug(("Huh?? Could not find filelock!\n"));
	return DOSFALSE;
    }
}

/*
 * 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 MESSYDOS: disk, Volume node or not.
 */

DEVLIST        *
NewVolNode(name, date)
struct DateStamp *date;
char *name;
{
    DOSINFO	   *di;
    register DEVLIST *volnode;
    char	   *volname;	    /* This is my volume name */

    di = BTOC(((ROOTNODE *) DOSBase->dl_Root)->rn_Info);

    if (volnode = dosalloc((ulong)sizeof (DEVLIST))) {
	if (volname = dosalloc(32L)) {
	    volname[0] = strlen(name);
	    strcpy(volname + 1, name);      /* Make sure \0 terminated */

	    volnode->dl_Type = DLT_VOLUME;
	    volnode->dl_Task = DosPort;
	    volnode->dl_DiskType = IDDiskType;
	    volnode->dl_Name = CTOB(volname);
	    volnode->dl_VolumeDate = *date;
	    volnode->dl_MSFileLockList = NULL;

	    Forbid();
	    volnode->dl_Next = di->di_DevInfo;
	    di->di_DevInfo = (long) CTOB(volnode);
	    Permit();
	} else {
	    dosfree(volnode);
	    volnode = NULL;
	}
    } else {
	error = ERROR_NO_FREE_STORE;
    }

    return volnode;
}

/*
 *  Get the current VolNode a new name from the volume label.
 */

void
NewVolNodeName()
{
    if (VolNode) {
	register char *volname = BTOC(VolNode->dl_Name);

	strncpy(volname + 1, Disk.vollabel.de_Msd.msd_Name, 8+3);
	volname[1+8+3] = '\0';      /* Make sure \0 terminated */
	ZapSpaces(volname + 1, volname + 1 + 8+3);
	volname[0] = strlen(volname+1);
    }
}

/*
 *  Get the current VolNode a new date, from the last root directory.
 */

void
NewVolNodeDate()
{
    if (VolNode) {
	ToDateStamp(&VolNode->dl_VolumeDate,
		    Disk.vollabel.de_Msd.msd_Date,
		    Disk.vollabel.de_Msd.msd_Time);
    }
}

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

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

    debug(("FreeVolNode %08lx\n", volnode));

    if (volnode == NULL)
	return;

    dlp = &di->di_DevInfo;
    Forbid();
    for (dl = BTOC(di->di_DevInfo); dl && dl != volnode; dl = BTOC(dl->dl_Next))
	dlp = &dl->dl_Next;
    if (dl == volnode) {
	*(BPTR *) dlp = dl->dl_Next;
	dosfree(BTOC(dl->dl_Name));
	dosfree(dl);
    }
#ifdef DEBUG
    else {
	debug(("****PANIC: Unable to find volume node\n"));
    }
#endif				/* DEBUG */
    Permit();

    if (volnode == VolNode)
	VolNode = NULL;
}

/*
 *  This is also called from the real handler when the last lock on a
 *  volume is UnLock()ed, or the last file has been Close()d.
 */

int
MayFreeVolNode(volnode)
DEVLIST *volnode;
{
    if (volnode->dl_LockList == NULL) {
	FreeVolNode(volnode);
	return 1;
    }

    return 0;
}

/*
 *  Our disk has been removed. Save the FileLocks in the dl_LockList,
 *  and let the handler save its MSFileLocks in the dl_MSFileLockList field.
 *  If it has nothing to save, forget about the volume, and return
 *  DOSTRUE.
 *  There is one subtlety that MSDiskRemoved must know about:
 *  If it MSUnLock()s the last lock on the volume, the VolNode is
 *  deleted via FreeLockList().. MayFreeVolNode().. FreeVolNode().
 *  But then there is no place anymore to put NULL in, so that needs
 *  to be done first.
 */

int
DiskRemoved()
{
    debug(("DiskRemoved %08lx\n", VolNode));

    if (VolNode == NULL) {
	IDDiskType = ID_NO_DISK_PRESENT;/* really business of MSDiskRemoved */
	return DOSTRUE;
    }

    VolNode->dl_Task = NULL;
    MSDiskRemoved(&VolNode->dl_MSFileLockList);
    if (VolNode == NULL) {  /* Could happen via MSDiskRemoved() */
	return DOSTRUE;
    }
    NewVolNodeDate();       /* Fetch new date of root directory */
    VolNode = NULL;
    return DOSFALSE;
}

/*
 *  Reconstruct everything from a Volume node
 */

void
DiskInserted(volnode)
register DEVLIST	*volnode;
{
    debug(("DiskInserted %08lx\n", volnode));

    VolNode = volnode;

    if (volnode) {
	volnode->dl_Task = DosPort;
	MSDiskInserted(&volnode->dl_MSFileLockList, volnode);
	volnode->dl_MSFileLockList = NULL;
    }
}

DEVLIST *
WhichDiskInserted()
{
    char name[34];
    struct DateStamp date;
    register DEVLIST *dl = NULL;

    if (!Inhibited && IdentifyDisk(name, &date) == 0) {
	DOSINFO        *di = BTOC(((ROOTNODE *) DOSBase->dl_Root)->rn_Info);
	byte	       *nodename;
	int		namelen = strlen(name);

	for (dl = BTOC(di->di_DevInfo); dl; dl = BTOC(dl->dl_Next)) {
	    nodename = BTOC(dl->dl_Name);
	    if (nodename[0] != namelen || strncmp(nodename+1,name,namelen))
		continue;
	    if (dl->dl_VolumeDate == date)  /* Non-standard! Structure compare! */
		break;
	}

	name[31] = '\0';
	if (dl == NULL)
	    dl = NewVolNode(name, &date);
    }

    return dl;
}

void
DiskChange()
{
    debug(("DiskChange\n"));
    DiskChanged = 0;
    DiskRemoved();
    DiskInserted(WhichDiskInserted());
}

int
CheckRead(lock)
struct FileLock *lock;
{
    if (lock && BTOC(lock->fl_Volume) != VolNode)
	error = ERROR_DEVICE_NOT_MOUNTED;
    else if (IDDiskType == ID_NO_DISK_PRESENT)
	error = ERROR_NO_DISK;
    else if (IDDiskType != ID_DOS_DISK)
	error = ERROR_NOT_A_DOS_DISK;

    return error;
}

int
CheckWrite(lock)
struct FileLock *lock;
{
    if (lock && BTOC(lock->fl_Volume) != VolNode)
	error = ERROR_DEVICE_NOT_MOUNTED;
    else if (IDDiskType == ID_NO_DISK_PRESENT)
	error = ERROR_NO_DISK;
    else if (IDDiskType != ID_DOS_DISK)
	error = ERROR_NOT_A_DOS_DISK;
    else if (IDDiskState == ID_VALIDATING)
	error = ERROR_DISK_NOT_VALIDATED;
    else if (IDDiskState != ID_VALIDATED)
	error = ERROR_DISK_WRITE_PROTECTED;

    return error;
}

#ifdef DEBUG
		    /*	DEBUGGING			*/
PORT *Dbport;	    /*	owned by the debug process	*/
PORT *Dback;	    /*	owned by the DOS device driver	*/
short DBEnable;

/*
 *  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.
 */

extern void debugproc();

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

    Dback = CreatePort("MSH:Dback", -1L);
    CreateProc("MSH_DB", (long)task->tc_Node.ln_Pri+1, CTOB(debugproc), 4096L);
    WaitPort(Dback);                                /* handshake startup    */
    GetMsg(Dback);                                  /* remove dummy msg     */
    DBEnable = 1;
    dbprintf("Debugger running V1.10\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(50L);                 /*  ensure he's dead    */
    }
}

dbprintf(a,b,c,d,e,f,g,h,i,j)
long a,b,c,d,e,f,g,h,i,j;
{
    struct {
	MSG	msg;
	char	buf[256];
    } msgbuf;
    register MSG *msg = &msgbuf.msg;
    register long len;

    if (Dbport && DBEnable) {
	sprintf(msgbuf.buf,a,b,c,d,e,f,g,h,i,j);
	len = strlen(msgbuf.buf)+1;
	msg->mn_Length = len;	/*  Length NEVER 0  */
	PutMsg(Dbport, msg);
	WaitPort(Dback);
	GetMsg(Dback);
    }
}

/*
 *  BTW, the DOS library used by debugmain() was actually opened by
 *  the device driver.
 */

debugmain()
{
    register MSG *msg;
    register long len;
    register void *fh;
    void *fh2;
    MSG DummyMsg;

    Dbport = CreatePort("MSH:Dbport", -1L);
    fh = Open("CON:0/10/640/190/FileSystem debug", MODE_NEWFILE);
    fh2 = Open("PAR:", MODE_OLDFILE);
    PutMsg(Dback, &DummyMsg);
    for (;;) {
	WaitPort(Dbport);
	msg = GetMsg(Dbport);
	len = msg->mn_Length;
	if (len == 0)
	    break;
	--len;			    /*	Fix length up	*/
	if (DBEnable & 1)
	    Write(fh, msg+1, len);
	if (DBEnable & 2)
	    Write(fh2, msg+1, len);
	PutMsg(Dback, msg);
    }
    Close(fh);
    Close(fh2);
    DeletePort(Dbport);
    PutMsg(Dback, msg);             /*  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
 *  within 8 bytes of address (remember the first longword is a segment
 *  pointer and skipped).  Sigh....  (see CreateProc() above).
 */

#asm
	public	_debugproc
	public	_debugmain

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

#endif				/* DEBUG */
