/*
 *		PCDISK.C
 *
 *		By Eddy Carroll
 *
 *		Based somewhat on RDF: by Olaf Seibert, which itself was based
 *		on Matt Dillon's excellent PIPE: device.
 *
 *		This handler lets you access a hard disk partition as if the
 *		entire contents of the file formed a single file which can
 *		be read, written to, and seeked on. This is designed mainly
 *		for use with the Commodore Bridgeboard which uses an AmigaDOS
 *		file to emulate a PC hard disk. By faking an AmigaDOS file
 *		like this, we can avoid the inefficiency of having to go through
 *		the standard file system.
 *
 *		The bridgeboard actually supports two types of Amiga-mounted PC
 *		partitions. Autoboot partitions are unique, and have a special
 *		512 byte header at the start giving the disk geometry. The
 *		bridgeboard can boot from this. Other partitions (non-autoboot)
 *		don't have the header at the start. To allow the two types to
 *		be interchanged, we fake the autoboot header in the handler
 *		when it's required, and never actually store it on disk at all.
 *
 *		The bridgeboard assumes that PC disks are laid out according to
 *		"standard" PC disk conventions, i.e. 17 sectors/track and 4 heads.
 *		This is not required for the autoboot volume however, and it is
 *		possible to use an Amiga-style geometry. This makes it possible
 *		to share a bridgeboard partition with an AmigaDOS handler such
 *		as MSDOSFileSystem which is supplied with CrossDOS, allowing
 *		easy copying of files from the Amiga side.
 * 
 *		This handler uses the filename given when opening a file to
 *		specify various items. Filenames can have any combination of
 *		the following switches, though not all make sense when combined:
 *
 *		/AUTO	-- Include a 512 byte autoboot header at start of file
 *		/AMIGA	-- Use the actual Amiga sector and head info
 *		/IBM	-- Use the IBM settings of 4 heads, 17 sectors (default)
 *		/NEW	-- Only openi new files (reject open for read access)
 *		/Sx		-- Force x sectors per track
 *		/Hx		-- Force x heads per cylinder
 *
 *		For example, accessing the file PCD: on its own gives a non-autoboot
 *		partition and accessing PCD:AUTO/AMIGA gives an autoboot partition
 *		which has the same layout on the PC as it does on the Amiga.
 *
 *		In general, you need only use PCD:/AUTO for autoboot partitions and
 *		PCD: for non-autoboot partitions. Any additional filename text is
 *		simply ignored, unless proceeded by a '/', in which case an error
 *		is returned.
 *
 *		The /NEW option is intended for use with the bridgeboard JLINK
 *		command to initialise a new partition for use by the Bridgeboard
 *		software; the JLINK command tries to open an existing file
 *		first, and if it finds one, refuses to write the new partition
 *		info we need.
 *
 *		The following functions are handled:
 *
 *			Open() Close() OpenFileFromLock()
 *			Read() Write()
 *			Seek()
 *			Examine()
 *			Lock()
 *			DupLock()
 *			UnLock()
 *
 *		These are more or less just dummies which appear to do something
 *		useful.
 *
 *		DeleteFile()
 *		IsFileSystem()
 *		SetComment()
 *		SetDate()
 *
 *		Implementation note: Since we have to be re-entrant, we don't use
 *		any globals at all (except for library bases, which can be safely
 *		shared). This makes some things a little clumsy, but there you go.
 */

#define DEBUG	0							/* If true, full debugging msgs	*/
#define SNOOP	0							/* If true, monitor read/writes	*/

#ifndef LATTICE_50
#include "system.h"
#endif

#if DEBUG
#define DB(x)	x
#else
#define DB(x)
#endif
#define P		KPrintF

#define BUFSIZE			16384				/* Buffer size for masked xfers	*/
#define BOOTSIZE		sizeof(BootBlock)	/* Size of special Janus Boot	*/

#define DEF_COMMENT	\
	"Bridgeboard compatible DOS-style file. Copyright (c) Eddy Carroll 1992."

#define IBM_HEADS		4			/* Default heads/cyl for an IBM disk	*/
#define IBM_SECTORS		17			/* Default secs/track for an IBM disk	*/	

											/* Various ACTIONs supported	*/
											/* -------------------------    */
#define ACTION_FIND_UPDATE			1004	/* Three variations on Open()	*/
#define ACTION_FIND_INPUT			1005
#define ACTION_FIND_OUTPUT			1006
#define ACTION_END					1007	/* Close()						*/
#define ACTION_SEEK 				1008	/* Seek()						*/
#define ACTION_FH_FROM_LOCK			1026	/* 2.0, opens file from lock	*/
#define ACTION_IS_FILESYSTEM		1027	/* Is a file system				*/

#define ST_FILE						-3		/* Indicates a normal file		*/


#undef	BADDR
#define BADDR(x)	((void *)((ULONG)(x) << 2))	/* Convert BCPL->CPTR		*/
#define CTOB(x)		(((ULONG)(x)) >> 2)			/* Convert CPTR->BADDR		*/

#define FNAME_LEN	50						/* Maximum length of filename	*/

