/* device.c:
 *
 * Handler for ISO-9660 (+ Rock Ridge) + HFS CDROM filing system.
 * Based on DOSDEV V1.10 (2 Nov 87) by Matthew Dillon.
 *
 * ----------------------------------------------------------------------
 * This code is (C) Copyright 1993 by Frank Munkert.
 * All rights reserved.
 * This software may be freely distributed and redistributed for
 * non-commercial purposes, provided this notice is included.
 * ----------------------------------------------------------------------
 * History:
 * 
 * 21-Nov-93   fmu   - User programmable diskchange check interval.
 *                   - Better support for ACTION_INHIBIT.
 *                   - Handles filenames with ';'.
 * 15-Nov-93   fmu   Missing return value for 'handler' inserted.
 * 14-Nov-93   fmu   Added ACTION_USER packet for 'cdcontrol' program.
 * 15-Oct-93   fmu   Adapted to new VOLUME structure.
 * 10-Oct-93   fmu   - Creates volume node for 'no DOS' disks.
 *		     - Volume node now contains the correct volume
 *		       creation date.
 * 09-Oct-93   fmu   - New format for mountlist startup field.
 *		     - SAS/C support.
 *		     - Debug process assembly tag adapted to small
 *		       memory model.
 *		     - Get_Startup moved to file devsupp.c.
 * 03-Oct-93   fmu   - New buffering options 'S' and 'C'.
 *                   - Fixed bug in cdlock.
 *		     - Fixed bug in ACTION_CURRENT_VOLUME.
 * 27-Sep-93   fmu   Added ACTION_SAME_LOCK
 * 25-Sep-93   fmu   - Send 'disk inserted' / 'disk removed' event via
 *                     input.device if disk has been changed.
 *                   - Corrected bug in ACTION_DISK_INFO.
 * 24-Sep-93   fmu   - Added fast memory option 'F'.
 *                   - Added ACTION_IS_FILESYSTEM.
 *                   - Added 'write protected' error for write actions.
 *                   - Added ACTION_CURRENT_VOLUME.
 *                   - Unload handler code after ACTION_DIE.
 *                   - Immediately terminate program if called from CLI.
 *                   - Added library version number.
 *                   - Set volume label to "Unnamed" for disks without name.
 * 16-Sep-93   fmu   Added code to detect whether a lock stems from the
 *                   current volume or from another volume which has
 *                   been removed from the drive.
 */

/*
 *  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.
 */
 
#include <stdlib.h>
#include <string.h>

#if defined(_DCC) && !defined(REGISTERED)
#define abs
#endif

#include "device.h"
#include "cdrom.h"
#include "generic.h"
#include "intui.h"
#include "devsupp.h"
#include "cdcontrol.h"
#include "params.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   	*/

#if !defined(_DCC) || defined(REGISTERED)
EXECLIB *SysBase;     /* EXEC library base				*/
#endif
DOSLIB	*DOSBase;     /* DOS library base for debug process		*/
LIB	*UtilityBase; /* Utility library for miscellaneous tasks 	*/

CDROM   *g_cd;
VOLUME  *g_volume;
CDROM_OBJ *g_top_level_obj;
char	*g_vol_name;

PORT *g_timer_mp;   		/*  timer message port		*/
struct timerequest *g_timer_io; /*  timer i/o request		*/
ULONG g_timer_sigbit;
ULONG g_dos_sigbit;

char	g_device[80];		/* SCSI device name */
short	g_unit;			/* SCSI unit */
short	g_use_rock_ridge;	/* Use Rock Ridge flag 'R' */
short	g_map_to_lowercase;	/* Map to lower case flag 'L' */
int     g_trackdisk;		/* Use trackdisk calls instead of SCSI-direct */
int	g_fastmem;		/* Use fast memory for SCSI buffers */
int	g_files_pending;	/* Number of open file handles */

int	g_std_buffers;		/* Number of buffers for standard SCSI access */
int	g_file_buffers;		/* Number of buffers for contiguous reads */

t_bool	g_show_version_numbers; /* Show version numbers */

int	g_scan_interval;	/* Time between successive diskchange checks */

t_bool	g_disk_inserted;	/* Is a disk inserted? */

char	g_play_cdda_command[80];/* Command invoked if appicon is activated */

struct MsgPort *DosTask;

#ifndef NDEBUG
		    /*	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	*/
#endif

