/* $Revision Header * Header built automatically - do not edit! *************
 *
 *	(C) Copyright 1991 by Olaf Barthel
 *
 *	Name .....:	Flat-Handler.c
 *	Created ..:	Saturday 11-May-91 17:55
 *	Revision .:	3
 *
 *	Date		Author		Comment
 *	=========	========	====================
 *	26-Jul-91	Olsen		Added ACTION_COPY_DIR
 *	11-Jul-91	Olsen		Minor fixes.
 *	11-May-91	Olsen		Created this file!
 *
 * $Revision Header ********************************************************/

	/* Standard FS error types. */

enum	{ ERR_WRITEPROTECT,ERR_NODISK,ERR_UNREADABLE,ERR_WRITEERROR };

	/* This is a link node used both for locks and filehandles. */

struct FlatNode
{
	struct FlatNode		*fn_Succ;	/* Vanilla node head. */
	struct FlatNode		*fn_Pred;

	ULONG			 fn_UniqueID;	/* A unique ID. */

	LONG			 fn_Mode;	/* Either shared or exclusive. */

	struct DeviceNode	*fn_DevInfo;	/* Pointer to a device node,
						 * needed by ExNext and the like.
						 */
	ULONG			 fn_BlockSize;	/* Size of a block (512 bytes are standard). */
	ULONG			 fn_FirstBlock;	/* The first accessible block. */
	ULONG			 fn_NumBlocks;	/* Maximum number of available blocks. */

	LONG			 fn_Position;	/* Current file position in bytes. */
	struct FileLock		 fn_Lock;	/* A dummy file lock. */

	UBYTE			 fn_Name[40];	/* Name of this file. */

	struct MsgPort		*fn_DiskPort;	/* Driver data. */
	struct IOExtTD		*fn_DiskRequest;
	APTR			 fn_DiskBuffer;

	BYTE			 fn_CheckCount;	/* The disk state is checked
						 * every tenth r/w attempt,
						 * this byte keeps the count.
						 */
};

	/* This list keeps all the locks and filehandles. */

struct List			 FlatList;

	/* Each open/lock call increments this counter to
	 * guarantee that each item receives a unique identifier.
	 */

ULONG				 UniqueCounter = 0;

	/* Shared library identifiers. */

struct ExecBase			*SysBase = NULL;
struct DosLibrary		*DOSBase = NULL;
struct IntuitionBase		*IntuitionBase = NULL;

	/* Prototypes for this module. */

LONG __saveds			HandlerEntry(VOID);

LONG __regargs			DoRead(struct FlatNode *FlatNode,LONG Size,UBYTE *Buffer,struct Process *Caller);
LONG __regargs			DoWrite(struct FlatNode *FlatNode,LONG Size,UBYTE *Buffer,struct Process *Caller);

UBYTE * __regargs		BaseName(UBYTE *String);

UBYTE __regargs			Local2Upper(UBYTE c);
UBYTE __regargs			StrCmp(UBYTE *a,UBYTE *b);

struct FlatNode * __regargs	FindFlatNodeByID(ULONG UniqueID);
struct FlatNode * __regargs	FindFlatNodeByName(UBYTE *Name);

VOID __regargs			BtoCStr(UBYTE *Name,BSTR String,LONG MaxLength);

LONG __regargs			ShowRequest(APTR WindowPtr,BYTE Type,UBYTE *Drive);

struct DeviceNode * __regargs	FindDevice(struct DeviceNode *LastNode,struct FileSysStartupMsg	**Startup,struct DosEnvec **DosEnvec,UBYTE *Name);

VOID __regargs			DeleteNode(struct FlatNode *FlatNode);
struct FlatNode * __regargs	CreateNode(LONG Type,UBYTE *Name);

VOID __regargs			ReturnPacket(struct DosPacket *Packet,ULONG Res1,ULONG Res2,struct Process *HandlerProc);
struct DosPacket * __regargs	WaitPacket(struct Process *HandlerProc);

	/* HandlerEntry():
	 *
	 *	Entry point for this module.
	 */