#define branchto	goto					/* (-: Avoid goto's :-)     	*/

typedef struct DosPacket	DosPacket;
typedef struct Process		Proc;
typedef struct DeviceNode	DevNode;
typedef struct DosEnvec		DosEnvec;
typedef struct MsgPort		MsgPort;
typedef struct IOStdReq     IOStdReq;
typedef unsigned char		uchar;

/*
 *		This is the autoboot block that appears at the start of autoboot
 *		partitions used by the bridgeboard (the PC isn't directly
 *		aware of this).
 */
typedef struct BootBlock {
	uchar		title[8];			/* Title, normally 'ABOOT'			*/
	UWORD		heads;				/* Heads per cylinder				*/
	UWORD		sectors;			/* Sectors per track				*/
	UWORD		cylinders;			/* Cylinders per disk				*/
	UWORD		filler[249];		/* Pad to 512 bytes in size			*/
} BootBlock;


/*
 *		This structure is used to hold details of a particular IBM
 *		partition on our virtual device (block offsets are relative to
 *		the start of the physical device, not our virtual device).
 */
typedef struct IBMInfo {
	ULONG	sectors;				/* Number of sectors per track		*/
	ULONG	heads;					/* Number of heads per cylinder		*/
	ULONG	numcyls;				/* Number of cylinders				*/
	ULONG	startblock;				/* Starting block number			*/
	ULONG	numblocks;				/* Number of blocks					*/
	ULONG	isautoboot;				/* True if autoboot disk			*/
	ULONG	newfileonly;			/* True if only allow new files		*/
} IBMInfo;

/*
 *		Our file structure, used to keep track of where we are etc. We
 *		have an optional attachment that can be added to the end of
 *		the file header, to contain a preface to the main disk file.
 *		This can be used to fake an autoboot block if the PC disk is
 *		an autoboot partition, and also to create "fake" files like
 *		automatically generated mount-lists etc, if required. If
 *		datasize is zero, no such attachment exists.
 *
 *		Note that the data[1] is merely a place holder; in actual fact,
 *		we allocate additional space at the end of the structure to hold
 *		the full amount of data needed. To avoid confusion, we let 4
 *		bytes go to waste at the end (i.e. we don't compensate for the
 *		fact that we've already allocated 1 longword of the array).
 */
typedef struct MyFile {
	ULONG	  start;				/* Starting byte on physical device	*/
	ULONG	  offset;				/* Current file offset form _Start	*/
	ULONG	  maxoffset;			/* Maximum value file_Offset can be	*/
	ULONG	  datasize;				/* Size of the attached data info	*/
	ULONG	  data[1];				/* Placeholder for attached data	*/
} MyFile;

/*
 *		A standard lock structure, with the addition that we remember the
 *		name used to open it, so that we can parse the options when
 *		Open() is called.
 */
typedef struct MyLock {
	struct FileLock	fl;				/* A standard AmigaDOS lock			*/
	IBMInfo			ibm;			/* Details about disk layout		*/
} MyLock;

/*
 *		Transient info that needs to be passed around various functions
 *		It's collected in a single structure to reduce overhead in
 *		function calls.
 *
 *		This is mainly used to handle the 'Mask' parameter from the
 *		mountlist. Any attempts to read directly from disk into memory
 *		that is not wholly within the range indicated by the mask has
 *		to pass through a buffer in CHIP ram, since the device can't
 *		DMA directly into the indicated memory. To avoid allocating
 *		CHIP ram for this buffer needlessly on systems that have no
 *		such problems, we only allocate the buffer the first time it's
 *		actually needed.
 */
typedef struct MaskData {
	long	xfermask;				/* Mask to check mem buffer against */
	uchar	*xferbuffer;			/* CHIP mem used for transfers		*/
	long	xfersize;				/* Size of above buffer				*/
} MaskData;


/*
 *		Prototypes and external declarations
 */

extern struct ExecBase	*AbsExecBase;	/* Absolute global address 4		*/
struct ExecBase			*SysBase;		/* Required to make Exec calls		*/
struct DOSBase			*DOSBase;		/* Used for AmigaDOS calls			*/
extern DosPacket		*taskwait();	/* Wait for a message				*/

ULONG PerformIO(MaskData *maskdata, IOStdReq *ioreq, ULONG cmd, void *buffer,
				ULONG offset, ULONG size);
MyFile *OpenMyFile(IBMInfo *ibm, ULONG blocksize);
int ParseFilename(uchar *filename, IBMInfo *ibm, DosEnvec *env);
#if DEBUG
uchar *actname();
#endif


/*
 *		PCdisk_handler()
 *
 *		Okay, here we go. This is the main entry point. We get our
 *		startup message from AmigaDOS giving us info about the device
 *		characteristics etc. and then go into a loop where we process
 *		packet requests until eventually we are told to die.
 */