void *dosalloc(ulong);
void dosfree (ulong *);
void btos(LONG, char *);
void *NextNode (NODE *);
void *GetHead (LIST *);
LOCK *cdlock(CDROM_OBJ *, int);
void cdunlock (LOCK *);
CDROM_OBJ *getlockfile (LONG);
char *typetostr (int);
void returnpacket(struct DosPacket *);
int packetsqueued (void);
int Check_For_Volume_Name_Prefix (char *);
void Fill_FileInfoBlock (FIB *, CDROM_INFO *, VOLUME *);
void Mount (void);
void Unmount (int);
int Mount_Check (void);
void Check_Disk (void);
void Send_Timer_Request (void);
void Cleanup_Timer_Device (void);
int Open_Timer_Device (void);
void Send_Event (int);

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

#if defined(LATTICE)
int __saveds handler (void)
#elif defined(_DCC) && !defined(REGISTERED)
int _main (void)
#elif defined(_DCC) && defined(REGISTERED)
__geta4 int handler (void)
#else
int handler (void)
#endif
{
    register PACKET *packet;
    register short   error;
    MSG     *msg;
    ubyte   notdone = 1;
    char    buf[256];
    void    *tmp;
    ULONG   signals;
    ubyte   playing = FALSE;

    /*
     *	Initialize all global variables. SysBase MUST be initialized before
     *  we can make Exec calls. The DOS library is opened for the debug
     *  process only.
     */

#if !defined(_DCC) || defined(REGISTERED)
    SysBase = *(EXECLIB **) 4L;
#endif
    DosProc = (PROC *) FindTask(NULL);
    if (DosProc->pr_CLI)
      return RETURN_FAIL;
    DOSBase = (DOSLIB *) OpenLibrary ((UBYTE *) "dos.library",37);
    UtilityBase = (LIB *) OpenLibrary ((UBYTE *) "utility.library",37);

    BUG(DBDisable = 0;)				/*  Init. globals	*/
    BUG(Dbport = Dback = NULL;)
    DevList = NULL;
    g_disk_inserted = TRUE;
    {
	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.
	 */

        DosNode = BTOC(packet->dp_Arg3);

	Init_Intui ();

	if (UtilityBase && DOSBase && Get_Startup (packet->dp_Arg2)) {
	    /*
	     *	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;
	    
	    DosTask = DosNode->dn_Task;

	} else {			    /*	couldn't open dos.library   */
	    packet->dp_Res1 = DOS_FALSE;
	    returnpacket(packet);
	    return 0;			    /*	exit process		    */
	}
	returnpacket(packet);
    }

    /*
     *	Initialize debugging code
     */

    BUG(dbinit();)

    BUG(dbprintf("%d std buffers, %d file buffers\n",
    		 g_std_buffers, g_file_buffers);)

    g_timer_sigbit = 0;
    if (g_scan_interval > 0) {
      /* Initialize timer: */
      if (Open_Timer_Device ())
        Send_Timer_Request ();
    }

    g_vol_name = dosalloc (128);

    /* Mount volume (if any disk is inserted): */
    Mount ();

    g_dos_sigbit = 1L << DosProc->pr_MsgPort.mp_SigBit;

    g_files_pending = 0;

    /*
     *	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;) {
	signals = Wait(g_dos_sigbit | g_timer_sigbit | g_app_sigbit);
	if (signals & g_timer_sigbit) {
	  GetMsg (g_timer_mp);
	  Check_Disk ();
	  Send_Timer_Request ();
	}
	if (signals & g_app_sigbit) {
	  struct Message *msg;
	  while (msg = GetMsg (g_app_port)) {
	    ReplyMsg (msg);
	    if (g_play_cdda_command[0])
	      SystemTags ((UBYTE *) g_play_cdda_command,
	    		  SYS_Input, Open ((UBYTE *) "NIL:", MODE_OLDFILE),
	    		  SYS_Output, Open ((UBYTE *) "NIL:", MODE_NEWFILE),
	    		  SYS_Asynch, TRUE,
			  TAG_END);
	    else {
	      if (playing)
	        Stop_Play_Audio (g_cd);
	      else
	        Start_Play_Audio (g_cd);
	      playing = !playing;
	    }
	  }
	}
	if (!(signals & g_dos_sigbit))
	  continue;
	while (msg = GetMsg(&DosProc->pr_MsgPort)) {
	    packet = (PACKET *)msg->mn_Node.ln_Name;
	    packet->dp_Res1 = DOS_TRUE;
	    packet->dp_Res2 = 0;
	    error = 0;
#ifndef NDEBUG
	    dbprintf("Packet: %3ld %08lx %08lx %08lx %10s ",
		packet->dp_Type,
		packet->dp_Arg1, packet->dp_Arg2,
		packet->dp_Arg3,
		typetostr(packet->dp_Type)
	    );
#endif

	    if (packet->dp_Type != ACTION_DIE) {
	      if (DevList == NULL) {
		packet->dp_Res1 = DOS_FALSE;
		packet->dp_Res2 = (g_disk_inserted ? ERROR_NOT_A_DOS_DISK :
				   ERROR_NO_DISK);
		BUG(dbprintf("ERR=%ld\n", (long) packet->dp_Res2);)
		returnpacket(packet);
		continue;	        
	      }
	    }

	    switch(packet->dp_Type) {
	    case ACTION_DIE:	    /*	attempt to die? 		    */
		notdone = 0;	    /*	try to die			    */
		break;
	    case ACTION_USER:	    /*  Mode,Par1,Par2		    Bool    */
	        error = Handle_Control_Packet (packet->dp_Arg1,
					       packet->dp_Arg2,
					       packet->dp_Arg3);
	        break;
	    case ACTION_FINDINPUT:  /*  FileHandle,Lock,Name        Bool    */
		{
		    if (Mount_Check ()) {
		      CDROM_OBJ *obj;
		      CDROM_OBJ *parentdir = getlockfile(packet->dp_Arg2);
		      int       offs;

		      if (parentdir->volume != g_volume) {
		      	/* old lock from another disk: */
			error = ERROR_OBJECT_NOT_FOUND;
			goto openbreak;
		      }
		      
		      btos(packet->dp_Arg3,buf);
		      BUG(dbprintf("'%s' ", buf);)
		      offs = Check_For_Volume_Name_Prefix (buf);
		      if (obj = Open_Object (parentdir, buf + offs)) {
			if (obj->directory_f) {
			    error = ERROR_OBJECT_WRONG_TYPE;
			    goto openbreak;
			}
		      } else {
			if (iso_errno == ISOERR_ILLEGAL_NAME) {
			    error = ERROR_INVALID_COMPONENT_NAME;
			    goto openbreak;
			} else if (iso_errno == ISOERR_NOT_FOUND)
		          error = ERROR_OBJECT_NOT_FOUND;
			else if (iso_errno == ISOERR_NO_MEMORY) {
			  error = ERROR_NO_FREE_STORE;
			  goto openbreak;
			} else {
			  error = 333;
			  goto openbreak;
			}
		      }
		      if (!error) {
		        g_files_pending++;
		  	((FH *)BTOC(packet->dp_Arg1))->fh_Arg1 = (long) obj;
		      }
		    } else
		      error = ERROR_NO_DISK;
		}
	      openbreak:
		break;
	    case ACTION_READ:	    /*	 FHArg1,CPTRBuffer,Length   ActLength  */
		{
		    CDROM_OBJ *obj = (CDROM_OBJ *) packet->dp_Arg1;
		    char      *ptr = (char *) packet->dp_Arg2;
		    long    length = packet->dp_Arg3;
		    int     actual;

		    if (obj->volume != g_volume) {
		      /* old lock from another disk: */
		      error = ERROR_OBJECT_NOT_FOUND;
		      break;
		    }
		    actual = Read_From_File (obj, ptr, length);
		    packet->dp_Res1 = actual;
		}
		break;
	    case ACTION_END:	    /*	 FHArg1 		    Bool:TRUE  */
		{
		    CDROM_OBJ *obj = (CDROM_OBJ *) packet->dp_Arg1;

		    Close_Object (obj);
		    g_files_pending--;
		}
		break;
	    case ACTION_SEEK:	    /*	 FHArg1,Position,Mode	    OldPosition*/
	        {
		    CDROM_OBJ *obj = (CDROM_OBJ *) packet->dp_Arg1;
		    long offset = packet->dp_Arg2;
		    int mode = packet->dp_Arg3;
		    
		    if (obj->volume != g_volume) {
		      /* old lock from another disk: */
		      error = ERROR_OBJECT_NOT_FOUND;
		      break;
		    }
		    packet->dp_Res1 = obj->pos;
		    if (!Seek_Position (obj, offset, mode)) {
		      error = ERROR_SEEK_ERROR;
		      packet->dp_Res1 = -1;
		    }
		}
		break;
	    case ACTION_EXAMINE_NEXT: /*   Lock,Fib		      Bool	 */
		{
		    FIB       *fib = BTOC (packet->dp_Arg2);
		    CDROM_OBJ *dir = getlockfile (packet->dp_Arg1);
		    CDROM_INFO info;

		    if (dir->volume != g_volume) {
		      /* old lock from another disk: */
		      error = ERROR_OBJECT_NOT_FOUND;
		      break;
		    }
		    if (!dir->directory_f) {
			error = ERROR_OBJECT_WRONG_TYPE;
			break;
		    }
		    if (Examine_Next (dir, &info,
		    		      (unsigned long *) &fib->fib_DiskKey)) {
		      error = 0;
		      Fill_FileInfoBlock (fib, &info, dir->volume);
		    } else {
		      error = ERROR_NO_MORE_ENTRIES;
		    }
		    break;
		}
	    case ACTION_EXAMINE_OBJECT: /*   Lock,Fib			Bool	   */
		{
		    FIB *fib = BTOC (packet->dp_Arg2);
                    CDROM_OBJ *obj = getlockfile (packet->dp_Arg1);
		    CDROM_INFO info;

		    if (obj->volume != g_volume) {
		      /* old lock from another disk: */
		      error = ERROR_OBJECT_NOT_FOUND;
		      break;
		    }
		    fib->fib_DiskKey = 0;
		    error = 0;
		    if (!CDROM_Info (obj, &info))
		      error = -1;
		    else
		      Fill_FileInfoBlock (fib, &info, obj->volume);
		}
		break;
	    case ACTION_INFO:	    /*	Lock, InfoData	  Bool:TRUE    */
		tmp = BTOC(packet->dp_Arg2);
		error = -1;
		/*  fall through    */
	    case ACTION_DISK_INFO:  /*	InfoData	  Bool:TRUE    */
		{
		  if (Mount_Check ()) {
		    register INFODATA *id;

		    (error) ? (id = tmp) : (id = BTOC (packet->dp_Arg1));
		    error = 0;
		    memset (id, 0, sizeof(*id));
		    id->id_DiskState = ID_WRITE_PROTECTED;
		    id->id_NumBlocks	 = Volume_Size (g_volume);
		    id->id_NumBlocksUsed = id->id_NumBlocks;
		    id->id_BytesPerBlock = 2048;
		    id->id_DiskType = ID_DOS_DISK;
		    id->id_VolumeNode = (long) CTOB (DevList);
		    id->id_InUse = 0;
		  }
		}
		break;
	    case ACTION_IS_FILESYSTEM:   /*  -                      Bool */
	      packet->dp_Res1 = DOSTRUE;
	      break;
	    case ACTION_PARENT:     /*	 Lock			    ParentLock */
	        {
		  if (Mount_Check ()) {
		    if (packet->dp_Arg1) {
		      CDROM_OBJ *obj = getlockfile (packet->dp_Arg1);
		      CDROM_OBJ *parent;
		  
		      if (obj->volume != g_volume) {
		        /* old lock from another disk: */
		        error = ERROR_OBJECT_NOT_FOUND;
		        break;
		      }
		      if (Is_Top_Level_Object (obj)) {
		        packet->dp_Res1 = packet->dp_Res2 = 0;
		      } else {
		        parent = Find_Parent (obj);
		        if (!parent) {
		          if (iso_errno == ISOERR_NO_MEMORY)
		            error = ERROR_NO_FREE_STORE;
		          else
		            error = ERROR_OBJECT_NOT_FOUND;
		        } else {
		          packet->dp_Res1 = (long)
			    CTOB (cdlock (parent, ACCESS_READ));
		        }
		      }
		    } else
		      error = ERROR_OBJECT_NOT_FOUND;
		  } else
		    error = ERROR_NO_DISK;
		}
		break;
	    case ACTION_LOCATE_OBJECT:	/*   Lock,Name,Mode		Lock	   */
		{
		  if (Mount_Check ()) {
		    CDROM_OBJ *parentdir = getlockfile (packet->dp_Arg1);
                    CDROM_OBJ *obj;
		    int offs;

		    if (parentdir->volume != g_volume) {
		      /* old lock from another disk: */
		      error = ERROR_OBJECT_NOT_FOUND;
		      break;
		    }
		    btos (packet->dp_Arg2, buf);
#ifndef NDEBUG
		    dbprintf ("'%s' %ld ", buf, packet->dp_Arg3);
		    if (strcmp(buf,"debugoff") == 0)
			DBDisable = 1;
		    if (strcmp(buf,"debugon") == 0)
			DBDisable = 0;
#endif

		    offs = Check_For_Volume_Name_Prefix (buf);
		    if (buf[offs]==0) {
		      if (parentdir)
		        obj = Clone_Object (parentdir);
		      else
		        obj = Open_Top_Level_Directory (g_volume);
		    } else
		      obj = Open_Object (parentdir, buf + offs);
		    
		    if (obj) {
		      packet->dp_Res1 = (long) CTOB (cdlock (obj, packet->dp_Arg3));
		    } else {
		      if (iso_errno == ISOERR_SCSI_ERROR) {
		        error = ERROR_OBJECT_NOT_FOUND;
			Unmount (FALSE);
		      } else if (iso_errno == ISOERR_ILLEGAL_NAME)
			error = ERROR_INVALID_COMPONENT_NAME;
		      else if (iso_errno == ISOERR_NOT_FOUND)
			error = ERROR_OBJECT_NOT_FOUND;
		      else if (iso_errno == ISOERR_NO_MEMORY)
		        error = ERROR_NO_FREE_STORE;
		      else
		        error = 333;
		    }
		  } else
		    error = ERROR_NO_DISK;
		}
		break;
	    case ACTION_COPY_DIR:   /*	 Lock,			    Lock       */
	    	{
		  if (packet->dp_Arg1) {
		    CDROM_OBJ *obj = getlockfile (packet->dp_Arg1);
		    CDROM_OBJ *new = Clone_Object (obj);
		  
		    if (!new)
		      error = ERROR_NO_FREE_STORE;
		    else
		      packet->dp_Res1 = (long) CTOB (cdlock (new, ACCESS_READ));
		  } else
		    packet->dp_Res1 = 0;
		}
		break;
	    case ACTION_FREE_LOCK:  /*	 Lock,			    Bool       */
		if (packet->dp_Arg1);
		    cdunlock (BTOC(packet->dp_Arg1));
		break;
	    case ACTION_CURRENT_VOLUME: /* -                        DevList    */
	        packet->dp_Res1 = (long) CTOB (DevList);
	        break;
	    case ACTION_INHIBIT:    /*	 Bool			    Bool       */
                if (packet->dp_Arg1 == DOS_FALSE)
		  Check_Disk ();
		break;
	    /*
	     *  FINDINPUT and FINDOUTPUT normally should return the
	     *  'write protected' error. If the field 'Name', however,
	     *  designates the root (e.g. CD0:), then the 'wrong type'
	     *  error should be returned. Otherwise, AmigaDOS would do
	     *  some funny things (such as saying 'Volume CD0: is write-
	     *  protected') if you try to mount the handler with the
	     *  field 'Mount' set to 1.
	     */
	    case ACTION_FINDOUTPUT: /*   Handle  Lock  Name         Bool       */
	    case ACTION_FINDUPDATE: /*   Handle  Lock  Name         Bool       */
	    {
	      int pos;
	      
	      btos(packet->dp_Arg3,buf);
	      BUG(dbprintf("'%s' ", buf);)
	      if ((pos = Check_For_Volume_Name_Prefix (buf)) &&
	          buf[pos] == 0)
		error = ERROR_OBJECT_WRONG_TYPE;
	      else
		error = ERROR_DISK_WRITE_PROTECTED;
	      break;
	    }
	    case ACTION_SAME_LOCK: /*    Lock  Lock                 Bool       */
	    {
	      CDROM_OBJ *obj1 = getlockfile(packet->dp_Arg1),
	                *obj2 = getlockfile(packet->dp_Arg2);

              if (Same_Objects (obj1, obj2))
                packet->dp_Res1 = DOSTRUE;
	      else
                packet->dp_Res1 = DOSFALSE;

	      break;
	    }
	    case ACTION_RENAME_DISK:
	    case ACTION_WRITE:
	    case ACTION_SET_PROTECT:
	    case ACTION_DELETE_OBJECT:
	    case ACTION_RENAME_OBJECT:
	    case ACTION_CREATE_DIR:
	    case ACTION_SET_COMMENT:
	    case ACTION_SET_DATE:
	    case ACTION_SET_FILE_SIZE:
              error = ERROR_DISK_WRITE_PROTECTED;
              break;
	    /*
	     *	A few other packet types which we do not support
	     */
	    case ACTION_MORE_CACHE: /*	 #BufsToAdd		    Bool       */
	    case ACTION_WAIT_CHAR:  /*	 Timeout, ticks 	    Bool       */
	    case ACTION_FLUSH:	    /*	 writeout bufs, disk motor off	       */
	    case ACTION_SCREEN_MODE:/*	 Bool(-1:RAW 0:CON)	    OldState   */
	    default:
		error = ERROR_ACTION_NOT_KNOWN;
		break;
	    }
	    if (packet) {
		if (error) {
		    BUG(dbprintf("ERR=%ld\n", error);)
		    packet->dp_Res1 = DOS_FALSE;
		    packet->dp_Res2 = error;
		} else {
		    BUG(dbprintf("RES=%06lx\n", packet->dp_Res1));
		}
		returnpacket(packet);
	    }
	}
    }
    BUG(dbprintf("Can we remove ourselves? ");)
    Delay(100);	    /*	I wanna even see the debug message! */
    Forbid();
    if (packetsqueued() || g_files_pending ||
    	(g_volume && g_volume->locks)) {
	Permit();
	BUG(dbprintf(" ..  not yet!\n");)
	notdone = 1;
	goto top;		/*  sorry... can't exit     */
    } else
	BUG(dbprintf(" ..  yes!\n");)

    /* remove timer device and any pending timer requests: */
    if (g_timer_sigbit)
      Cleanup_Timer_Device ();

    /* this is getting dangerous. We will unload our very own
     * code via UnLoadSeg() and need to keep the system in
     * Forbid() state in order to avoid getting the free memory
     * reclaimed by other tasks. This means: *NO* Wait() after
     * Unmount(TRUE) ist called!
     */

    Unmount (TRUE);
    
    dosfree ((ulong *) g_vol_name);

    Cleanup_CDROM (g_cd);

    Close_Intui ();

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

    BUG(dbuninit();)
    if (UtilityBase)
      CloseLibrary (UtilityBase);
    if (DOSBase)
      CloseLibrary ((struct Library *) DOSBase);
    
    return 0;
}