LONG __saveds
HandlerEntry()
{
	struct Process			*HandlerProc;

	struct FileHandle		*FileHandle;
	struct FileLock			*FileLock;

	LONG				 ReadBytes,WriteBytes,Bytes;
	LONG				 NewPosition;
	struct FileInfoBlock		*FileInfo;
	UBYTE				*FileName;
	UBYTE				 NameBuffer[257];

	struct DosPacket		*FlatPacket;
	struct DeviceNode		*FlatDevNode;

	struct FlatNode			*FlatNode;

		/* Set up SysBase. */

	SysBase = *(struct ExecBase **)4;

		/* Know who we are. */

	HandlerProc = (struct Process *)SysBase -> ThisTask;

		/* Started from Shell (oops!)? */

	if(!HandlerProc -> pr_CLI)
	{
			/* Wait for startup packet. */

		FlatPacket = WaitPacket(HandlerProc);

			/* Clear the list. */

		NewList(&FlatList);

			/* Pick up the pointer to our DeviceNode. */

		FlatDevNode = (struct DeviceNode *)BADDR(FlatPacket -> dp_Arg3);

			/* Install ourselves at the other hand. */

		FlatDevNode -> dn_Task = &HandlerProc -> pr_MsgPort;

			/* Open DOS; we are not making DOS calls but
			 * rather use the base to scan for block-
			 * mapped devices.
			 */

		if(!(DOSBase = (struct DosLibrary *)OpenLibrary("dos.library",0)))
		{
			ReturnPacket(FlatPacket,DOSFALSE,FlatPacket -> dp_Res2,HandlerProc);
			goto FallOff;
		}

			/* Open Intuition; we might want to put up
			 * auto-requesters.
			 */

		if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0)))
		{
			ReturnPacket(FlatPacket,DOSFALSE,FlatPacket -> dp_Res2,HandlerProc);
			goto FallOff;
		}

			/* Initialization finished, now return the
			 * startup packet.
			 */

		ReturnPacket(FlatPacket,DOSTRUE,FlatPacket -> dp_Res2,HandlerProc);

			/* Go into loop waiting for data packets. */

		FOREVER
		{
				/* Wait for packet. */

			FlatPacket = WaitPacket(HandlerProc);

				/* Examine the packet type. */

			switch(FlatPacket -> dp_Type)
			{
					/* Obtain a filelock. */

				case ACTION_LOCATE_OBJECT:

						/* Convert the file name. */

					BtoCStr(NameBuffer,FlatPacket -> dp_Arg2,256);

						/* Are we to return a lock
						 * to a file or a lock to the
						 * root directory?
						 */

					if(FileName = BaseName(NameBuffer))
					{
							/* Look for a file of this name. */

						if(FlatNode = FindFlatNodeByName(FileName))
						{
								/* See if the file is currently locked. */

							if((FlatNode -> fn_Mode != FlatPacket -> dp_Arg3) || (FlatPacket -> dp_Arg3 == EXCLUSIVE_LOCK && FlatNode -> fn_Mode == EXCLUSIVE_LOCK))
							{
								ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_IN_USE,HandlerProc);
								break;
							}
						}

							/* Create a new item and add it to the list. */

						if(FlatNode = CreateNode(FlatPacket -> dp_Arg3,FileName))
						{
							AddTail(&FlatList,(struct Node *)FlatNode);

								/* Initialize the default data so DOS will
								 * get along with us.
								 */

							FlatNode -> fn_Lock . fl_Access	= FlatPacket -> dp_Arg3;
							FlatNode -> fn_Lock . fl_Task	= &HandlerProc -> pr_MsgPort;
							FlatNode -> fn_Lock . fl_Volume	= MKBADDR(FlatDevNode);
							FlatNode -> fn_Lock . fl_Key	= FlatNode -> fn_UniqueID;

							FlatPacket -> dp_Res1 = MKBADDR(&FlatNode -> fn_Lock);

							strcpy(FlatNode -> fn_Name,FileName);

							ReturnPacket(FlatPacket,FlatPacket -> dp_Res1,FlatPacket -> dp_Res2,HandlerProc);
						}
						else
							ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_NOT_FOUND,HandlerProc);
					}
					else
					{
						if(FlatNode = CreateNode(FlatPacket -> dp_Arg3,NULL))
						{
							AddTail(&FlatList,(struct Node *)FlatNode);

							FlatNode -> fn_Lock . fl_Access	= FlatPacket -> dp_Arg3;
							FlatNode -> fn_Lock . fl_Task	= &HandlerProc -> pr_MsgPort;
							FlatNode -> fn_Lock . fl_Volume	= MKBADDR(FlatDevNode);
							FlatNode -> fn_Lock . fl_Key	= FlatNode -> fn_UniqueID;

							FlatPacket -> dp_Res1 = MKBADDR(&FlatNode -> fn_Lock);

							ReturnPacket(FlatPacket,FlatPacket -> dp_Res1,FlatPacket -> dp_Res2,HandlerProc);
						}
						else
							ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_NOT_FOUND,HandlerProc);
					}

					break;

					/* Free a lock obtained above. */

				case ACTION_FREE_LOCK:

						/* Is the lock part of the list? */

					if(FlatPacket -> dp_Arg1)
					{
						if(FlatNode = FindFlatNodeByID(((struct FileLock *)BADDR(FlatPacket -> dp_Arg1)) -> fl_Key))
						{
							Remove((struct Node *)FlatNode);

							DeleteNode(FlatNode);
						}
					}

					FlatPacket -> dp_Res1 = DOSTRUE;

					ReturnPacket(FlatPacket,FlatPacket -> dp_Res1,FlatPacket -> dp_Res2,HandlerProc);

					break;

					/* Duplicate a shared lock. */

				case ACTION_COPY_DIR:

						/* Make sure a ZERO lock gives
						 * a more or less valid return
						 * code.
						 */

					FlatPacket -> dp_Res1 = 0;
					FlatPacket -> dp_Res2 = 0;

						/* Are we to duplicate a non-ZERO lock? */

					if(FlatPacket -> dp_Arg1)
					{
						FileLock = (struct FileLock *)BADDR(FlatPacket -> dp_Arg1);

							/* Try to find the corresponding list entry. */

						if(FlatNode = FindFlatNodeByID(FileLock -> fl_Key))
						{
								/* Only shared locks may be duplicated. */

							if(FlatNode -> fn_Mode == EXCLUSIVE_LOCK)
							{
								FlatPacket -> dp_Res1 = DOSFALSE;
								FlatPacket -> dp_Res2 = ERROR_OBJECT_IN_USE;
							}
							else
							{
								struct FlatNode *AnotherNode;

									/* Create a new node. */

								if(FlatNode -> fn_Name[0])
									AnotherNode = CreateNode(SHARED_LOCK,FlatNode -> fn_Name);
								else
									AnotherNode = CreateNode(SHARED_LOCK,NULL);

									/* Did we get what we wanted? */

								if(AnotherNode)
								{
										/* Not quite so unique I suppose. */

									AnotherNode -> fn_UniqueID = FlatNode -> fn_UniqueID;

										/* Add the freshly created lock
										 * to the list.
										 */

									AddTail(&FlatList,(struct Node *)AnotherNode);

										/* Fill in the Lock data. */

									AnotherNode -> fn_Lock . fl_Access	= SHARED_LOCK;
									AnotherNode -> fn_Lock . fl_Task	= &HandlerProc -> pr_MsgPort;
									AnotherNode -> fn_Lock . fl_Volume	= MKBADDR(FlatDevNode);
									AnotherNode -> fn_Lock . fl_Key		= AnotherNode -> fn_UniqueID;

										/* Successful return. */

									FlatPacket -> dp_Res1 = MKBADDR(&AnotherNode -> fn_Lock);
								}
								else
								{
										/* Failed to create node. */

									FlatPacket -> dp_Res1 = DOSFALSE;
									FlatPacket -> dp_Res2 = ERROR_NO_FREE_STORE;
								}
							}
						}
						else
						{
								/* Couldn't find the lock. */

							FlatPacket -> dp_Res1 = DOSFALSE;
							FlatPacket -> dp_Res2 = ERROR_OBJECT_NOT_FOUND;
						}
					}

					ReturnPacket(FlatPacket,FlatPacket -> dp_Res1,FlatPacket -> dp_Res2,HandlerProc);

					break;

					/* Examine a file. */

				case ACTION_EXAMINE_OBJECT:

						/* Get filelock and fileinfoblock in handy variables. */

					FileLock = (struct FileLock *)BADDR(FlatPacket -> dp_Arg1);
					FileInfo = (struct FileInfoBlock *)BADDR(FlatPacket -> dp_Arg2);

						/* Are both identifiers valid? */

					if(FileLock && FileInfo)
					{
						BYTE Success = FALSE;

							/* Can we find the item? */

						if(FlatNode = FindFlatNodeByID(FileLock -> fl_Key))
						{
							struct FileSysStartupMsg	*Startup;
							struct DosEnvec			*DosEnvec;

								/* Is it a file or a directory? */

							if(FlatNode -> fn_Name[0])
							{
									/* Find the approriate device. */

								if(FlatNode -> fn_DevInfo = FindDevice(NULL,&Startup,&DosEnvec,FlatNode -> fn_Name))
								{
									struct MsgPort *DiskPort;

										/* Create device driver data. */

									if(DiskPort = (struct MsgPort *)CreatePort(NULL,0))
									{
										struct IOExtTD *DiskRequest;

										if(DiskRequest = (struct IOExtTD *)CreateExtIO(DiskPort,sizeof(struct IOExtTD)))
										{
											BtoCStr(NameBuffer,Startup -> fssm_Device,256);

											if(!OpenDevice(NameBuffer,Startup -> fssm_Unit,DiskRequest,Startup -> fssm_Flags))
											{
													/* We are actually faking part of the data
													 * to be returned in the fileinfoblock.
													 * This isn't Unix and there are only two
													 * kinds of directory entries: files and
													 * directories. The protection bits are by
													 * default configured to mimic the state of
													 * the corresponding drive. If not write-
													 * enabled, the file will have the write
													 * access-bit cleared, if there is no disk
													 * in the drive, the read bit will be cleared,
													 * the file size will be zero as well.
													 */

												memset(FileInfo,0,sizeof(struct FileInfoBlock));

												FileInfo -> fib_DiskKey		= FileLock -> fl_Key;
												FileInfo -> fib_DirEntryType	= -3;
												FileInfo -> fib_Protection	= FIBF_EXECUTE|FIBF_DELETE;
												FileInfo -> fib_Size		= (DosEnvec -> de_HighCyl - DosEnvec -> de_LowCyl + 1) * DosEnvec -> de_BlocksPerTrack * DosEnvec -> de_Surfaces * (DosEnvec -> de_SizeBlock << 2);
												FileInfo -> fib_NumBlocks	= (DosEnvec -> de_HighCyl - DosEnvec -> de_LowCyl + 1) * DosEnvec -> de_BlocksPerTrack * DosEnvec -> de_Surfaces;

													/* The rest is mocked up or cleared
													 * with zeroes, a disk may not have
													 * a valid root block on it.
													 */

												DateStamp(&FileInfo -> fib_Date);

												memcpy(FileInfo -> fib_FileName,BADDR(FlatNode -> fn_DevInfo -> dn_Name),((UBYTE *)BADDR(FlatNode -> fn_DevInfo -> dn_Name))[0] + 1);

												DiskRequest -> iotd_Req . io_Command = TD_PROTSTATUS;

												if(!DoIO(DiskRequest))
												{
													if(DiskRequest -> iotd_Req . io_Actual)
														FileInfo -> fib_Protection |= FIBF_WRITE;
												}

												DiskRequest -> iotd_Req . io_Command = TD_CHANGESTATE;

												if(!DoIO(DiskRequest))
												{
													if(DiskRequest -> iotd_Req . io_Actual)
													{
														FileInfo -> fib_Protection |= FIBF_READ|FIBF_WRITE;

														FileInfo -> fib_Size = FileInfo -> fib_NumBlocks = 0;
													}
												}

												Success = TRUE;

												CloseDevice(DiskRequest);
											}

											DeleteExtIO(DiskRequest);
										}

										DeletePort(DiskPort);
									}
								}
							}
							else
							{
									/* This is very much the same as above,
									 * but this time it's the root directory
									 * we will create.
									 */

								memset(FileInfo,0,sizeof(struct FileInfoBlock));

								FileInfo -> fib_DiskKey		= FileLock -> fl_Key;
								FileInfo -> fib_DirEntryType	= 1;
								FileInfo -> fib_Protection	= FIBF_EXECUTE;
								FileInfo -> fib_Size		= 0;
								FileInfo -> fib_NumBlocks	= 0;

								DateStamp(&FileInfo -> fib_Date);

								strcpy(FileInfo -> fib_FileName,"\4FLAT");

								Success = TRUE;
							}
						}

						if(Success)
						{
							FlatPacket -> dp_Res1 = DOSTRUE;

							ReturnPacket(FlatPacket,FlatPacket -> dp_Res1,FlatPacket -> dp_Res2,HandlerProc);
						}
						else
							ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_NOT_FOUND,HandlerProc);
					}
					else
						ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_NOT_FOUND,HandlerProc);

					break;

					/* Examine the next directory entry (if available). */

				case ACTION_EXAMINE_NEXT:

						/* This works very much the same as above, with the
						 * exception that we are trying to gather information
						 * on the next available DosList Device entry.
						 */

					FileLock = (struct FileLock *)BADDR(FlatPacket -> dp_Arg1);
					FileInfo = (struct FileInfoBlock *)BADDR(FlatPacket -> dp_Arg2);

					if(FileLock && FileInfo)
					{
						BYTE Success = FALSE;

						if(FlatNode = FindFlatNodeByID(FileLock -> fl_Key))
						{
							struct FileSysStartupMsg	*Startup;
							struct DosEnvec			*DosEnvec;

							if(FlatNode -> fn_DevInfo = FindDevice(FlatNode -> fn_DevInfo,&Startup,&DosEnvec,NULL))
							{
								struct MsgPort *DiskPort;

								if(DiskPort = (struct MsgPort *)CreatePort(NULL,0))
								{
									struct IOExtTD *DiskRequest;

									if(DiskRequest = (struct IOExtTD *)CreateExtIO(DiskPort,sizeof(struct IOExtTD)))
									{
										BtoCStr(NameBuffer,Startup -> fssm_Device,256);

										if(!OpenDevice(NameBuffer,Startup -> fssm_Unit,DiskRequest,Startup -> fssm_Flags))
										{
											memset(FileInfo,0,sizeof(struct FileInfoBlock));

											FileInfo -> fib_DiskKey		= FileLock -> fl_Key;
											FileInfo -> fib_DirEntryType	= -1;
											FileInfo -> fib_Protection	= FIBF_EXECUTE|FIBF_DELETE;
											FileInfo -> fib_Size		= (DosEnvec -> de_HighCyl - DosEnvec -> de_LowCyl + 1) * DosEnvec -> de_BlocksPerTrack * DosEnvec -> de_Surfaces * (DosEnvec -> de_SizeBlock << 2);
											FileInfo -> fib_NumBlocks	= (DosEnvec -> de_HighCyl - DosEnvec -> de_LowCyl + 1) * DosEnvec -> de_BlocksPerTrack * DosEnvec -> de_Surfaces;

											DateStamp(&FileInfo -> fib_Date);

											memcpy(FileInfo -> fib_FileName,BADDR(FlatNode -> fn_DevInfo -> dn_Name),((UBYTE *)BADDR(FlatNode -> fn_DevInfo -> dn_Name))[0] + 1);

											DiskRequest -> iotd_Req . io_Command = TD_PROTSTATUS;

											if(!DoIO(DiskRequest))
											{
												if(DiskRequest -> iotd_Req . io_Actual)
													FileInfo -> fib_Protection |= FIBF_WRITE;
											}

											DiskRequest -> iotd_Req . io_Command = TD_CHANGESTATE;

											if(!DoIO(DiskRequest))
											{
												if(DiskRequest -> iotd_Req . io_Actual)
												{
													FileInfo -> fib_Protection |= FIBF_READ|FIBF_WRITE;

													FileInfo -> fib_Size = FileInfo -> fib_NumBlocks = 0;
												}
											}

											Success = TRUE;

											CloseDevice(DiskRequest);
										}

										DeleteExtIO(DiskRequest);
									}

									DeletePort(DiskPort);
								}
							}
							else
							{
								ReturnPacket(FlatPacket,DOSFALSE,ERROR_NO_MORE_ENTRIES,HandlerProc);
								break;
							}
						}

						if(Success)
						{
							FlatPacket -> dp_Res1 = DOSTRUE;

							ReturnPacket(FlatPacket,FlatPacket -> dp_Res1,FlatPacket -> dp_Res2,HandlerProc);
						}
						else
							ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_NOT_FOUND,HandlerProc);
					}
					else
						ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_NOT_FOUND,HandlerProc);

					break;

					/* Open any file for reading/writing. */

				case ACTION_FINDINPUT:
				case ACTION_FINDOUTPUT:
				case ACTION_FINDUPDATE:	

						/* Convert the file name. */

					BtoCStr(NameBuffer,FlatPacket -> dp_Arg3,256);

					if(FileName = BaseName(NameBuffer))
					{
						LONG Mode;

							/* Only the MODE_OLDFILE type allows
							 * shared data access.
							 */

						if(FlatPacket -> dp_Type == ACTION_FINDINPUT)
							Mode = SHARED_LOCK;
						else
							Mode = EXCLUSIVE_LOCK;

						FileHandle = (struct FileHandle *)BADDR(FlatPacket -> dp_Arg1);

							/* Is there already a lock or filehandle by this
							 * name?
							 */

						if(FlatNode = FindFlatNodeByName(FileName))
						{
								/* If so, is it locked? */

							if((FlatNode -> fn_Mode != Mode) || (Mode == EXCLUSIVE_LOCK && FlatNode -> fn_Mode == EXCLUSIVE_LOCK))
							{
								ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_IN_USE,HandlerProc);
								break;
							}
						}

							/* Create a new list entry. */

						if(FlatNode = CreateNode(Mode,FileName))
						{
							AddTail(&FlatList,(struct Node *)FlatNode);

							FileHandle -> fh_Arg1 = FlatNode -> fn_UniqueID;

								/* Turn on the disk motor. */

							FlatNode -> fn_DiskRequest -> iotd_Req . io_Command	= TD_MOTOR;
							FlatNode -> fn_DiskRequest -> iotd_Req . io_Length	= 1;

							DoIO(FlatNode -> fn_DiskRequest);

							ReturnPacket(FlatPacket,DOSTRUE,FlatPacket -> dp_Res2,HandlerProc);
						}
						else
							ReturnPacket(FlatPacket,DOSFALSE,ERROR_NO_FREE_STORE,HandlerProc);
					}
					else
						ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_NOT_FOUND,HandlerProc);

					break;

					/* Close a file opened above. */

				case ACTION_END:

						/* Find the node. */

					if(FlatNode = FindFlatNodeByID(FlatPacket -> dp_Arg1))
					{
							/* Turn off the motor. */

						FlatNode -> fn_DiskRequest -> iotd_Req . io_Command	= TD_MOTOR;
						FlatNode -> fn_DiskRequest -> iotd_Req . io_Length	= 0;

						DoIO(FlatNode -> fn_DiskRequest);

						Remove((struct Node *)FlatNode);

						DeleteNode(FlatNode);
					}

					ReturnPacket(FlatPacket,DOSTRUE,0,HandlerProc);
					break;

					/* Read a couple of bytes from a file. */

				case ACTION_READ:

						/* Do we have a valid filehandle? */

					if(FlatNode = FindFlatNodeByID(FlatPacket -> dp_Arg1))
					{
						ReadBytes = FlatPacket -> dp_Arg3;

							/* Reading across the data media size? */

						if(FlatNode -> fn_Position + ReadBytes > (FlatNode -> fn_BlockSize * FlatNode -> fn_NumBlocks))
							ReadBytes = -1;

						FlatPacket -> dp_Res2 = 0;

							/* Read a few bytes. */

						if(ReadBytes > 0)
						{
							if(Bytes = DoRead(FlatNode,ReadBytes,(APTR)FlatPacket -> dp_Arg2,FlatPacket -> dp_Port -> mp_SigTask))
								FlatNode -> fn_Position += Bytes;

							FlatPacket -> dp_Res1 = Bytes;
						}
						else
						{
							if(ReadBytes == 0)
								FlatPacket -> dp_Res1 = 0;
							else
								FlatPacket -> dp_Res1 = -1;
						}

						ReturnPacket(FlatPacket,FlatPacket -> dp_Res1,FlatPacket -> dp_Res2,HandlerProc);
					}
					else
						ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_WRONG_TYPE,HandlerProc);

					break;

					/* Write a few bytes to a file. */

				case ACTION_WRITE:

					if(FlatNode = FindFlatNodeByID(FlatPacket -> dp_Arg1))
					{
						WriteBytes = FlatPacket -> dp_Arg3;

						if(FlatNode -> fn_Position + WriteBytes > (FlatNode -> fn_BlockSize * FlatNode -> fn_NumBlocks))
							WriteBytes = -1;

						FlatPacket -> dp_Res2 = 0;

						if(WriteBytes > 0)
						{
							if(Bytes = DoWrite(FlatNode,WriteBytes,(APTR)FlatPacket -> dp_Arg2,FlatPacket -> dp_Port -> mp_SigTask))
								FlatNode -> fn_Position += Bytes;

							FlatPacket -> dp_Res1 = Bytes;
						}
						else
						{
							if(WriteBytes == 0)
								FlatPacket -> dp_Res1 = 0;
							else
								FlatPacket -> dp_Res1 = -1;
						}

						ReturnPacket(FlatPacket,FlatPacket -> dp_Res1,FlatPacket -> dp_Res2,HandlerProc);
					}
					else
						ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_WRONG_TYPE,HandlerProc);

					break;

					/* Move the r/w pointer inside a file. */

				case ACTION_SEEK:

					if(FlatNode = FindFlatNodeByID(FlatPacket -> dp_Arg1))
					{
						if(FlatPacket -> dp_Arg3 == OFFSET_BEGINNING)
							NewPosition = FlatPacket -> dp_Arg2;

						if(FlatPacket -> dp_Arg3 == OFFSET_CURRENT)
							NewPosition = FlatNode -> fn_Position + FlatPacket -> dp_Arg2;

						if(FlatPacket -> dp_Arg3 == OFFSET_END)
							NewPosition = FlatNode -> fn_Position - FlatPacket -> dp_Arg2;

						if(NewPosition < 0 || NewPosition > (FlatNode -> fn_BlockSize * FlatNode -> fn_NumBlocks))
						{
							FlatPacket -> dp_Res1 = -1;
							FlatPacket -> dp_Res2 = ERROR_SEEK_ERROR;
						}
						else
						{
							FlatPacket -> dp_Res1 = FlatNode -> fn_Position;

							FlatNode -> fn_Position = NewPosition;
						}

						ReturnPacket(FlatPacket,FlatPacket -> dp_Res1,FlatPacket -> dp_Res2,HandlerProc);
					}
					else
						ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_WRONG_TYPE,HandlerProc);

					break;

					/* Remove the handler. */

				case ACTION_DIE:

						/* Are we allowed to remove ourselves? */

					if(!FlatList . lh_Head -> ln_Succ)
					{
						ReturnPacket(FlatPacket,DOSTRUE,0,HandlerProc);

						goto FallOff;
					}
					else
						ReturnPacket(FlatPacket,DOSFALSE,ERROR_OBJECT_IN_USE,HandlerProc);

					break;

					/* Ignore the rest of the packets. */

				default:

					ReturnPacket(FlatPacket,DOSFALSE,ERROR_ACTION_NOT_KNOWN,HandlerProc);
					break;
			}
		}

			/* Cease actions, close libraries and exit. */