void PCdisk_handler()
{
	MaskData	maskdata[1];	/* Pointer to memory mask info for disk i/o	*/
	Proc		*myproc;		/* Pointer to our process					*/
	DevNode 	*mynode;		/* Our device node passed in parmpkt Arg3	*/
	MsgPort		*ioport = NULL;	/* Port used for interaction with device	*/
	IOStdReq	*ioreq  = NULL;	/* We use a single i/o request for all i/o	*/
	DosEnvec	*env = NULL;	/* Our Dos device environment table entry	*/
	uchar	    *default_device;/* Name of the disk device we are using		*/
	ULONG		default_unit;	/* What unit on this device					*/
	ULONG		default_flags;	/* Any necessary flags						*/
	ULONG		opencount = 0;	/* How many files & locks are open     		*/
	ULONG		blocksize;		/* Size of a block in bytes					*/
	ULONG		cylsize;		/* Size of a device cylinder in bytes		*/
	ULONG		startblock;		/* Start block on our partition				*/
	ULONG		numblocks;		/* Number of blocks on our partition		*/
	ULONG		numcyls;		/* Number of cylinders on our partition		*/
	ULONG		block_offset_mask;	/* Isolate part an offset within a blk	*/
	ULONG		block_align_mask;	/* Force an offset to be block aligned	*/
	uchar		*oneblock = NULL;	/* Pointer to mem area to hold one blk	*/
	uchar		devname[50];	/* Name of our DOS device (PCD: etc.)		*/
	uchar		buf[50];		/* Scratch buffer							*/
	uchar		expunge = 0;	/* If true, we have been expunged			*/
	uchar		done;			/* Used for loop termination				*/

	SysBase = (struct ExecBase *)AbsExecBase;	/* Get pointer to exec		*/
	DOSBase = OpenLibrary("dos.library", 0);	/* and AmigaDOS				*/
	myproc	= (Proc *)FindTask(0L);

	/*
	 *	The debugging uses the serial port, at current settings. Connect
	 *	a dumb terminal (or a modem with echo mode enabled + a comms package)
	 *	to see the output.
	 */
	DB(P("Started debugging for custom device\n"));

	/*
	 *		Initial startup message. This is where we get our device
	 *		parameters from (device name, etc.)
	 */
	{
		register DosPacket *mypkt;

		mypkt  = taskwait(myproc);
		ioport = CreatePort(0, 0);

		if (ioport) {
			struct FileSysStartupMsg *fssm;
			uchar *dname;

			mynode	= BADDR(mypkt->dp_Arg3);
			fssm    = BADDR(mynode->dn_Startup);
			dname   = BADDR(mypkt->dp_Arg1);
			if (dname) {
				strncpy(devname, dname+1, *dname);
				devname[*dname] = 0;
			} else
				devname[0] = 0;
			
			if (!fssm) {
				returnpkt(mypkt, myproc, DOSFALSE, ERROR_DEVICE_NOT_MOUNTED);
				branchto exit;
			}
				
			default_device    = (uchar *)BADDR(fssm->fssm_Device) + 1;
			default_unit      = fssm->fssm_Unit;
			default_flags     = fssm->fssm_Flags;
			env               = BADDR(fssm->fssm_Environ);
			blocksize	      = env->de_SizeBlock * 4;
			block_offset_mask = blocksize - 1;
			block_align_mask  = ~block_offset_mask;
			numcyls		      = env->de_HighCyl - env->de_LowCyl + 1;
			numblocks	      = env->de_BlocksPerTrack *
							    env->de_Surfaces * numcyls;
			cylsize           = blocksize * env->de_BlocksPerTrack *
							    env->de_Surfaces;
			startblock	      = cylsize * env->de_LowCyl;

			maskdata->xferbuffer = NULL;
			maskdata->xfermask   = env->de_Mask;

			/*
			 *		Now try and initialise an i/o port for ourselves.
			 *		If we can't do it, have to fail
			 */
			ioreq = CreateStdIO(ioport);
			if (!ioreq || OpenDevice(default_device, default_unit,
									 ioreq, default_flags) != 0
					   || (oneblock = AllocMem(blocksize, 0)) == NULL) {
				returnpkt(mypkt, myproc, DOSFALSE, ERROR_NO_FREE_STORE);
				branchto exit;
			}
			mynode->dn_Task = &myproc->pr_MsgPort;
			returnpkt(mypkt, myproc, DOSTRUE, 0);
		} else {
			returnpkt(mypkt, myproc, DOSFALSE, ERROR_NO_FREE_STORE);
			branchto exit;
		}
		DB(P("'%s', %ld flags %ld\n",
				default_device, default_unit, default_flags));
	}

top:

	/*
	 *		Main loop. We sit here waiting for DOS requests and handle
	 *		them as best we can.
	 */
	done = 0;
	while (!done) {
		register DosPacket *mypkt;		/* Dos packet received		*/
		register MyFile *file;			/* Pointer to current File	*/
		long type;						/* Type of packet			*/

		mypkt = taskwait(myproc);		/* Wait/get next packet 	*/
		type  = mypkt->dp_Type;			/* Packet type				*/
		mypkt->dp_Res1 = DOSTRUE;		/* Default return value 	*/
		mypkt->dp_Res2 = 0; 			/* Default no error 		*/

		/*
		 *		Extract File pointer.  Doesn't apply to Open()
		 */
		file = (MyFile *)mypkt->dp_Arg1;

		DB(P("%-12s arg3 %8lx, file: %8lx arg2: %8lx\n",
			  actname(mypkt->dp_Type), mypkt->dp_Arg3, file, mypkt->dp_Arg2));

		switch (type) {

		case ACTION_FIND_INPUT:
		case ACTION_FIND_OUTPUT:
		case ACTION_FIND_UPDATE:
			/*
			 *		Handles opening any file on this device.
			 */
			{
				IBMInfo ibm[1];	
				uchar *name = BADDR(mypkt->dp_Arg3);

				/* Parse the given filename */
				if (*name)
					strncpy(buf, name + 1, *name);
				buf[*name] = '\0';
				DB(P("Open file: %s\n", buf));

				if (!ParseFilename(buf, ibm, env) ||
					(ibm->newfileonly && type != ACTION_FIND_OUTPUT)) {
					mypkt->dp_Res1 = DOSFALSE;
					mypkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
				} else {
					file = OpenMyFile(ibm, blocksize);
					if (file) {
						struct FileHandle *fh = BADDR(mypkt->dp_Arg1);

						memset(fh, 0, sizeof(*fh));
						fh->fh_Arg1 = (long)file;
						fh->fh_Pos  = -1;
						fh->fh_End  = -1;
						fh->fh_Type = &myproc->pr_MsgPort;
						opencount++;
					} else {
						mypkt->dp_Res1 = DOSFALSE;
						mypkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
					}
				}
			}
returnpkt:
			returnpktplain(mypkt, myproc);
			break;

		case ACTION_END:							/* CLOSE	*/
			DB(P("Close File %08lx\n", file));
			FreeMem(file, sizeof(*file));
			returnpktplain(mypkt, myproc);
			--opencount;
			break;

		case ACTION_READ:					/* Arg2: buffer */
			{								/* Arg3: size	*/
				uchar *buffer   = (uchar *)mypkt->dp_Arg2;
				ULONG oldoffset = file->offset;
				ULONG size = mypkt->dp_Arg3;
				ULONG done = 0;
				int error  = 0;

				DB(P("Read File %08lx ", file));
				DB(P("buffer %08lx  size %08lx offset %08lx\n",
					 		   buffer, size, file->offset));
#if SNOOP
				P("Read : Block No. = %8lx, # Blocks = %8lx\n",
				   file->offset >> 9, size >> 9);
#endif
				/*
				 *	 Don't read past the end of the virtual file
				 */
				done = file->maxoffset - file->offset;
				if (size > done)
					 size = done;
				mypkt->dp_Res1 = size;

				/*
				 *		Handle reads from the bootblock if this is
				 *		an autoboot disk. Will also be used if any other
				 *		kind of header is attached to the front of the
				 *		file (such as a mountlist entry, for example.)
				 */
				if (size && file->offset < file->datasize) {
					ULONG bsize = MIN(size, file->datasize - file->offset);
					uchar *header = (uchar *)file->data;
						
					memcpy(buffer, header + file->offset, bsize);
					file->offset += bsize;
					size   -= bsize;
					buffer += bsize;
				}
				if (size && (file->offset & block_offset_mask)) {
					/*
					 *		We want all disk i/o via scsi.device to be
					 *		on sector boundaries, to prevent scsi.device
					 *		getting confused. So, if we are not aligned
					 *		on a sector, we read the whole sector anyway,
					 *		then just copy what we need.
					 */
					ULONG blkoffset  = file->offset & block_align_mask;
					ULONG byteoffset = file->offset & block_offset_mask;
					ULONG numbytes   = MIN(blocksize - byteoffset, size);

					DB(P("Read: first sectors, offset = %8ld, num = %8ld\n",
						  file->offset, numbytes));
					error |= PerformIO(maskdata, ioreq, CMD_READ, oneblock,
									   file->start + blkoffset, blocksize);
					memcpy(buffer, oneblock + file->offset-blkoffset, numbytes);
					file->offset += numbytes;
					buffer       += numbytes;
					size         -= numbytes;
				}
				/*
				 *		Now we're sector-aligned. Read in as many full
				 *		blocks as possible. (This is by far the most
				 *		common case, since almost all of our requests will
				 *		come from the bridgeboard software, which always
				 *		looks for data on sector boundaries.
				 */		
				if (size >= blocksize) {
					ULONG numbytes = size & block_align_mask;

					DB(P("Read: full sectors, offset = %8ld, num = %8ld\n",
						  file->offset, numbytes));
					error |= PerformIO(maskdata, ioreq, CMD_READ, buffer,
							  		   file->start + file->offset, numbytes);
					file->offset += numbytes;
					buffer       += numbytes;
					size         -= numbytes;
				}
				/*
				 *		Now check for any partial read from the final
				 *		sector. As above, we handle this by reading the
				 *		entire sector into a local buffer, then copying
				 *		the required data into the caller's buffer.
				 */
				if (size) {
					DB(P("Read: last sector,   offset = %8ld, num = %8ld\n",
						  file->offset, size));
					error |= PerformIO(maskdata, ioreq, CMD_READ, oneblock,
							  		   file->start + file->offset, blocksize);
					memcpy(buffer, oneblock, size);
					file->offset += size;
					buffer       += size;
					size          = 0;
				}
				/*
				 *		Now check did an error occur at any stage
				 */
				if (error) {
					mypkt->dp_Res1 = -1;
					mypkt->dp_Res2 = ERROR_READ_PROTECTED; /* Seems best */
					file->offset   = oldoffset;
				}
			}
			branchto returnpkt;

		case ACTION_WRITE:					/* Arg2: buffer */
			{								/* Arg3: size	*/
				uchar *buffer   = (uchar *)mypkt->dp_Arg2;
				ULONG oldoffset = file->offset;
				ULONG size = mypkt->dp_Arg3;
				ULONG done = 0;
				int error  = 0;

				DB(P("Write: File = %8lx ", file));
				DB(P("buffer %08lx  size %08lx offset %08lx\n",
					 		   buffer, size, file->offset));
#if SNOOP
				P("Write: Block No. = %8lx, # Blocks = %8lx\n",
				   file->offset >> 9, size >> 9);
#endif
				/*
				 *	 Don't write past the end of the virtual file
				 */
				done = file->maxoffset - file->offset;
				if (size > done)
					 size = done;
				mypkt->dp_Res1 = size;

				/*
				 *		Handle writes to the header of the file, if any.
				 *		We do this by just plain ignoring such writes,
				 *		since they aren't stored permanently anyway.
				 */
				if (size && file->offset < file->datasize) {
					ULONG bsize = MIN(size, file->datasize - file->offset);
						
					file->offset += bsize;
					size   -= bsize;
					buffer += bsize;
				}
				if (size && (file->offset & block_offset_mask)) {
					/*
					 *		We want all disk i/o via scsi.device to be
					 *		on sector boundaries, to prevent scsi.device
					 *		getting confused. So, if we are not aligned
					 *		on a sector, we read the whole sector, modify
					 *		what's necessary, then write it again.
					 */
					ULONG blkoffset  = file->offset & block_align_mask;
					ULONG byteoffset = file->offset & block_offset_mask;
					ULONG numbytes   = MIN(blocksize - byteoffset, size);

					DB(P("Write: first sectors, offset = %8ld, num = %8ld\n",
						  file->offset, numbytes));
					error |= PerformIO(maskdata, ioreq, CMD_READ, oneblock,
									   file->start + blkoffset, blocksize);
					memcpy(oneblock + file->offset-blkoffset, buffer, numbytes);
					if (!error) {
						error |= PerformIO(maskdata, ioreq, CMD_WRITE,
								 oneblock, file->start + blkoffset, blocksize);
					}
					file->offset += numbytes;
					buffer       += numbytes;
					size         -= numbytes;
				}
				/*
				 *		Now we're sector-aligned. Write as many full
				 *		blocks as possible. (This is by far the most
				 *		common case, since almost all of our requests will
				 *		come from the bridgeboard software, which always
				 *		mainpulates data on sector boundaries.
				 */		
				if (size >= blocksize) {
					ULONG numbytes = size & block_align_mask;

					DB(P("Write: full sectors, offset = %8ld, num = %8ld\n",
						  file->offset, numbytes));
					error |= PerformIO(maskdata, ioreq, CMD_WRITE, buffer,
							  		   file->start + file->offset, numbytes);
					file->offset += numbytes;
					buffer       += numbytes;
					size         -= numbytes;
				}
				/*
				 *		Now check for any partial writes to the final
				 *		sector. As above, we handle this by reading the
				 *		entire sector into a local buffer, modifying the
				 *		desired info, then writing it back again.
				 */
				if (size) {
					DB(P("Write: last sector,   offset = %8ld, num = %8ld\n",
						  file->offset, size));
					error |= PerformIO(maskdata, ioreq, CMD_READ, oneblock,
							  		   file->start + file->offset, blocksize);
					memcpy(oneblock, buffer, size);
					if (!error) {
						error |= PerformIO(maskdata, ioreq, CMD_WRITE, oneblock,
								 file->start + file->offset, blocksize);
					}
					file->offset += size;
					buffer       += size;
					size          = 0;
				}
				/*
				 *		Now check did an error occur at any stage
				 */
				if (error) {
					mypkt->dp_Res1 = -1;
					mypkt->dp_Res2 = ERROR_WRITE_PROTECTED; /* Seems best */
					file->offset   = oldoffset;
				}
			}
			branchto returnpkt;

		case ACTION_DIE:
			expunge = 1;
			if (opencount == 0)
				 done = 1;
			branchto returnpkt;

		case ACTION_SEEK:
		{
			long offset = -1;

			switch (mypkt->dp_Arg3) {	/* seek mode */

			case OFFSET_BEGINNING:
				offset = mypkt->dp_Arg2;
				break;

			case OFFSET_CURRENT:
				offset = file->offset + mypkt->dp_Arg2;
				break;

			case OFFSET_END:
				offset = file->maxoffset + mypkt->dp_Arg2;
				break;
			}
			if (offset >= 0 && offset <= file->maxoffset) {
				mypkt->dp_Res1 = offset;
				file->offset   = offset;
			} else {
				mypkt->dp_Res1 = -1;
				mypkt->dp_Res2 = ERROR_SEEK_ERROR;
			}
			branchto returnpkt;
		}

		case ACTION_LOCATE_OBJECT:	/* Sort of like Open() */
		{
			uchar   *name = BADDR(mypkt->dp_Arg2);
			MyLock  *mylock = NULL;
			IBMInfo ibm[1];	

			/* Parse the given filename */
			if (*name)
				strncpy(buf, name + 1, *name);
			buf[*name] = '\0';
			DB(P("Lock file: %s\n", buf));

			if (!ParseFilename(buf, ibm, env) ||
				(ibm->newfileonly && mypkt->dp_Arg3 != ACCESS_WRITE)) {
				mypkt->dp_Res1 = DOSFALSE;
				mypkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
			} else if (mylock = AllocMem(sizeof (MyLock), MEMF_CLEAR)) {
				/*
					*		Allocated a lock node okay, so now just copy
					*		the information about the partition gleaned
					*		from the filename. This can then be examined
					*		by Examine() etc. if necessary.
					*/
				mylock->fl.fl_Volume = CTOB(mynode);
				mylock->fl.fl_Access = mypkt->dp_Arg3;
				mylock->fl.fl_Task   = &myproc->pr_MsgPort;
				mylock->ibm          = *ibm;
				opencount++;
			} else {
				mypkt->dp_Res2 = ERROR_NO_FREE_STORE;
			}
			mypkt->dp_Res1 = CTOB(mylock);
			branchto returnpkt;
		}

		case ACTION_EXAMINE_OBJECT:
		{
			struct FileInfoBlock *fib = BADDR(mypkt->dp_Arg2);
			MyLock *mylock = BADDR(mypkt->dp_Arg1);
			IBMInfo *ibm = &mylock->ibm;

			fib->fib_DiskKey		= ibm->startblock;
			fib->fib_DirEntryType	= ST_FILE;
			strcpy(fib->fib_FileName+1, "JanusFile");
			fib->fib_FileName[0] 	= strlen(fib->fib_FileName+1);
			fib->fib_Protection  	= 0;
			fib->fib_EntryType   	= fib->fib_DirEntryType;
			fib->fib_Size        	= ibm->numblocks * blocksize;
			fib->fib_NumBlocks   	= ibm->numblocks;
			DateStamp(&fib->fib_Date);	/* Use current time */
			strcpy(fib->fib_Comment+1, DEF_COMMENT);
			fib->fib_Comment[0] 	= strlen(fib->fib_Comment+1);
			branchto returnpkt;
		}

		case ACTION_EXAMINE_NEXT:
			mypkt->dp_Res1 = DOSFALSE;
			mypkt->dp_Res2 = ERROR_NO_MORE_ENTRIES;
			branchto returnpkt;

		case ACTION_COPY_DIR:
		{
			MyLock *mylock = NULL;

			if (mylock = AllocMem(sizeof (MyLock), MEMF_CLEAR)) {
				*mylock = *(MyLock *)BADDR(mypkt->dp_Arg1);
				opencount++;
			} else {
				mypkt->dp_Res2 = ERROR_NO_FREE_STORE;
			}
			mypkt->dp_Res1 = CTOB(mylock);
			branchto returnpkt;
		}

		case ACTION_FREE_LOCK:
		{
			if (mypkt->dp_Arg1) {
				FreeMem(BADDR(mypkt->dp_Arg1), sizeof(MyLock));
				opencount--;
			}
			branchto returnpkt;
		}

		case ACTION_FH_FROM_LOCK:
		{
			/*
			 *		The lock we're passed in has already been initialised
			 *		with the appropriate disk info, so we just pretty
			 *		much duplicate the Open() code here to create and
			 *		initialise the file handle based on this.
			 *		opening. This 'hack' only works because we are
			 *		not interpreting the filename in any particular way.
			 */
			MyLock *mylock = BADDR(mypkt->dp_Arg2);

			file = OpenMyFile(&mylock->ibm, blocksize);
			if (file) {
				struct FileHandle *fh = BADDR(mypkt->dp_Arg1);

				memset(fh, 0, sizeof(*fh));
				fh->fh_Arg1 = (long)file;
				fh->fh_Pos  = -1;
				fh->fh_End  = -1;
				fh->fh_Type = &myproc->pr_MsgPort;
				FreeMem(mylock, sizeof (MyLock));
			} else {
				mypkt->dp_Res1 = DOSFALSE;
				mypkt->dp_Res2 = ERROR_NO_FREE_STORE;
			}
			branchto returnpkt;
		}

		case ACTION_PARENT:
			mypkt->dp_Res1 = 0;
			mypkt->dp_Res2 = ERROR_DIR_NOT_FOUND;
			branchto returnpkt;

		case ACTION_DISK_INFO:
		case ACTION_INFO:
		{
			struct InfoData *info;
			
			if (type == ACTION_DISK_INFO)
				info = BADDR(mypkt->dp_Arg1);
			else /* ACTION_INFO */
				info = BADDR(mypkt->dp_Arg2);

			info->id_NumSoftErrors = 0;
			info->id_UnitNumber    = default_unit;
			info->id_DiskState     = ID_VALIDATED;
			info->id_NumBlocks     = numblocks;
			info->id_NumBlocksUsed = numblocks;
			info->id_BytesPerBlock = env->de_SizeBlock * 4;
			info->id_DiskType      = ID_DOS_DISK;
			info->id_VolumeNode    = CTOB(mynode);
			info->id_InUse         = 1;
			branchto returnpkt;
		}
			
		case ACTION_CURRENT_VOLUME:
			mypkt->dp_Res1 = CTOB(mynode);
			branchto returnpkt;

		case ACTION_IS_FILESYSTEM:
		case ACTION_SET_PROTECT:
		case ACTION_SET_COMMENT:
		case ACTION_SET_DATE:
		case ACTION_INHIBIT:
		case ACTION_FLUSH:
			/*
			 *		We make these into NOP's rather than letting them fail,
			 *		to stop application programs from getting worried.
			 */
			branchto returnpkt;

		default:
			returnpkt(mypkt, myproc, DOSFALSE, ERROR_ACTION_NOT_KNOWN);
			break;
		} /* end switch */

		DB(P("             Res1 %08lx   Res2 %08lx\n",
			  mypkt->dp_Res1, mypkt->dp_Res2));
	} /* end while (done) */

	/*
	 *	Can only exit if no messages pending.  There might be a window
	 *	here, but there is nothing that can be done about it.
	 */

#if DEBUG
	if (!expunge)
		branchto top;
#endif

	Forbid();
	if (taskpktrdy(myproc)) {
		Permit();
		branchto top;
	}

	mynode->dn_Task = NULL;
	Permit();

exit:
	if (ioreq->io_Device)	CloseDevice(ioreq);
	if (ioreq)				DeleteStdIO(ioreq);
	if (ioport)				DeletePort(ioport);
	if (oneblock)			FreeMem(oneblock, blocksize);

	if (maskdata->xferbuffer) {
		FreeMem(maskdata->xferbuffer, maskdata->xfersize);
		maskdata->xferbuffer = NULL;
	}

	DB(P("Exiting from device.\n"));

	/* We are a process "so we fall off the end of the world" */

	if (expunge) {
		BPTR mycode = mynode->dn_SegList;

		Forbid();
		mynode->dn_SegList = NULL;
		UnLoadSeg(mycode);
		/* We execute in unallocated memory now... and Forbid()den. */
	}
	CloseLibrary(DOSBase);

	/* MUST fall through */
}