/*
 *  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(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?
 */

int packetsqueued (void)
{
    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(ulong bytes)
{
    register ulong *ptr;

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

void dosfree (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(LONG bstr, char *buf)
{
    unsigned char *str = BTOC(bstr);
    bmov((char *) str+1, buf, *str);
    buf[*str] = 0;
}

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

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

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

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

LOCK *cdlock(CDROM_OBJ *cdfile, int mode)
{
  LOCK *lock = dosalloc (sizeof(LOCK));

  cdfile->volume->locks++;
  lock->fl_Key = (long) cdfile;
  lock->fl_Access = ACCESS_READ;
  lock->fl_Task = &DosProc->pr_MsgPort;
  lock->fl_Volume = (BPTR) CTOB (DevList);
  return(lock);
}

void cdunlock (LOCK *lock)
{
  CDROM_OBJ *obj = (CDROM_OBJ *) lock->fl_Key;

  if (--obj->volume->locks == 0 && !obj->volume->valid) {
    Close_Volume (obj->volume);
  }
  Close_Object (obj);
  dosfree ((ulong *) lock);				/* free lock	    */
}

/*
 *  GETLOCKFILE(bptrlock)
 *
 *  Return the CDROM_OBJ (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 CDROM.
 */

CDROM_OBJ *getlockfile (LONG lock)
{
  LOCK *rl = BTOC (lock);

  if (rl)
    return (CDROM_OBJ *) rl->fl_Key;
  return g_top_level_obj;
}

/*
 * If p_pathname contains a ':' character, return the position of the first
 * character after ':'
 * Otherwise, return 0.
 */

int Check_For_Volume_Name_Prefix (char *p_pathname)
{
  char *pos = strchr (p_pathname, ':');
  
  return pos ? (pos - p_pathname) + 1 : 0;
}

/*
 * Fills a FileInfoBlock with the information contained in the
 * directory record of a CD-ROM directory or file.
 */

void Fill_FileInfoBlock (FIB *p_fib, CDROM_INFO *p_info, VOLUME *p_volume)
{
  char *src = p_info->name;
  char *dest = p_fib->fib_FileName+1;
  int len = p_info->name_length;

  p_fib->fib_DirEntryType = p_info->directory_f ? 2 : -3;
  
  /* I don't know exactly why I have to set fib_EntryType, but other
   * handlers (e.g. DiskHandler by J Toebes et.al.) also do this.
   */
   
  p_fib->fib_EntryType = p_fib->fib_DirEntryType;
  
  if (len == 1 && *src == ':') {
    /* root of file system: */
    p_fib->fib_DirEntryType = 2 /* was: 1 */;
    /* file name == volume name: */
    memcpy (p_fib->fib_FileName, g_vol_name, g_vol_name[0]+1);
  } else {
    /* copy file name: */
    if (g_show_version_numbers) {
      p_fib->fib_FileName[0] = len;
      for (; len; len--)
        *dest++ = *src++;
    } else {
      short i, real_len=len;
      for (i=0; i<len; i++) {
        if (*src == ';')
	  real_len = i;
        *dest++ = *src++;
      }
      p_fib->fib_FileName[0] = real_len;
    }

    if (g_map_to_lowercase && p_volume->protocol == PRO_ISO) {
      /* convert ISO filename to lowercase: */
      int i, len = p_fib->fib_FileName[0];
      char *cp = p_fib->fib_FileName + 1;
    
      for (i=0; i<len; i++, cp++)
        *cp = ToLower (*cp);
    }
  }
  p_fib->fib_Protection = 0;
  p_fib->fib_Size = p_info->file_length;
  p_fib->fib_NumBlocks = p_info->file_length >> 11;
  p_fib->fib_Comment[0] = 0;

  p_fib->fib_Date.ds_Days   = p_info->date / (24 * 60 * 60);
  p_fib->fib_Date.ds_Minute = (p_info->date % (24 * 60 * 60)) / 60;
  p_fib->fib_Date.ds_Tick   = (p_info->date % 60) * TICKS_PER_SECOND;
}

/*
 * 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.
 */
 
void Create_Volume_Node (LONG p_disk_type, ULONG p_volume_date)
{
  DOSINFO *di = BTOC(((ROOTNODE *)DOSBase->dl_Root)->rn_Info);
  DEVLIST *dl;

  Forbid ();
  DevList = dl = dosalloc(sizeof(DEVLIST));
  dl->dl_Type = DLT_VOLUME;
  dl->dl_Task = &DosProc->pr_MsgPort;
  dl->dl_DiskType = p_disk_type;
  dl->dl_Name = (BSTR) CTOB (g_vol_name);
  dl->dl_VolumeDate.ds_Days = p_volume_date / (24 * 60 * 60);
  dl->dl_VolumeDate.ds_Minute = (p_volume_date % (24 * 60 * 60)) / 60;
  dl->dl_VolumeDate.ds_Tick = (p_volume_date % 60) * TICKS_PER_SECOND;
  dl->dl_Next = di->di_DevInfo;
  di->di_DevInfo = (long)CTOB(dl);
  Permit ();
}

/*
 * Mount a volume.
 */

void Mount (void)
{
  char buf[33];

  if (Has_Audio_Tracks (g_cd))
    Show_CDDA_Icon ();

  g_volume = Open_Volume (g_cd, g_use_rock_ridge);
  if (!g_volume) {
    BUG(dbprintf ("!!! cannot open VOLUME !!!\n");)
#if 0
    strcpy (g_vol_name, "\9NoDosDisk");
    Create_Volume_Node (ID_NOT_REALLY_DOS, 0);
    Send_Event (TRUE);
#endif
    return;
  } else {
    g_disk_inserted = TRUE;
    g_top_level_obj = Open_Top_Level_Directory (g_volume);
    if (!g_top_level_obj) {
      BUG(dbprintf ("!!! cannot open top level directory !!!\n");)
      return;
    }
  }
  
  BUG(dbprintf ("***mounting*** ");)

  Volume_ID (g_volume, buf, sizeof (buf)-1);  
  g_vol_name[0] = strlen (buf);
  memcpy (g_vol_name+1, buf, strlen (buf));

  if (!(g_vol_name[0]))
    memcpy (g_vol_name, "\7Unnamed", 8);

  /* AmigaDOS expects the BCPL string g_vol_name to be null-terminated: */
  g_vol_name[g_vol_name[0]+1] = 0;

  Create_Volume_Node (ID_DOS_DISK, Volume_Creation_Date (g_volume));
  Send_Event (TRUE);
}

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

void Unmount (int p_remove_device)
{
  DOSINFO *di;
  DEVLIST *dl;
  DEVNODE *dn;
  void *dlp;

  Hide_CDDA_Icon ();

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

  if (DevList) {
    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((ulong *) dl);
    } else {
      BUG(dbprintf("****PANIC: Unable to find volume node\n");)
    }

    if (DevList->dl_DiskType != ID_NOT_REALLY_DOS) {
      Close_Object (g_top_level_obj);
      if (g_volume->locks == 0)
        Close_Volume (g_volume);
      else {
        g_volume->valid = 0;
      }
    }

    DevList = NULL;
  }

  Send_Event (FALSE);

  g_volume = 0;

  /* when the handler code exits the corresponding device
   * node (e.g. "CD0") will be modified. The handler code
   * will be unloaded and the task entry will be set to
   * zero, so the next device access will reload and
   * restart the handler code.
   */

  if (p_remove_device) {
    di = BTOC (((ROOTNODE *) DOSBase->dl_Root)->rn_Info);
    dlp = &di->di_DevInfo;
    dn = BTOC (di->di_DevInfo);

    while (dn) {
      if (dn->dn_Task == DosTask) {
        BUG(dbprintf("got it! %b\n",dn->dn_Name);)
	if (TypeOfMem (BADDR (dn->dn_SegList))) {
	  UnLoadSeg (dn->dn_SegList);
	  dn->dn_SegList = 0;
	} else
	  BUG(dbprintf("not a valid seglist\n");)

	dn->dn_Task = NULL;

        *(BPTR *) dlp = dn->dn_Next;
        dosfree ((ulong *) dn);

	break;
      }	else {
        dlp = &dn->dn_Next;
	dn = BTOC (dn->dn_Next);
      }
    }
  } else
    Permit ();
}

/*
 * Mount_Check returns 1 if a valid disk is inserted in the drive. A check is
 * only performed if previously the drive was empty.
 */

int Mount_Check (void)
{
  if (!g_disk_inserted) {
   /*
    * No disk was inserted up to now: we will check whether
    * a disk has been inserted by sending the test unit ready
    * command. We have to send the command twice because
    * the first SCSI command after inserting a new disk is
    * always rejected.
    */
    if (Test_Unit_Ready (g_cd) ||
        Test_Unit_Ready (g_cd)) {
      g_disk_inserted = TRUE;
      Mount ();
    } else {
      return 0;
    }
    if (DevList)
      return 1;
    else {
      /* Mount() did not succeed: */
      return 0;
    }
  }
  return 1;
}

/*
 *  Open timer device structures:
 */

int Open_Timer_Device (void)
{
  if (!(g_timer_mp = CreateMsgPort ())) {
    BUG(dbprintf ("cannot create timer message port!\n");)
    return 0;
  }
  if (!(g_timer_io = (struct timerequest *)
  	 CreateIORequest (g_timer_mp, sizeof (struct timerequest)))) {
    BUG(dbprintf ("cannot create timer i/o structure!\n");)
    DeleteMsgPort (g_timer_mp);
    return 0;
  }
  if (OpenDevice ((UBYTE *) TIMERNAME, UNIT_VBLANK,
  		  (struct IORequest *) g_timer_io, 0)) {
    BUG(dbprintf ("cannot open timer device!\n");)
    DeleteIORequest ((struct IORequest *) g_timer_io);
    DeleteMsgPort (g_timer_mp);
    return 0;
  }
  g_timer_sigbit = 1L << g_timer_mp->mp_SigBit;
  return 1;
}

/*
 *  Remove timer device structures:
 */

void Cleanup_Timer_Device (void)
{
  /* remove any pending requests: */
  if (!CheckIO ((struct IORequest *) g_timer_io))
    AbortIO ((struct IORequest *) g_timer_io);
  WaitIO ((struct IORequest *) g_timer_io);
  
  CloseDevice ((struct IORequest *) g_timer_io);
  DeleteIORequest ((struct IORequest *) g_timer_io);
  DeleteMsgPort (g_timer_mp);
}

/*
 *  Send timer request
 */

void Send_Timer_Request (void)
{
  g_timer_io->tr_node.io_Command = TR_ADDREQUEST;
  g_timer_io->tr_time.tv_secs = g_scan_interval;
  g_timer_io->tr_time.tv_micro = 0;
  SendIO ((struct IORequest *) g_timer_io);
}

/*
 *  Check whether the disk has been removed or inserted.
 */

void Check_Disk (void)
{
  BUG(dbprintf ("Checking Disk... ");)
  if (g_disk_inserted) {
    if (Test_Unit_Ready (g_cd)) {
      BUG(dbprintf ("no disk change  "));
    } else {
      g_disk_inserted = FALSE;
      BUG(dbprintf ("disk has been removed  ");)
      if (DevList)
        Unmount (FALSE);
      Hide_CDDA_Icon ();
    }
  }
  if (!g_disk_inserted) {
    if (Test_Unit_Ready (g_cd) ||
        Test_Unit_Ready (g_cd)) {
      g_disk_inserted = TRUE;
      BUG(dbprintf ("disk has been inserted");)
      Mount ();
    }
  }
  BUG(dbprintf ("\n");)
}

/* The following lines will generate a `disk inserted/removed' event, in order
 * to get Workbench to rescan the DosList and update the list of
 * volume icons.
 */

void Send_Event (int p_inserted)
{
  struct IOStdReq *InputRequest;/* OLSEN */
  struct MsgPort *InputPort;	/* OLSEN */

  if (InputPort = (struct MsgPort *) CreateMsgPort ()) {
    if (InputRequest = (struct IOStdReq *)
        CreateIORequest (InputPort, sizeof (struct IOStdReq))) {
      if (!OpenDevice ((UBYTE *) "input.device", 0,
      		       (struct IORequest *) InputRequest, 0)) {
	static struct InputEvent InputEvent;

	memset (&InputEvent, 0, sizeof (struct InputEvent));

	InputEvent.ie_Class = p_inserted ? IECLASS_DISKINSERTED :
					   IECLASS_DISKREMOVED;

	InputRequest->io_Command = IND_WRITEEVENT;
	InputRequest->io_Data = &InputEvent;
	InputRequest->io_Length = sizeof (struct InputEvent);

	DoIO ((struct IORequest *) InputRequest);

	CloseDevice ((struct IORequest *) InputRequest);
      }
      DeleteIORequest (InputRequest);
    }
    DeleteMsgPort (InputPort);
  }
}