FallOff:	FlatDevNode -> dn_Task = NULL;

		if(IntuitionBase)
			CloseLibrary(IntuitionBase);

		if(DOSBase)
			CloseLibrary(DOSBase);
	}
}

	/* BaseName(UBYTE *String):
	 *
	 *	Returns the base of a filename.
	 */

UBYTE * __regargs
BaseName(UBYTE *String)
{
	if(String[0])
	{
		SHORT i;

		for(i = strlen(String) - 1 ; i >= 0 ; i--)
		{
			if(String[i] == ':' || String[i] == '/')
			{
				if(String[i + 1])
					return(&String[i + 1]);
				else
					return(NULL);
			}
		}

		return(String);
	}
	else
		return(NULL);
}

	/* Local2Upper(UBYTE c):
	 *
	 *	Convert a character to upper case.
	 */

UBYTE __regargs
Local2Upper(UBYTE c)
{
	return((UBYTE)((((c) >= 224 && (c) <= 254) || ((c) >= 'a' && (c) <= 'z')) ? (c) - 32 : c));
}

	/* StrCmp(UBYTE *a,UBYTE *b):
	 *
	 *	Do string comparison ignoring case.
	 */

UBYTE __regargs
StrCmp(UBYTE *a,UBYTE *b)
{
	for( ; Local2Upper(*a) == Local2Upper(*b) ; a++, b++)
	{
		if(!(*a))
			return(0);
	}

	return((UBYTE)(Local2Upper(*a) - Local2Upper(*b)));
}

	/* FindFlatNodeByID(ULONG UniqueID):
	 *
	 *	Scan the item list looking for a list entry with
	 *	a matching ID.
	 */