/*
 *		PerformIO(maskdata, ioreq, cmd, buffer, offset, size)
 *
 *		Carries out specified IO as per parameters and returns when
 *		complete. Maskdata is used to check for disk i/o which doesn't
 *		fall within the scope of the supplied buffer. Such i/o is
 *		handled by passing it through an intermediary buffer first,
 *		then copying it by hand to the required destination.
 *
 *		Note that offset and size must be multiples of the device's
 *		blocksize, or the device (especially scsi.device) may lock up.
 *
 *		Returns -1 if an error occurred.
 */
ULONG PerformIO(MaskData *maskdata, IOStdReq *ioreq, ULONG cmd, void *buffer,
				ULONG offset, ULONG size)
{
	DB(P("PerfIO buffer %08lx  size %08lx offset %08lx\n",
				  buffer, size, offset));
	ioreq->io_Command	= cmd;
	ioreq->io_Length	= size;
	ioreq->io_Data		= buffer;
	ioreq->io_Offset	= offset;
	DoIO(ioreq);
	if (ioreq->io_Actual != size || ioreq->io_Error)
		return (-1);
	else
		return (0);
}

/*
 *		ParseFilename(filename, ibminfo, diskenv)
 *
 *		This function parses the specified filename and fills in the
 *		ibminfo structure with details of the geometry of the disk as
 *		it should appear to the PC. Defaults are taken from diskenv as
 *		necessary.
 *
 *		We can fairly easily calculate the IBM sector and head values.
 *		We manipulate the starting block info a bit, however, to
 *		arrange that the IBM partition data starts an integral number
 *		of cylinders from the start of the physical disk. This is not
 *		strictly required (after all, the bridgeboard software is
 *		accessing the whole thing as a file anyway and couldn't care
 *		less) but it helps filesystems like the MSDOSFileSystem in
 *		CrossDOS which expect such things. It costs us a tiny amount
 *		of usable disk space, but not enough to worry about.
 *		
 *		The filename itself can have a number of options, as detailed
 *		at the start of this source code. If the filename doesn't
 *		make sense, a zero is returned, else non-zero.
 */
