/* Handler for accessing floppies and other block devices as character devices */
/* ©1993 Anders Hammarquist */

#include <exec/types.h>
#include <exec/memory.h>
#include <dos/dosextens.h>
#include <dos/filehandler.h>
#include <devices/trackdisk.h>
#include <proto/dos.h>
#include <proto/exec.h>

#ifndef BTOC
#define BTOC	BADDR
#define CTOB	MKBADDR
#endif

#define CleanExit(x,y)  {CleanUp(x,y,packet); return;}
#define max(a,b)        ((a)>(b)?(a):(b))
#define min(a,b)        ((a)<(b)?(a):(b))

/* Version */

const char *version="$VER: rfp-handler 1.0 (93.01.19) © Anders Hammarquist";

/* Globals - (NOTE - this code MUST be pure) */
struct	ExecBase    *SysBase;	/* Exec library pointer */
struct	DosLibrary  *DOSBase;	/* Dos library pointer */
BOOL	DiskDevice;		/* Did we open device? */

struct	Process     *DosProc;	/* Our Process */
struct	DeviceNode  *DosNode;	/* Our Device node */

struct	DosEnvec    *DosEnv;	/* Our environment */

/* This is for when we support multiple readers/writers and locks */
/*
struct	MemList     *FHBase;	* Open FileHandles *
struct	MemList     *LCBase;	* Open FileLocks *
*/

/* For now - quick and dirty - only one user at a time */
BOOL	IsOpenNew;		/* We are opened in MODE_NEWFILE */
BOOL	IsOpenReadWrite;	/* We are opened in MODE_READWRITE */
BOOL	IsOpenOld;		/* We are opened in MODE_OLDFILE */

struct	IOExtTD     *DiskIO;	/* Structure for trackdisk IO */
struct	MsgPort     *DiskPort;	/* Message port for trackdisk IO */
ULONG	FirstByte;		/* What byte to start at */
ULONG	LastByte;		/* Last byte readable */
ULONG	BytesPerTrack;		/* How many BytesPerTrack? */
ULONG	BufMemType;		/* Use this for buffer */
UBYTE	*ReadBuffer;		/* Buffer for RW */

void ReturnPacket(struct DosPacket *);
void CleanUp(BOOL,ULONG,struct DosPacket *);
BOOL PacketsQueued(void);
void __stdargs KPrintF(char *,...);