struct FlatNode * __regargs
FindFlatNodeByID(ULONG UniqueID)
{
	struct FlatNode *Node;

	Node = (struct FlatNode *)FlatList . lh_Head;

	while(Node -> fn_Succ)
	{
		if(Node -> fn_UniqueID == UniqueID)
			return(Node);

		Node = Node -> fn_Succ;
	}

	return(NULL);
}

	/* FindFlatNodeByName(UBYTE *Name):
	 *
	 *	Scan the item list looking for an entry with a
	 *	matching name.
	 */

struct FlatNode * __regargs
FindFlatNodeByName(UBYTE *Name)
{
	struct FlatNode *Node;

	Node = (struct FlatNode *)FlatList . lh_Head;

	while(Node -> fn_Succ)
	{
		if(!StrCmp(Node -> fn_Name,Name))
			return(Node);

		Node = Node -> fn_Succ;
	}

	return(NULL);
}

	/* BtoCStr(UBYTE *Name,BSTR String,LONG MaxLength):
	 *
	 *	Convert a BCPL string into a `C' string.
	 */

VOID __regargs
BtoCStr(UBYTE *Name,BSTR String,LONG MaxLength)
{
	UBYTE *Src,Length;

	if(Src = (UBYTE *)BADDR(String))
	{
		if((Length = Src[0]) > MaxLength)
			Length = MaxLength;

		Src++;

		while(Length--)
			*Name++ = *Src++;

		*Name = 0;
	}
}

	/* ShowRequest(APTR WindowPtr,BYTE Type,UBYTE *Drive):
	 *
	 *	If trouble shows up, behave like the standard
	 *	FS and complain.
	 */