int ParseFilename(uchar *filename, IBMInfo *ibm, DosEnvec *env)
{
	char *p;
	long startblock;
	long numblocks;
	long icylsize;

	ibm->heads       = 0;
	ibm->sectors     = 0;
	ibm->isautoboot  = 0;
	ibm->newfileonly = 0;

	p = filename;
	while (*p) {
		if (*p == '/') {
			p++;
			if (strnicmp(p, "AMIGA", 5) == 0) {
				ibm->heads   = env->de_Surfaces;
				ibm->sectors = env->de_BlocksPerTrack;
				p += 5;
			} else if (strnicmp(p, "IBM", 3) == 0) {
				ibm->heads   = IBM_HEADS;
				ibm->sectors = IBM_SECTORS;
				p += 3;
			} else if (strnicmp(p, "AUTO", 4) == 0) {
				ibm->isautoboot = 1;
				p += 4;
			} else if (strnicmp(p, "NEW", 3) == 0) {
				ibm->newfileonly = 1;
				p += 3;
			} else if (*p == 's' || *p == 'S') {
				ibm->sectors = atoi(++p);
				while (*p && *p != '/')
					p++;
			} else if (*p == 'h' || *p == 'H') {
				ibm->heads = atoi(++p);
				while (*p && *p != '/')
					p++;
			} else
				return (0);
		} else
			*p++;
	}
	if (ibm->heads   == 0)	ibm->heads   = IBM_HEADS;
	if (ibm->sectors == 0)	ibm->sectors = IBM_SECTORS;

	icylsize   = ibm->heads * ibm->sectors;
	startblock = env->de_LowCyl * env->de_Surfaces * env->de_BlocksPerTrack;
	numblocks  = env->de_BlocksPerTrack * env->de_Surfaces *
				 (env->de_HighCyl - env->de_LowCyl + 1);

	ibm->startblock = ((startblock + icylsize - 1) / icylsize) * icylsize;
	ibm->numcyls    = (numblocks - (ibm->startblock - startblock)) / icylsize;
	ibm->numblocks  = ibm->numcyls * icylsize;
	return (1);
}