void __saveds RFP_Handler(void)
{
    struct  DosPacket	*packet;
    struct  Message	*msg;
    struct  FileSysStartupMsg	*fssm;

    /* Initialize globals */

    SysBase = *(struct ExecBase **)4;

    DosProc = (struct Process *)FindTask(NULL);
    DiskDevice = NULL; BytesPerTrack = NUMSECS * TD_SECTOR; BufMemType=0;
    FirstByte = 0; LastByte = NUMSECS * TD_SECTOR * 160;

    /* get startup packet */

    WaitPort(&(DosProc->pr_MsgPort));
    msg = GetMsg(&(DosProc->pr_MsgPort));
    packet = (struct DosPacket *)msg->mn_Node.ln_Name;

    /* Set up stuff... */
    if(!(DOSBase = (struct DosLibrary *)OpenLibrary("dos.library",0)))
	/* This really can't fail */
    {
	/* Couldn't open dos.library ?!?! */
	CleanExit(DOSFALSE,ERROR_INVALID_RESIDENT_LIBRARY);
    }

    DosNode = BTOC(packet->dp_Arg3);
    DosNode->dn_Task = &(DosProc->pr_MsgPort); /* One process for all access */
    fssm = (struct FileSysStartupMsg *)(BTOC(DosNode->dn_Startup));
    DosEnv = BTOC(fssm->fssm_Environ);

    if(!(DiskPort = CreatePort(0,0)))
    {
	/* Couldn't create message port */
	CleanExit(DOSFALSE,ERROR_NO_FREE_STORE);
    }

    if(!(DiskIO = (struct IOExtTD *)CreateExtIO(DiskPort,sizeof(struct IOExtTD))))
    {
	/* Couldn't create IO struct */
	CleanExit(DOSFALSE,ERROR_NO_FREE_STORE);
    }

    if(DiskDevice=OpenDevice(
	(UBYTE *)(BTOC(fssm->fssm_Device))+1,
	((struct FileSysStartupMsg *)BTOC(DosNode->dn_Startup))->fssm_Unit,
	DiskIO,
	((struct FileSysStartupMsg *)BTOC(DosNode->dn_Startup))->fssm_Flags
	))
    {
	/* Couldn't open device */
	CleanExit(DOSFALSE,ERROR_OBJECT_NOT_FOUND);
    }

    if(DosEnv->de_TableSize>=DE_BLKSPERTRACK)
    {
	BytesPerTrack = DosEnv->de_SizeBlock * 4 * DosEnv->de_SectorPerBlock *
			DosEnv->de_BlocksPerTrack * DosEnv->de_Surfaces;
	switch(DosEnv->de_TableSize>=DE_BUFMEMTYPE?DE_BUFMEMTYPE:DosEnv->de_TableSize)
	{
	    case DE_BUFMEMTYPE:
		BufMemType = DosEnv->de_BufMemType;
	    case DE_UPPERCYL:
		LastByte = (DosEnv->de_HighCyl+1) * BytesPerTrack;
	    case DE_LOWCYL:
		FirstByte = DosEnv->de_LowCyl * BytesPerTrack;
		break;
	    default:
		LastByte = 80 * BytesPerTrack;
	}
    }
    if(!(ReadBuffer=AllocMem(BytesPerTrack,BufMemType|MEMF_CLEAR|MEMF_PUBLIC)))
    {						/* Just in case  ^^^^^^^^^^^ */
	/* Couldn't allocate buffer */
	CleanExit(DOSFALSE,ERROR_NO_FREE_STORE);
    }

    /* Reply initial DOS packet */
    ReturnPacket(packet);

    /****************
    ***************

    Ok, it seems like we're all done with the setup now, so we should be
    ready to go into endless-loop mode

    ***************
    ****************/

    while(1)
    {
	WaitPort(&DosProc->pr_MsgPort);
	while(msg = GetMsg(&DosProc->pr_MsgPort))
	{
	    LONG tmp, Buf, Pos, Act, Len; UBYTE *tmpp;
	    packet = (struct DosPacket *)msg->mn_Node.ln_Name;
	    packet->dp_Res1 = DOSTRUE;
	    packet->dp_Res2 = 0;

	    switch(packet->dp_Type)
	    {
		case ACTION_NIL:
		    break;
		case ACTION_DIE:
		    if(!(IsOpenNew||IsOpenReadWrite||IsOpenOld)||!PacketsQueued())
		    {
			/* Ok, we can go away */
			CleanExit(DOSTRUE,0);
			/* gone */
		    }
		    packet->dp_Res2 = ERROR_OBJECT_IN_USE;
		    break;
		case ACTION_FINDOUTPUT:     /* FH,Lock,Name	    Bool */
		    if(!(IsOpenNew||IsOpenReadWrite||IsOpenOld))
		    {
			/* Maybe we should check for disk in drive and protection */
			IsOpenNew = TRUE;
			/*
			((struct FileHandle *)BTOC(packet->dp_Arg1))->fh_Arg1=0;
			((struct FileHandle *)BTOC(packet->dp_Arg1))->fh_Arg2=0;
			*/
			Buf=0;Pos=0;
		    }
		    else
		    {
			packet->dp_Res2 = ERROR_OBJECT_IN_USE;
		    }
		    break;
		case ACTION_FINDINPUT:	    /* FH, Lock, Name	    Bool */
		    if(!(IsOpenNew||IsOpenReadWrite||IsOpenOld))
		    {
			/* check? */
			IsOpenOld = TRUE;
			/*
			((struct FileHandle *)BTOC(packet->dp_Arg1))->fh_Arg1=0;
			((struct FileHandle *)BTOC(packet->dp_Arg1))->fh_Arg2=0;
			*/
			Buf=0;Pos=0;
		    }
		    else
		    {
			packet->dp_Res2 = ERROR_OBJECT_IN_USE;
		    }
		    break;
		case ACTION_FINDUPDATE:     /* FH, Lock, Name	    Bool */
		    if(!(IsOpenNew||IsOpenReadWrite||IsOpenOld))
		    {
			/* check? */
			IsOpenReadWrite = TRUE;
			/*
			((struct FileHandle *)BTOC(packet->dp_Arg1))->fh_Arg1=0;
			((struct FileHandle *)BTOC(packet->dp_Arg1))->fh_Arg2=0;
			*/
			Buf=0;Pos=0;
		    }
		    else
		    {
			packet->dp_Res2 = ERROR_OBJECT_IN_USE;
		    }
		    break;
		case ACTION_READ:	    /* FH.Arg1,CPTRBuf,Len  ActLen */
		    Act=tmp=0; Len=packet->dp_Arg3;
		    tmpp=(UBYTE *)packet->dp_Arg2;

		    while(Act<Len)
		    {
			Buf=(Pos/BytesPerTrack)*BytesPerTrack;

			if(Buf>=LastByte)
			{
			    packet->dp_Res2 = -1;
			}
			else
			{
			    DiskIO->io_Length=BytesPerTrack;
			    DiskIO->io_Data=(APTR)ReadBuffer;
			    DiskIO->io_Offset=(ULONG)Buf;
			    DiskIO->io_Command=CMD_READ;
			    if(DoIO(DiskIO))
			     switch(DiskIO->io_Error)
			     {
				/* :( */
				case TDERR_DiskChanged:
				    packet->dp_Res2=ERROR_DEVICE_NOT_MOUNTED;
				    break;
				case TDERR_SeekError:
				    packet->dp_Res2=ERROR_SEEK_ERROR;
				    break;
				case TDERR_DriveInUse:
				    packet->dp_Res2=ERROR_OBJECT_IN_USE;
				    break;
				default:
				    packet->dp_Res2=-1;
				    break;
			     }
			    else
			    {
				/* Read sucessfull */
				/* tmpp =>  ptr into caller's buffer */
				/* tmp	=>  Bytes to read from this block */
				/* Pos	=>  Byte position on disk */
				/* Buf	=>  Byte position of ReadBuffer[0] */
				/* P-B	=>  Chars read in this block */
				/* Len	=>  Total read lenght */
				/* Act	=>  Total actual read Bytes */
				/* L-A	=>  Total bytes left to read */

				tmp=min(BytesPerTrack-(Pos-Buf),Len-Act);

				CopyMem(ReadBuffer+Pos-Buf,tmpp,tmp);

				tmpp+=tmp;Act+=tmp;
				Pos+=tmp;
			    }
			}
			packet->dp_Res1=Act;
			if(packet->dp_Res2) break;
		    }
		    break;
		case ACTION_WRITE:	/* FH.Arg1,CPTRBuf,Len	    ActLen */
		    Act=tmp=0; Len=packet->dp_Arg3;
		    tmpp=(UBYTE *)packet->dp_Arg2;

		    while(Act<Len)
		    {
			Buf=(Pos/BytesPerTrack)*BytesPerTrack;

			if(Buf>=LastByte)
			{
			    packet->dp_Res2 = ERROR_DISK_FULL;
			}
			else
			{
			    /* tmpp =>	ptr into caller's buffer */
			    /* tmp  =>	Bytes to write to this block */
			    /* Pos  =>	Byte position on disk */
			    /* Buf  =>	Byte position of ReadBuffer[0] */
			    /* P-B  =>	Chars used in this block */
			    /* Len  =>	Total written lenght */
			    /* Act  =>	Total actual written Bytes */
			    /* L-A  =>	Total bytes left to write */

			    tmp=min(BytesPerTrack-(Pos-Buf),Len-Act);

			    CopyMem(tmpp,ReadBuffer+Pos-Buf,tmp);

			    tmpp+=tmp;Act+=tmp;Pos+=tmp;

			    DiskIO->io_Length=BytesPerTrack;
			    DiskIO->io_Data=(APTR)ReadBuffer;
			    DiskIO->io_Offset=(ULONG)Buf;
			    DiskIO->io_Command=CMD_WRITE;
			    if(DoIO(DiskIO))
			     switch(DiskIO->io_Error)
			     {
				/* :( */
				case TDERR_WriteProt:
				    packet->dp_Res2=ERROR_DISK_WRITE_PROTECTED;
				    break;
				case TDERR_DiskChanged:
				    packet->dp_Res2=ERROR_DEVICE_NOT_MOUNTED;
				    break;
				case TDERR_SeekError:
				    packet->dp_Res2=ERROR_SEEK_ERROR;
				    break;
				case TDERR_DriveInUse:
				    packet->dp_Res2=ERROR_OBJECT_IN_USE;
				    break;
				default:
				    packet->dp_Res2=-1;
				    break;
			     }
			}
			packet->dp_Res1=Act;
			if(packet->dp_Res2) break;
		    }
		    break;
		case ACTION_END:	/*  FH.Arg1		Bool */
		    /*
		    Buf=&(packet->dp_Arg1);
		    */

		    IsOpenNew=IsOpenOld=IsOpenReadWrite=FALSE;
		    DiskIO->io_Length=BytesPerTrack;
		    DiskIO->io_Data=(APTR)ReadBuffer;
		    DiskIO->io_Offset=(ULONG)Buf;
		    DiskIO->io_Command=CMD_UPDATE;
		    DoIO(DiskIO);
		    DiskIO->io_Command=TD_MOTOR;
		    DiskIO->io_Length=0;
		    DoIO(DiskIO);
		    break;
		case ACTION_SEEK:	/*  FH.Arg1,Pos,Mode	OldPos */
		    /*
		    Buf=&(packet->dp_Arg1);
		    Pos=Buf+1;
		    */

		    switch(packet->dp_Arg3)
		    {
		    case OFFSET_BEGINNING:
			if((packet->dp_Arg2>LastByte)||(packet->dp_Arg2<0))
			    packet->dp_Res2=ERROR_SEEK_ERROR;
			else
			{
			    packet->dp_Res1=Pos;
			    Pos=packet->dp_Arg2;
			}
			break;
		    case OFFSET_END:
			if((packet->dp_Arg2>LastByte)||(packet->dp_Arg2<0))
			    packet->dp_Res2=ERROR_SEEK_ERROR;
			else
			{
			    packet->dp_Res1=Pos;
			    Pos=LastByte-packet->dp_Arg2;
			}
			break;
		    case OFFSET_CURRENT:
			if((Pos+packet->dp_Arg2>LastByte)||
			   (Pos+packet->dp_Arg2<0))
			    packet->dp_Res2=ERROR_SEEK_ERROR;
			else
			{
			    packet->dp_Res1=Pos;
			    Pos+=packet->dp_Arg2;
			}
		    }
		    break;
		case ACTION_IS_FILESYSTEM:
		    packet->dp_Res1=DOSFALSE;
		    break;
		default:    /* Never mind the rest */
		    packet->dp_Res2=ERROR_ACTION_NOT_KNOWN;
	    }
	    if((packet->dp_Res2)&&(packet->dp_Res2!=-1))
		packet->dp_Res1=DOSFALSE;
	    else
	       packet->dp_Res2=0;

	    ReturnPacket(packet);
	}
    }
}

void ReturnPacket(struct DosPacket *packet)
{
    struct Message *msg;
    struct MsgPort *port;

    port=packet->dp_Port;
    msg=packet->dp_Link;
    packet->dp_Port=&DosProc->pr_MsgPort;
    msg->mn_Node.ln_Name = (UBYTE *)packet;
    msg->mn_Node.ln_Succ = NULL;
    msg->mn_Node.ln_Pred = NULL;
    PutMsg(port,msg);
}

BOOL PacketsQueued(void)
{
    return (BOOL)(DosProc->pr_MsgPort.mp_MsgList.lh_Head !=
	    DosProc->pr_MsgPort.mp_MsgList.lh_Tail);
}

void CleanUp(BOOL Err1,ULONG Err2,struct DosPacket *packet)
{
    packet->dp_Res1=Err1;
    if(Err1==DOSFALSE) packet->dp_Res2=Err2;

    if(ReadBuffer)  FreeMem(ReadBuffer,BytesPerTrack);
    if(!DiskDevice) CloseDevice(DiskIO);
    if(DiskIO)      DeleteExtIO(DiskIO);
    if(DiskPort)    DeletePort(DiskPort);
    if(DOSBase)     CloseLibrary(DOSBase);

    ReturnPacket(packet);
}