LONG __regargs
ShowRequest(APTR WindowPtr,BYTE Type,UBYTE *Drive)
{
	STATIC struct IntuiText DiskWriteProtected[3] =
	{
		{0,1,JAM2,15, 5,NULL,"Disk in drive",					&DiskWriteProtected[1]},
		{0,1,JAM2,15,15,NULL,"################################",	&DiskWriteProtected[2]},
		{0,1,JAM2,15,25,NULL,"is write protected",			NULL}
	};

	STATIC struct IntuiText DiskNotPresent[2] =
	{
		{0,1,JAM2,15, 5,NULL,"No disk present in drive",		&DiskNotPresent[1]},
		{0,1,JAM2,15,15,NULL,"################################",	NULL}
	};

	STATIC struct IntuiText DiskUnreadable[3] =
	{
		{0,1,JAM2,15, 5,NULL,"Disk in drive",				&DiskUnreadable[1]},
		{0,1,JAM2,15,15,NULL,"################################",	&DiskUnreadable[2]},
		{0,1,JAM2,15,25,NULL,"is unreadable",				NULL}
	};

	STATIC struct IntuiText DiskWriteError[2] =
	{
		{0,1,JAM2,15, 5,NULL,"Error writing to drive",			&DiskWriteError[1]},
		{0,1,JAM2,15,15,NULL,"################################",	NULL}
	};

	STATIC struct IntuiText Retry =
	{
		0,1,JAM2,7,3,NULL,"Retry",NULL
	};

	STATIC struct IntuiText Cancel =
	{
		0,1,JAM2,7,3,NULL,"Cancel",NULL
	};

		/* A -1 will result in cancelling the
		 * requester.
		 */

	if(WindowPtr != (APTR)-1)
	{
		struct IntuiText	*BodyText;
		SHORT			 i;

			/* Install the right alert type. */

		switch(Type)
		{
			case ERR_WRITEPROTECT:	BodyText = DiskWriteProtected;
						break;

			case ERR_NODISK:	BodyText = DiskNotPresent;
						break;

			case ERR_UNREADABLE:	BodyText = DiskUnreadable;
						break;

			case ERR_WRITEERROR:	BodyText = DiskWriteError;
						break;
		}

			/* Add the drive name. */

		for(i = 0 ; BodyText[1] . IText[i] = Local2Upper(Drive[i + 1]) ; i++);

			/* Show the requester. */

		return((LONG)AutoRequest(WindowPtr,BodyText,&Retry,&Cancel,DISKINSERTED,NULL,320,72));
	}

	return(FALSE);
}

	/* FindDevice():
	 *
	 *	Find a DeviceNode entry in the DosList.
	 */