/*
 *		OpenMyFile(IBMInfo *ibm, blocksize)
 *
 *		Attempts to create and initialise a MyFile structure suitable
 *		for accessing the section of the disk defined in ibm. This
 *		includes creating a fake Janus bootblock, just in case it's
 *		needed. Returns a pointer to the file structure if successfull,
 *		or NULL if not enough memory.
 *
 *		Blocksize is the physical size of a block on the disk.
 */
MyFile *OpenMyFile(IBMInfo *ibm, ULONG blocksize)
{
	MyFile *file;
	BootBlock *bb;
	ULONG headersize = 0;

	if (ibm->isautoboot)
		headersize = sizeof(BootBlock);

	file = (MyFile *)AllocMem(sizeof(MyFile) + headersize, MEMF_CLEAR);
	if (!file)
		return (NULL);

	file->start		 = ibm->startblock * blocksize;
	file->offset	 = 0;
	file->maxoffset	 = ibm->numblocks * blocksize;
	file->datasize   = headersize;

	if (ibm->isautoboot) {
		file->maxoffset += headersize;
		file->start     -= headersize;
		bb = (BootBlock *)file->data;
		strcpy(bb->title, "ABOOT");
		bb->heads     = ibm->heads;
		bb->sectors   = ibm->sectors;
		bb->cylinders = ibm->numcyls;
	}
	return (file);
}