struct DeviceNode * __regargs
FindDevice(struct DeviceNode *LastNode,struct FileSysStartupMsg	**Startup,struct DosEnvec **DosEnvec,UBYTE *Name)
{
	struct DeviceNode	*DevInfo;
	STATIC UBYTE		 NameBuffer[257];

	Forbid();

	if(LastNode)
		DevInfo = (struct DeviceNode *)BADDR(LastNode -> dn_Next);
	else
		DevInfo = (struct DeviceNode *)BADDR(((struct DosInfo *)BADDR(((struct RootNode *)DOSBase -> dl_Root) -> rn_Info)) -> di_DevInfo);

	while(DevInfo)
	{
		if(DevInfo -> dn_Type == DLT_DEVICE && DevInfo -> dn_Task && DevInfo -> dn_Startup)
		{
			if(Name)
			{
				BtoCStr(NameBuffer,DevInfo -> dn_Name,256);

				if(!StrCmp(NameBuffer,Name))
				{
					if(Startup)
						*Startup = (struct FileSysStartupMsg *)BADDR(DevInfo -> dn_Startup);

					if(DosEnvec)
						*DosEnvec = (struct DosEnvec *)BADDR(((struct FileSysStartupMsg *)BADDR(DevInfo -> dn_Startup)) -> fssm_Environ);

					Permit();

					return(DevInfo);
				}
			}
			else
			{
				if(Startup)
					*Startup = (struct FileSysStartupMsg *)BADDR(DevInfo -> dn_Startup);

				if(DosEnvec)
					*DosEnvec = (struct DosEnvec *)BADDR(((struct FileSysStartupMsg *)BADDR(DevInfo -> dn_Startup)) -> fssm_Environ);

				Permit();

				return(DevInfo);
			}
		}

		DevInfo = (struct DeviceNode *)BADDR(DevInfo -> dn_Next);
	}

	Permit();

	return(NULL);
}

	/* DeleteNode(struct FlatNode *FlatNode):
	 *
	 *	Delete a freshly created item node freeing
	 *	all associated resources.
	 */

VOID __regargs
DeleteNode(struct FlatNode *FlatNode)
{
	if(FlatNode -> fn_DiskBuffer)
		FreeMem(FlatNode -> fn_DiskBuffer,FlatNode -> fn_BlockSize);

	if(FlatNode -> fn_DiskRequest)
	{
		if(FlatNode -> fn_DiskRequest -> iotd_Req . io_Device)
			CloseDevice(FlatNode -> fn_DiskRequest);

		DeleteExtIO(FlatNode -> fn_DiskRequest);
	}

	if(FlatNode -> fn_DiskPort)
		DeletePort(FlatNode -> fn_DiskPort);

	FreeMem(FlatNode,sizeof(struct FlatNode));
}

	/* CreateNode(LONG Type,UBYTE *Name):
	 *
	 *	Create an item node with given characteristics,
	 *	can be either shared or exclusive access, if a name
	 *	is given will open the approriate device driver.	 
	 */

struct FlatNode * __regargs
CreateNode(LONG Type,UBYTE *Name)
{
	struct FlatNode *FlatNode;

	if(FlatNode = (struct FlatNode *)AllocMem(sizeof(struct FlatNode),MEMF_PUBLIC|MEMF_CLEAR))
	{
		if(Name)
		{
			struct DeviceNode		*DevInfo;
			struct FileSysStartupMsg	*Startup;
			struct DosEnvec			*DosEnvec;

				/* Try to find the device. */

			if(!(DevInfo = FindDevice(NULL,&Startup,&DosEnvec,Name)))
			{
				DeleteNode(FlatNode);

				return(NULL);
			}

				/* Create a MsgPort, this is where a
				 * potential problem exists: since all
				 * MsgPorts refer to the handler process,
				 * we will run out of signal bits if
				 * more than app. 16 files/locks are open at
				 * the same time.
				 */

			if(!(FlatNode -> fn_DiskPort = (struct MsgPort *)CreatePort(NULL,0)))
			{
				DeleteNode(FlatNode);

				return(NULL);
			}

				/* Create a device request. */

			if(!(FlatNode -> fn_DiskRequest = (struct IOExtTD *)CreateExtIO(FlatNode -> fn_DiskPort,sizeof(struct IOExtTD))))
			{
				DeleteNode(FlatNode);

				return(NULL);
			}

				/* Open the device driver. */

			if(OpenDevice(&((UBYTE *)BADDR(Startup -> fssm_Device))[1],Startup -> fssm_Unit,FlatNode -> fn_DiskRequest,Startup -> fssm_Flags))
			{
				DeleteNode(FlatNode);

				return(NULL);
			}

				/* Inquire the unit data. */

			FlatNode -> fn_BlockSize	= DosEnvec -> de_SizeBlock << 2;
			FlatNode -> fn_FirstBlock	= DosEnvec -> de_LowCyl * DosEnvec -> de_BlocksPerTrack * DosEnvec -> de_Surfaces;
			FlatNode -> fn_NumBlocks	= (DosEnvec -> de_HighCyl - DosEnvec -> de_LowCyl + 1) * DosEnvec -> de_BlocksPerTrack * DosEnvec -> de_Surfaces;

				/* Create a r/w buffer. */

			if(!(FlatNode -> fn_DiskBuffer = (APTR)AllocMem(FlatNode -> fn_BlockSize,DosEnvec -> de_BufMemType)))
			{
				DeleteNode(FlatNode);

				return(NULL);
			}

			strcpy(&FlatNode -> fn_Name[1],Name);
		}

		FlatNode -> fn_Mode	= Type;
		FlatNode -> fn_UniqueID	= UniqueCounter++;
	}

	return(FlatNode);
}

	/* ReturnPacket():
	 *
	 *	Return a standard DOS packet to its sender.
	 */

VOID __regargs
ReturnPacket(struct DosPacket *Packet,ULONG Res1,ULONG Res2,struct Process *HandlerProc)
{
	struct MsgPort *ReplyPort;

	ReplyPort = Packet -> dp_Port;

	Packet -> dp_Res1 = Res1;
	Packet -> dp_Res2 = Res2;

	Packet -> dp_Port = &HandlerProc -> pr_MsgPort;

	Packet -> dp_Link -> mn_Node . ln_Name	= (APTR)Packet;
	Packet -> dp_Link -> mn_Node . ln_Succ	= NULL;
	Packet -> dp_Link -> mn_Node . ln_Pred	= NULL;

	PutMsg(ReplyPort,Packet -> dp_Link);
}

	/* WaitPacket(struct Process *HandlerProc):
	 *
	 *	Wait for packet arrival.
	 */

struct DosPacket * __regargs
WaitPacket(struct Process *HandlerProc)
{
	struct Message *DOSMsg;

	WaitPort(&HandlerProc -> pr_MsgPort);

	DOSMsg = (struct Message *)GetMsg(&HandlerProc -> pr_MsgPort);

	return((struct DosPacket *)DOSMsg -> mn_Node . ln_Name);
}

	/* DoRead():
	 *
	 *	Read a few bytes from a file.
	 */

LONG __regargs
DoRead(struct FlatNode *FlatNode,LONG Size,UBYTE *Buffer,struct Process *Caller)
{
	LONG Block,Length,BytesRead = 0,Offset = FlatNode -> fn_Position;
	UBYTE *DiskBuffer = FlatNode -> fn_DiskBuffer;

		/* Time for a check? */

	if(!FlatNode -> fn_CheckCount)
	{
		FOREVER
		{
			FlatNode -> fn_DiskRequest -> iotd_Req . io_Command = TD_CHANGESTATE;

				/* Is there still a disk in the drive? */

			if(!DoIO(FlatNode -> fn_DiskRequest))
			{
				if(FlatNode -> fn_DiskRequest -> iotd_Req . io_Actual)
				{
					if(!ShowRequest(Caller -> pr_WindowPtr,ERR_NODISK,FlatNode -> fn_Name))
						return(0);
				}
				else
					break;
			}
		}
	}

	if(FlatNode -> fn_CheckCount++ == 10)
		FlatNode -> fn_CheckCount = 0;

		/* Convert offset from bytes into blocks. */

	Block	= Offset / FlatNode -> fn_BlockSize;
	Offset	= Offset % FlatNode -> fn_BlockSize;

	if(Size > 0)
	{
			/* Read the data block by block... */

		while(Size > 0)
		{
Retry:			FlatNode -> fn_DiskRequest -> iotd_Req . io_Command	= CMD_READ;
			FlatNode -> fn_DiskRequest -> iotd_Req . io_Offset	= Block * FlatNode -> fn_BlockSize + FlatNode -> fn_FirstBlock;
			FlatNode -> fn_DiskRequest -> iotd_Req . io_Length	= FlatNode -> fn_BlockSize;
			FlatNode -> fn_DiskRequest -> iotd_Req . io_Data	= DiskBuffer;

				/* Read the block. */

			if(DoIO(FlatNode -> fn_DiskRequest))
			{
				if(ShowRequest(Caller -> pr_WindowPtr,ERR_UNREADABLE,FlatNode -> fn_Name))
					goto Retry;
				else
					return(BytesRead);
			}

			Length = FlatNode -> fn_BlockSize - Offset;

			if(Length > Size)
				Length = Size;

				/* Copy the data. */

			memcpy(Buffer,&DiskBuffer[Offset],Length);

			Buffer = &Buffer[Length];

			Size -= Length;

			BytesRead += Length;

			Block++;

			Offset = 0;
		}
	}

	return(BytesRead);
}

	/* DoWrite():
	 *
	 *	Write a few bytes to a file. 
	 */