#if DEBUG
/*
 *		Translates action numbers to action names to ease debugging
 */
uchar *actname(int actnum)
{
	switch (actnum) {
		case ACTION_FIND_INPUT:		return ("FIND_INPUT");
		case ACTION_FIND_OUTPUT:	return ("FIND_OUTPUT");
 		case ACTION_FIND_UPDATE:	return ("FIND_UPDATE");
		case ACTION_END:			return ("END");
 		case ACTION_READ:			return ("READ");
 		case ACTION_WRITE:			return ("WRITE");
		case ACTION_DIE:			return ("DIE");
		case ACTION_SEEK:			return ("SEEK");
 		case ACTION_LOCATE_OBJECT:	return ("LOCATE_OBJ");
 		case ACTION_EXAMINE_OBJECT:	return ("EXAMINE_OBJ");
		case ACTION_EXAMINE_NEXT:	return ("EXAMINE_NEXT");
		case ACTION_COPY_DIR:		return ("COPY_DIR");
		case ACTION_FREE_LOCK:		return ("FREE_LOCK");
		case ACTION_FH_FROM_LOCK:	return ("FH_FROM_LOCK");
		case ACTION_PARENT:			return ("PARENT");
		case ACTION_DISK_INFO:		return ("DISK_INFO");
		case ACTION_INFO:			return ("INFO");
		case ACTION_IS_FILESYSTEM:	return ("IS_FILESYS");
		case ACTION_SET_PROTECT:	return ("SET_PROTECT");
		case ACTION_SET_COMMENT:	return ("SET_COMMENT");
		case ACTION_SET_DATE:		return ("SET_DATE");
		case ACTION_INHIBIT:		return ("INHIBIT");
		case ACTION_CURRENT_VOLUME:	return ("CURRENT_VOL");
		case ACTION_FLUSH:			return ("FLUSH");
		default:
			DB(P("Action_%04ld", actnum));
			return (" ");
	}
}
#endif