LONG __regargs
DoWrite(struct FlatNode *FlatNode,LONG Size,UBYTE *Buffer,struct Process *Caller)
{
	LONG Block,Length,BytesWritten = 0,Offset = FlatNode -> fn_Position;
	UBYTE *DiskBuffer = FlatNode -> fn_DiskBuffer;

		/* Time for a check? */

	if(!FlatNode -> fn_CheckCount)
	{
		FOREVER
		{
			FlatNode -> fn_DiskRequest -> iotd_Req . io_Command = TD_CHANGESTATE;

				/* Is there a disk in the drive? */

			if(!DoIO(FlatNode -> fn_DiskRequest))
			{
				if(FlatNode -> fn_DiskRequest -> iotd_Req . io_Actual)
				{
					if(!ShowRequest(Caller -> pr_WindowPtr,ERR_NODISK,FlatNode -> fn_Name))
						return(0);
				}
				else
					break;
			}
		}

		FOREVER
		{
			FlatNode -> fn_DiskRequest -> iotd_Req . io_Command = TD_PROTSTATUS;

				/* Is the disk write enabled? */

			if(!DoIO(FlatNode -> fn_DiskRequest))
			{
				if(FlatNode -> fn_DiskRequest -> iotd_Req . io_Actual)
				{
					if(!ShowRequest(Caller -> pr_WindowPtr,ERR_WRITEPROTECT,FlatNode -> fn_Name))
						return(0);
				}
				else
					break;
			}
		}
	}

	if(FlatNode -> fn_CheckCount++ == 10)
		FlatNode -> fn_CheckCount = 0;

		/* Convert offset from bytes into blocks. */

	Block	= Offset / FlatNode -> fn_BlockSize;
	Offset	= Offset % FlatNode -> fn_BlockSize;

	if(Size > 0)
	{
		while(Size > 0)
		{
Retry1:			if(Offset)
			{
					/* The data to write is smaller
					 * than a block, so we'll have to
					 * read the block to write to first,
					 * copy the data over and write the
					 * block back.
					 */

				FlatNode -> fn_DiskRequest -> iotd_Req . io_Command	= CMD_READ;
				FlatNode -> fn_DiskRequest -> iotd_Req . io_Offset	= Block * FlatNode -> fn_BlockSize + FlatNode -> fn_FirstBlock;
				FlatNode -> fn_DiskRequest -> iotd_Req . io_Length	= FlatNode -> fn_BlockSize;
				FlatNode -> fn_DiskRequest -> iotd_Req . io_Data	= DiskBuffer;

				if(DoIO(FlatNode -> fn_DiskRequest))
				{
					if(ShowRequest(Caller -> pr_WindowPtr,ERR_UNREADABLE,FlatNode -> fn_Name))
						goto Retry1;
					else
						return(BytesWritten);
				}

				Length = FlatNode -> fn_BlockSize - Offset;

				if(Length > Size)
					Length = Size;

				memcpy(&DiskBuffer[Offset],Buffer,Length);

Retry2:				FlatNode -> fn_DiskRequest -> iotd_Req . io_Command	= CMD_WRITE;
				FlatNode -> fn_DiskRequest -> iotd_Req . io_Offset	= Block * FlatNode -> fn_BlockSize + FlatNode -> fn_FirstBlock;
				FlatNode -> fn_DiskRequest -> iotd_Req . io_Length	= FlatNode -> fn_BlockSize;
				FlatNode -> fn_DiskRequest -> iotd_Req . io_Data	= DiskBuffer;

				if(DoIO(FlatNode -> fn_DiskRequest))
				{
					if(ShowRequest(Caller -> pr_WindowPtr,ERR_WRITEERROR,FlatNode -> fn_Name))
						goto Retry2;
					else
						return(BytesWritten);
				}

				Buffer = &Buffer[Length];

				Size -= Length;

				BytesWritten += Length;

				Block++;

				Offset = 0;
			}
			else
			{
				if(Size > FlatNode -> fn_BlockSize)
					Length = FlatNode -> fn_BlockSize;
				else
				{
					if(Size < FlatNode -> fn_BlockSize)
						memset(DiskBuffer,0,FlatNode -> fn_BlockSize);

					Length = Size;
				}

				memcpy(DiskBuffer,Buffer,Length);

Retry3:				FlatNode -> fn_DiskRequest -> iotd_Req . io_Command	= CMD_WRITE;
				FlatNode -> fn_DiskRequest -> iotd_Req . io_Offset	= Block * FlatNode -> fn_BlockSize + FlatNode -> fn_FirstBlock;
				FlatNode -> fn_DiskRequest -> iotd_Req . io_Length	= FlatNode -> fn_BlockSize;
				FlatNode -> fn_DiskRequest -> iotd_Req . io_Data	= DiskBuffer;

				if(DoIO(FlatNode -> fn_DiskRequest))
				{
					if(ShowRequest(Caller -> pr_WindowPtr,ERR_WRITEERROR,FlatNode -> fn_Name))
						goto Retry1;
					else
						return(BytesWritten);
				}

				Buffer = &Buffer[Length];

				Size -= Length;

				BytesWritten += Length;

				Block++;
			}
		}
	}

	return(BytesWritten);
}
