;/* "C:Execute" me to compile me with SAS/C 6
Set file Device-Handler

sc:c/sc MOD NOICON VERBOSE STRMERGE UNSCHAR NOSTKCHK IGNORE=73 MOD CSRC $file.c
sc:c/slink NOICONS FROM $file.o TO $file LIB lib:amiga.lib,lib:sc.lib,lib:debug.lib VERBOSE SMALLCODE SMALLDATA NODEBUG
protect $file +p

UnSet opt
UnSet file
Quit
*/

/*F*/ /* autodoc */
#if 0
AUTODOC Device-Handler
**
** $VER: Device-Handler 1.5 (24 Apr 1995)
**
** Original code was written by Matthias Scheler. The original
** distribution archive of Matthias has been included.
**
**
** NAME
**    Device-Handler - UNIX-like raw device access
**
** WARNING
**    This software is intended for advanced users only. I *strongly*
**    recommend all people who do not understand every detail given
**    herein to obtain further information about ->AmigaDOS-Devices
**    and ->Handlers and their implementation on top of ->Exec
**    device drivers.
**
**    ** WRONG USAGE OF THIS SOFTWARE WILL MOST LIKELY RESULT IN **
**    ** SEVERE LOSS OF DATA!                                    **
**
**    As this software is freeware in cases of data disruption you
**    will find _nobody_ being responsible except yourself. Especially
**    Matthias and me reject any liability.
**
** FUNCTION
**    Amiga Users often put a jealous glance to the UNIX world, were
**    raw device access is as simple as nowhere else. Several solutions
**    to fix this for Amigas have been provided in the past. One of these,
**    the DEV: handler by Matthias Scheler, has been taken to base this
**    software on.
**
**    Specifically, the original DEV: has the considerable drawback that it
**    only worked with devices that supported the ->TD_GETGEOMETRY
**    ->IORequest. Unfortunatly a lot of harddisk- (and other) drivers don't
**    implement this command.
**
**    So I decided to derive the Exec-device selection from on the AmigaDOS
**    stream name in such way, that the user provides the Name of an
**    AmigaDOS Handler instead of the Exec-Device. There are system conform
**    ways to find out on top of which Exec-device an AmigaDOS handler is
**    running, so why not leaving this job to the DEV:-Handler ?
**
**    With my DEV: enhancement, you may type things like "DEV:df0" or
**    "DEV:dh0", you get the general idea. Further improvements include the
**    ability to limit the total size which is allowed to read or write, and
**    to specify the low- and high cylinders to be used.
**
**    Note that the physical definition is completely retrieved from the
**    given handler name (e.g. dh0), so there are some implications:
**
**       * Exec-device will be always correct
**       * Exec-device unit will be allways correspond to the unit
**         the AmigaDOS handler works on, releasing you from dealing whith
**         it and thus making the usage of DEV: _much_ safer!
**       * Exec-device OpenDevice() flags are always correct
**       * Exec-device buffer memory type is always correct
**       * Most often you will want to use DEV: for ->UNIX tar and will want
**         to start writing exactly at block 0. For most floppy-base devices
**         this will be correct automatically. In contrast, nearly every
**         harddisk contains a ->RigidDiskBlock and the actual partitions
**         are located behind this, usually having a low-cylinder of about 3.
**         To archieve the intended bahaviour, you should create a special
**         DOSDriver/mountlist entry, such as:
**
**           RDH0:                       /* raw dh0 */
**              Device = scsi.device     /* change this accordingly */
**              Unit = 1
**              Flags = 0
**              Surfaces = 5
**              BlocksPerTrack = 17
**              LowCyl = 0 ;             /* specify here full range */
**              HighCyl = 1023           /* of cylinders */
**              Stacksize = 4000
**              Priority = 5
**              GlobVec = -1
**              BufMemType = 0
**           #
**
**         Another way is to use the LOCYL stream name option. Again note:
**         Playing around with LOCYL or HICYL is a pretty dangerous thing.
**         It is always advised to write a dedicated mountlist/DOSDriver.
**
**    Plead read also the original documentation of the DEV:-handler.
**
** STREAM NAME
**
**    The stream name is constructed as following:
**
**       DEV:<DOSHANDLER>[/<MAXLEN>][/LOCYL=<NUM>][/HICYL=<NUM>]
**
**    During stream name processing, all slashes are removed, thus creating
**    a standard AmigaDOS ReadArgs()-style text line, as you know it from
**    all CLI-Programs (I suppose you deleted all those that don't use
**    ReadArgs() ;).
**
**    Additionally, all dots ('.') which are not part of a quoted text, are
**    substituted by an equation symbol ('='). That comes in handy when using
**    the LOCYL or HICYL options. AmigaDOS strips all equations that are not
**    quoted. Thus, the command "type dev:df0/locyl=3" would lead to an error,
**    as the handler would get the text "dev:df0/locyl" instead of
**    "dev:df0/locyl=3". So, either use the dot or quote the whole stream spec.
**
**    *Any* number which is to pass to the handler may start with '$' or '0x'
**    or '0X' to indicate hexadecimal notation (default is decimal). A
**    trailing 'M' tells the handler to interprete it as a mega-value,
**    (i.e. multiply by 1024^2) and 'K' to signal a kilo value (factor 1024).
**    All charactes are case-insensitive.
**
**    The complete convenience of ReadArgs() applies to stream name
**    construction:
**
**       The template is "DOSHANDLER/A,MAXLEN,LOCYL/K,HICYL/K"
**
**       DOSNAME
**          A valid, AmigaDOS-device name such as DH0, df0, ... The device has
**          to be mounted, though.
**
**       MAXLEN
**          optional: maximum length of bytes to read or write. The (final)
**          lo/hicyl may enclose a smaller space: the smallest space is always
**          used. This value _must_ appear anywhere behind DOSHANDLER.
**
**       LOCYL
**          Starting cylinder read from/write to. If higher than the (final)
**          high cylinder, low cylinder is casted to high cylinder.
**
**       HICYL
**          Ending cylinder. If higher than physical, physical is used.
**
**    Some example stream names:
**
**       DEV:df0
**       DEV:dh0/1024
**       DEV:dh0/1k
**       DEV:dh0/LOCYL=0
**       DEV:dh1/HICYL=1k/LOCYL=0M/20M
**       DEV:df0/$1000
**       DEV:df0/0x2000/LOCYL.4
**       "DEV:df0/0X200/LOCYL=4"
**
** AUTHOR
**
**    Marius Gröger,
**    Bärstadter Str. 4
**    D-65307 Bad Schwalbach
**    email: mag@sysgo.de
**
** SOURCE
**
**    The source code of "Device-Handler" has been written with the GoldED
**    text editor (© Dietmar Eilert). Text folds start with "/*F*/"
**    and end with "/*E*/".
**
**    You may make any changes to the source for your own use.
**    If you consider them useful for everybody, tell me so I can
**    include them to the next public release.
**
**    The documentation has been extracted with makedoc (© Stefan Ruppert).
**
** $HISTORY:
**
** 24 Apr 1995 : 001.005 :  at the end of a media Write()/Read() returned wrong res1,
**                          so multi-volume tar archives shouldn't be a problem any
**                          longer
** 22 Feb 1995 : 001.004 :  some bug-fixing
** 18 Feb 1995 : 001.003 :  made Write() working...
** 20 Dec 1994 : 001.002 :  + one task/multiple FileHandles
**                          + source-code rework
**                          + better stream name scanning by ReadArgs()
**                          + new option MAXLENGTH
**                          + new options LOCYL, HICYL
**                          + highly improved read/write operation
** 18 Dec 1994 : 001.001 :  changed stream name convention
** 08 Dec 1994 : 001.000 :  took over from Matthias Scheler
**
ENDDOC
**
#endif
/*E*/

/*F*/ /* debug */
extern void KPrintF(char *, ...);
/*#define d(x) { KPrintF("%s:%ld: ", __FUNC__, __LINE__); KPrintF x; }*/
#define d(x) ;
/*E*/

/*F*/ /* includes */
#include <exec/memory.h>
#include <exec/execbase.h>
#include <devices/trackdisk.h>
#include <dos/dosextens.h>
#include <dos/filehandler.h>
#include <dos/dos.h>

#define __USE_SYSBASE

#include <clib/exec_protos.h>
#include <pragmas/exec_pragmas.h>

#include <clib/dos_protos.h>
#include <pragmas/dos_pragmas.h>

#include <clib/utility_protos.h>
#include <pragmas/utility_pragmas.h>

#include <string.h>

/* fix include files (anyawy I'm not yet using them:
** what about sending me a diff-file ?)
*/

#ifndef ACTION_GET_DISK_FSSM
#define ACTION_GET_DISK_FSSM 4201
#endif

#ifndef ACTION_FREE_DISK_FSSM
#define ACTION_FREE_DISK_FSSM 4202
#endif

/*E*/

/*F*/ /* declarations */

typedef struct GlobalData {
   struct Library *gd_SysBase, *gd_DOSBase, *gd_UtilityBase;
   struct MsgPort *gd_Port;
   struct DosList *gd_DosList;
   struct Process *gd_We;
} *GD;

#define SysBase (gd->gd_SysBase)
#define DOSBase (gd->gd_DOSBase)
#define UtilityBase (gd->gd_UtilityBase)

#define BCPL_MAXNAME 256
struct PrivateStreamData
{
   struct IOStdReq *psd_IO;
   UBYTE  psd_DOSName[BCPL_MAXNAME];
   UBYTE  psd_Device[32];
   ULONG  psd_Unit;
   ULONG  psd_Flags;
   ULONG  psd_BufMemType;
   LONG  psd_LoCyl, psd_HiCyl;
   LONG  psd_SectorSize;
   LONG  psd_Offset;
   LONG  psd_MaxLength;

   LONG  psd_Pos;
   LONG  psd_Len;
   LONG  psd_Left;
   UBYTE *psd_Buffer;
   UBYTE *psd_Ptr;
   BOOL   psd_Write;
};

#define MIN __builtin_min
#define MAX __builtin_min

#define STREAMNAME_TEMPLATE "DOSNAME/A,MAXLENGTH,LOCYL/K,HICYL/K"
enum { ARG_DOSNAME=0, ARG_MAXLENGTH, ARG_LOCYL, ARG_HICYL, ARG_COUNT };

/*E*/

/*F*/ /* entry */
static ULONG main(void);
static ULONG __saveds __asm entry(register __a0 UBYTE *line, register __d0 ULONG len)
{
   return(main());
}

const static UBYTE version[]= "\0$VER: Device-Handler 1.5 "__AMIGADATE__;

/*E*/

/*F*/ static struct DosPacket *WaitDosPacket(GD gd, struct MsgPort *port)

{
   WaitPort(port);
   return (struct DosPacket *)(GetMsg(port)->mn_Node.ln_Name);
}
/*E*/
/*F*/ static void ReplyDosPacket(GD gd, struct MsgPort *port, struct DosPacket *Packet,LONG Res1,LONG Res2)
{
   struct MsgPort *reply = Packet->dp_Port;

   Packet->dp_Port = port;
   Packet->dp_Link->mn_Node.ln_Name = (char *)Packet;
   Packet->dp_Res1 = Res1;
   Packet->dp_Res2 = Res2;

   PutMsg (reply, Packet->dp_Link);
}
/*E*/

/*F*/ static BOOL openres(GD gd)
{
   BOOL rc = FALSE;

   d(("entered\n"))

   DOSBase = NULL;
   UtilityBase = NULL;
   gd->gd_Port = NULL;

   if (SysBase->lib_Version >= 37)
   {
      if (DOSBase = OpenLibrary("dos.library", 37))
      {
         if (UtilityBase = OpenLibrary("utility.library", 37))
         {
            if (gd->gd_Port = CreateMsgPort())
            {
               rc = TRUE;
            }
            else d(("no handler port\n"))
         }
         else d(("no utility V37\n"))
      }
      else d(("no dos V37\n"))
   }
   else d(("no exec V37\n"))

   d(("left: %ld\n",rc))

   return rc;
}
/*E*/
/*F*/ static void closeres(GD gd)
{
   d(("freeing port\n"))
   if (gd->gd_Port) DeleteMsgPort(gd->gd_Port);

   d(("closing dos\n"))
   if (DOSBase) CloseLibrary(DOSBase);

   d(("closing utility\n"))
   if (UtilityBase) CloseLibrary(UtilityBase);
}
/*E*/

/*F*/ BOOL getul(GD gd, UBYTE *s, LONG *number)
{

#define IsDigit(c) ((c) >= '0' && (c) <= '9'))
#define IsXDigit(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'A' && (c) <= 'F'))

   BOOL hex, rc = TRUE;
   ULONG num;
   UBYTE c;
   WORD len;

   if (len = strlen(s))
   {
      if (!Strnicmp(s, "0x", 2))
      {
         hex = TRUE;
         s += 2;
      }
      else if (!Strnicmp(s, "$", 2))
      {
         hex = TRUE;
         s++;
      }
      else hex = FALSE;

      for(num=0;*s;s++)
      {
         c = ToUpper(*s);
         if (hex)
         {
            if (c >= '0' && c <= '9') num = num * 16 + c - '0';
            else if (c >= 'A' && c <= 'F') num = num * 16 + c - '0';
            else break;
         }
         else
         {
            if (c >= '0' && c <= '9') num = num * 10 + c - '0';
            else break;
         }
      }

      if (*s)
      {
         if (c == 'K') num *= 1024;
         else if (c == 'M') num *= 1024*1024;
         else rc = FALSE;
      }
   }
   else rc = FALSE;

   if (rc) *number = num;

   return rc;
}
/*E*/
/*F*/ static void stream2rdargs(UBYTE *buffer)
{
   UBYTE *p;
   enum { s_norm, s_esc, s_quote } s;

   for (p = buffer, s = s_norm; *p; p++)
      switch(s)
      {
         case s_norm:
            if (*p == '.') *p = '=';
            if (*p == '/') *p = '\040';
            else if (*p == '"') s = s_quote;
         break;
         case s_esc:
            s = s_quote;
         break;
         case s_quote:
            if (*p == '*') s = s_esc;
            if (*p == '"') s = s_norm;
         break;
      }
}
/*E*/
/*F*/ static BOOL streamspecs(GD gd, struct PrivateStreamData *psd, BPTR streamname, LONG *res)
{
   UBYTE *temp, *bstr, *start;
   BOOL rc = FALSE;
   LONG rdparam[ARG_COUNT];
   struct RDArgs *inrda, *rda;

   bstr = (UBYTE *)BADDR(streamname);

   if (temp = AllocVec(bstr[0]+2, MEMF_ANY))
   {
      if (inrda = AllocDosObject(DOS_RDARGS, NULL))
      {
         strncpy(temp, bstr+1, bstr[0]);
         temp[bstr[0]] = '\n';
         temp[bstr[0]+1] = '\0';

         if (start = (UBYTE*)strchr((char*)temp, ':'))
         {
            stream2rdargs(++start);

            inrda->RDA_Source.CS_Buffer = start;
            inrda->RDA_Source.CS_Length = strlen(start);
            inrda->RDA_Source.CS_CurChr = 0;

            psd->psd_MaxLength = 0x7fffffff;
            psd->psd_LoCyl = 0;
            psd->psd_HiCyl = 0x7fffffff;
            memset(rdparam, 0, ARG_COUNT*sizeof(LONG));
            d(("readargs(%s)\n",start))
            if (rda = ReadArgs((UBYTE*)STREAMNAME_TEMPLATE, rdparam, inrda))
            {
               rc = TRUE;

               strcpy(psd->psd_DOSName, (UBYTE*)rdparam[ARG_DOSNAME]);
               if (rdparam[ARG_MAXLENGTH]) rc = getul(gd,(UBYTE*)rdparam[ARG_MAXLENGTH], &psd->psd_MaxLength);
               if (rc && rdparam[ARG_LOCYL]) rc = getul(gd,(UBYTE*)rdparam[ARG_LOCYL], &psd->psd_LoCyl);
               if (rc && rdparam[ARG_HICYL]) rc = getul(gd,(UBYTE*)rdparam[ARG_HICYL], &psd->psd_HiCyl);

               d(("after ReadArgs(): doshandler %s maxlen %lu locyl %lu hicyl %lu\n",
                  psd->psd_DOSName,psd->psd_MaxLength,psd->psd_LoCyl,psd->psd_HiCyl))

               FreeArgs(rda);
            }
            else
            {
               d(("ReadArgs() failed\n"))
               *res = ERROR_BAD_STREAM_NAME;
            }
         }
         else
         {
            d(("no proper name\n"))
            *res = ERROR_BAD_STREAM_NAME;
         }

         FreeDosObject(DOS_RDARGS, inrda);
      }
      else
      {
         d(("no RDArgs\n"))
         *res = ERROR_NO_FREE_STORE;
      }

      FreeVec(temp);
   }
   else
   {
      d(("no more memory\n"))
      *res = ERROR_NO_FREE_STORE;
   }

   return rc;
}
/*E*/

/*F*/ static BOOL FindDevice(GD gd, struct PrivateStreamData *psd)
{
   struct DosList *dol;
   struct FileSysStartupMsg *fssm=NULL;
   BOOL rc = FALSE;
   UBYTE *name;
   UBYTE n;
   UBYTE pat[40];

   d(("entered: %s\n", psd->psd_DOSName))

   n = strlen(psd->psd_DOSName);

   if (dol = LockDosList(LDF_READ | LDF_DEVICES))
   {
      while(dol = NextDosEntry(dol, LDF_DEVICES))
      {
         name = (UBYTE*)BADDR(dol->dol_Name);

         memset(pat,0,40);
         strncpy(pat,name+1,name[0]);
         d(("%s\n",pat))

         if ((n == name[0]) && !Strnicmp((char*)(name+1), psd->psd_DOSName, n))
         {
            d(("found\n"))

            fssm = (struct FileSysStartupMsg*)BADDR(dol->dol_misc.dol_handler.dol_Startup);

            if (fssm)
            {
               struct DosEnvec *de = (struct DosEnvec *)BADDR(fssm->fssm_Environ);

               d(("found fssm\n"))
                  /* note: fssm->fssm_Device is a NUL terminated BSTR!! :-) */
               strcpy((char*)psd->psd_Device, (char*)BADDR(fssm->fssm_Device)+1);
               psd->psd_Flags = fssm->fssm_Flags;
               psd->psd_Unit = fssm->fssm_Unit;
               psd->psd_BufMemType = de->de_BufMemType;
               psd->psd_SectorSize = 4 * de->de_SizeBlock;
               psd->psd_HiCyl = MIN(psd->psd_HiCyl, de->de_HighCyl);
               psd->psd_LoCyl = MIN(psd->psd_LoCyl, psd->psd_HiCyl);
               d(("de->de_BlocksPerTrack %ld, psd->psd_LoCyl %ld, de->de_Surfaces %ld, psd->psd_SectorSize %ld\n",
                  de->de_BlocksPerTrack,psd->psd_LoCyl,de->de_Surfaces,psd->psd_SectorSize))
               psd->psd_Offset = de->de_BlocksPerTrack * psd->psd_LoCyl * de->de_Surfaces * psd->psd_SectorSize;

               psd->psd_Len = de->de_BlocksPerTrack * psd->psd_SectorSize * (psd->psd_HiCyl - psd->psd_LoCyl + 1) * de->de_Surfaces;
               psd->psd_Len = MIN(psd->psd_MaxLength, psd->psd_Len);
               d(("len %lu\n",psd->psd_Len))

               d(("device %s, unit %ld, flags %ld, secsiz %ld, off %ld\n",
                  psd->psd_Device,psd->psd_Unit,psd->psd_Flags,psd->psd_SectorSize,psd->psd_Offset))

               rc = TRUE;
            }
            else
               d(("no fssm\n"))

            break;
         }
      }

      UnLockDosList(LDF_READ | LDF_DEVICES);
   }

   return(rc);
}
/*E*/

/*F*/ static void __regargs OpenDH(GD gd, struct DosPacket *Pkt,ULONG *OpenCnt)
{
   struct FileHandle *FH;
   struct PrivateStreamData *psd;
   struct MsgPort *IOPort;
   BOOL ok = FALSE;

   Pkt->dp_Res1 = DOSFALSE;
   FH = (struct FileHandle *)BADDR(Pkt->dp_Arg1);
   FH->fh_Port = (struct MsgPort *)FALSE;
   FH->fh_Arg1 = 0L;

   d(("allocing psd\n"))
   if (psd = AllocVec(sizeof(*psd),MEMF_PUBLIC|MEMF_CLEAR))
   {
      if (streamspecs(gd, psd, (BPTR)Pkt->dp_Arg3, &Pkt->dp_Res2))
      {
         d(("creating msgport\n"))
         if (IOPort=CreateMsgPort())
         {
            if (psd->psd_IO=CreateIORequest(IOPort, sizeof(struct IOStdReq)))
            {
               if (FindDevice(gd, psd))
               {
                  if (OpenDevice(psd->psd_Device, psd->psd_Unit, (struct IORequest *)psd->psd_IO, psd->psd_Flags)==0L)
                  {
                     if (psd->psd_Buffer = AllocVec(psd->psd_SectorSize, psd->psd_BufMemType))
                     {
                        if (Pkt->dp_Type == ACTION_FINDOUTPUT)
                        {
                           psd->psd_Write = TRUE;
                           psd->psd_IO->io_Command = CMD_WRITE;
                        }
                        else
                        {
                           psd->psd_Write = FALSE;
                           psd->psd_IO->io_Command = CMD_READ;
                        }
                        psd->psd_Ptr = psd->psd_Buffer;
                        psd->psd_Pos = 0;
                        psd->psd_Left = 0;

                        FH->fh_Arg1 = (LONG)psd;

                        Pkt->dp_Res1 = DOSTRUE;
                        Pkt->dp_Res2 = 0L;

                        (*OpenCnt)++;
                        ok = TRUE;
                     }
                     else Pkt->dp_Res2 = ERROR_NO_FREE_STORE;

                     if (!ok) CloseDevice ((struct IORequest *)psd->psd_IO);
                  }
                  else
                  {
                     d(("could not open device >%s<\n",psd->psd_Device))
                     Pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
                  }
               }
               else
               {
                  d(("no device\n"))
                  Pkt->dp_Res2 = ERROR_OBJECT_NOT_FOUND;
               }

               if (!ok) DeleteIORequest(psd->psd_IO);
            }
            else
            {
               d(("no iorequest\n"))
               Pkt->dp_Res2 = ERROR_NO_FREE_STORE;
            }

            if (!ok) DeleteMsgPort (IOPort);
         }
         else
         {
            d(("no msgport\n"))
            Pkt->dp_Res2 = ERROR_NO_FREE_STORE;
         }
      }
      else
      {
         d(("no streamspecs()\n"))
      }
      if (!ok) FreeVec (psd);
   }
   else
   {
       d(("no psd\n"))
       Pkt->dp_Res2 = ERROR_NO_FREE_STORE;
   }

   d(("leaving OpenDH() with Res2 = %ld\n",Pkt->dp_Res2))
}
/*E*/
/*F*/ static void __regargs CloseDH(GD gd, struct PrivateStreamData *psd,ULONG *OpenCnt)
{
   d(("CloseDH()\n"))
   if (psd->psd_Write)
   {
      if (psd->psd_Left < psd->psd_SectorSize)
      {
         d(("DoIO(CMD_WRITE)\n"))

         memset(psd->psd_Ptr,'\0',psd->psd_Left);
         psd->psd_IO->io_Length  = psd->psd_SectorSize;
         psd->psd_IO->io_Data    = psd->psd_Buffer;
         psd->psd_IO->io_Offset  = psd->psd_Pos + psd->psd_Left-psd->psd_SectorSize;
         DoIO((struct IORequest *)psd->psd_IO);
      }
      psd->psd_IO->io_Command=CMD_UPDATE;

      d(("DoIO(CMD_UPDATE)\n"))
      DoIO((struct IORequest *)psd->psd_IO);
   }

   d(("FreeVec() buffer\n"))

   FreeVec(psd->psd_Buffer);

   d(("stopping motor off\n"))
   psd->psd_IO->io_Command = TD_MOTOR;
   psd->psd_IO->io_Length = 0L;
   DoIO((struct IORequest *)psd->psd_IO);
   d(("closing device\n"))
   CloseDevice ((struct IORequest *)psd->psd_IO);

   d(("deleting msg port\n"))
   DeleteMsgPort (psd->psd_IO->io_Message.mn_ReplyPort);

   d(("deleting stdio\n"))
   DeleteIORequest (psd->psd_IO);

   d(("FreeVec() rh\n"))
   FreeVec (psd);

   (*OpenCnt)--;

   d(("leaving closedh()\n"))

}
/*E*/

/*F*/ static LONG __regargs RawRead(GD gd, struct PrivateStreamData *psd, UBYTE *Buffer, LONG Bytes, LONG *res2)
{
   LONG LatchBytes, RDBytes;
   BOOL error = FALSE;

      /* check bouunds */
   if ((psd->psd_Pos + Bytes) > psd->psd_Len) Bytes = psd->psd_Len - psd->psd_Pos;

   LatchBytes = Bytes;

   d(("read %ld bytes\n",Bytes))

   /*
   ** first, read what's left in buffer
   */
   if (psd->psd_Left)
   {
      RDBytes=MIN(psd->psd_Left, Bytes);
      CopyMem(psd->psd_Ptr, Buffer, RDBytes);

      Buffer += RDBytes;
      Bytes -= RDBytes;

      psd->psd_Pos += RDBytes;   /* incr. position in file */
      psd->psd_Ptr += RDBytes;
      psd->psd_Left -= RDBytes;
   }

   /*
   ** second, try to read as much blocks as possible directly to
   ** user buffer, avoiding CopyMem()
   */
   if (!error && (Bytes > psd->psd_SectorSize))
   {
      RDBytes = (Bytes / psd->psd_SectorSize) * psd->psd_SectorSize;

      psd->psd_IO->io_Data = Buffer;
      psd->psd_IO->io_Length = RDBytes;
      psd->psd_IO->io_Offset = psd->psd_Pos + psd->psd_Offset;
      if (DoIO((struct IORequest *)psd->psd_IO))
      {
         RDBytes = psd->psd_IO->io_Actual;
         error = TRUE;
         *res2 = psd->psd_IO->io_Error;
      }
      Buffer += RDBytes;
      Bytes -= RDBytes;

      psd->psd_Pos += RDBytes;         /* incr. position in file */
      psd->psd_Left = 0;               /* for safety, initialise buffer */
      psd->psd_Ptr = psd->psd_Buffer;
   }

   if (!error && Bytes)
   {
      /*
      ** now there are only some bytes < SectorSize left, so they fit into one buffer
      ** So, read a buffer and copy some bytes from it to user-buffer.
      */
      RDBytes = Bytes;
      psd->psd_IO->io_Length = psd->psd_SectorSize;
      psd->psd_IO->io_Data = psd->psd_Buffer;
      psd->psd_IO->io_Offset = psd->psd_Pos + psd->psd_Offset;

      if (DoIO((struct IORequest *)psd->psd_IO))
      {
         *res2 = psd->psd_IO->io_Error;
         RDBytes = psd->psd_IO->io_Actual;
      }

      CopyMem(psd->psd_Buffer, Buffer, RDBytes);

      psd->psd_Pos += RDBytes;
      psd->psd_Ptr = psd->psd_Buffer + RDBytes;
      psd->psd_Left = psd->psd_SectorSize - RDBytes;

      Bytes -= RDBytes;
   }

   return LatchBytes - Bytes;
}
/*E*/
/*F*/ static LONG __regargs RawWrite(GD gd, struct PrivateStreamData *psd,UBYTE *Buffer,LONG Bytes, LONG *res2)
{
   LONG LatchBytes,WRBytes;
   BOOL error = FALSE;

      /* check bounds */
   if ((psd->psd_Pos + Bytes) > psd->psd_Len) Bytes = psd->psd_Len-psd->psd_Pos;

   LatchBytes = Bytes;

   d(("write %ld bytes\n",Bytes))

   /*
   ** first, take care for outstanding data
   */
   if (Bytes && psd->psd_Left)
   {
      WRBytes = MIN(psd->psd_Left, Bytes);
      CopyMem(Buffer, psd->psd_Ptr, WRBytes);

      psd->psd_Ptr += WRBytes;      /* incr. position in buffer */
      psd->psd_Left -= WRBytes;     /* decr. number of unused bytes */

      if (psd->psd_Left == 0)       /* Buffer full ? */
      {                             /* then write it out */
         psd->psd_IO->io_Data = psd->psd_Buffer;
         psd->psd_IO->io_Length = psd->psd_SectorSize;
         psd->psd_IO->io_Offset = psd->psd_Pos + psd->psd_Offset;
         psd->psd_IO->io_Command = CMD_WRITE;
         if (DoIO((struct IORequest *)psd->psd_IO))
         {
            *res2 = psd->psd_IO->io_Error;
            WRBytes = psd->psd_IO->io_Actual;
            error = TRUE;
         }

         psd->psd_Ptr = psd->psd_Buffer;  /* reset buffer pointers */
         psd->psd_Left = 0;
      }

      Buffer += WRBytes;
      Bytes -= WRBytes;
      psd->psd_Pos += WRBytes;      /* incr. position in file */
   }

   if (!error && Bytes)
   {
      /*
      ** second, try to write as much blocks as possible directly from
      ** user buffer in one go, avoiding CopyMem() and multiple I/O-Requests
      */
      if (Bytes > psd->psd_SectorSize)
      {
            /* I don't like to use bit masking at this point */
         WRBytes = (Bytes / psd->psd_SectorSize) * psd->psd_SectorSize;

         psd->psd_IO->io_Data = Buffer;
         psd->psd_IO->io_Length = WRBytes;
         psd->psd_IO->io_Offset = psd->psd_Pos + psd->psd_Offset;
         psd->psd_IO->io_Command = CMD_WRITE;
         if (DoIO((struct IORequest *)psd->psd_IO))
         {
            *res2 = psd->psd_IO->io_Error;
            WRBytes = psd->psd_IO->io_Actual;
            error = TRUE;
         }

         Buffer += WRBytes;
         Bytes -= WRBytes;
         psd->psd_Pos += WRBytes;   /* incr. position in file */

         psd->psd_Ptr = psd->psd_Buffer;  /* for safety, reset buffer pointers */
         psd->psd_Left = 0;
      }
   }

   if (!error && Bytes)
   {
      /*
      ** now there are only some bytes < SectorSize left, and they fit into one
      ** buffer.
      ** NOTE: the actual writing is delayed to Close() or further Write()'s
      */
      WRBytes = Bytes;
      CopyMem(Buffer, psd->psd_Buffer, WRBytes);

      psd->psd_Pos += WRBytes;     /* incr. position in file */
      Bytes -= WRBytes;
      Buffer += WRBytes;

      psd->psd_Ptr = psd->psd_Buffer + WRBytes;     /* set position in buffer */
      psd->psd_Left = psd->psd_SectorSize - WRBytes; /* set number of unused bytes */
   }

   return LatchBytes - Bytes;
}
/*E*/

/*F*/ static ULONG main(void)
{
   struct GlobalData GlobalData;
   struct GlobalData *gd;

   struct DosPacket *Pkt;
   BOOL Done;
   ULONG OpenCnt;

   gd = &GlobalData;

   d(("Hello, this is the DEV-Handler\n"))

   SysBase = *(struct Library**)4L;

   if ((gd->gd_We = (struct Process *)FindTask(NULL))->pr_CLI) return RETURN_FAIL;

   d(("waiting for startup-packet...\n"))
   Pkt = WaitDosPacket(gd, &gd->gd_We->pr_MsgPort);

   if (openres(gd))
   {
      gd->gd_DosList = (struct DosList *)BADDR(Pkt->dp_Arg3);
      gd->gd_DosList->dol_Task = gd->gd_Port;

      d(("replying startup-packet...\n"))
      ReplyDosPacket(gd, gd->gd_Port, Pkt, DOSTRUE, Pkt->dp_Res2);

      Done=FALSE;
      OpenCnt=0L;

      d(("entering mainloop\n"))

      while (!Done)
      {
         Pkt=WaitDosPacket(gd, gd->gd_Port);

         d(("got packet %ld\n",Pkt->dp_Type))

         Pkt->dp_Res1 = DOSFALSE;
         Pkt->dp_Res2 = ERROR_ACTION_NOT_KNOWN;

         switch (Pkt->dp_Type)
         {
            case ACTION_FINDINPUT:
            case ACTION_FINDOUTPUT:
               OpenDH (gd, Pkt, &OpenCnt);
               ReplyDosPacket(gd, gd->gd_Port,Pkt, Pkt->dp_Res1, Pkt->dp_Res2);
            break;

            case ACTION_END:
               CloseDH (gd, (struct PrivateStreamData *)Pkt->dp_Arg1,&OpenCnt);
               ReplyDosPacket(gd, gd->gd_Port,Pkt, DOSTRUE, 0);
            break;

            case ACTION_READ:
            {
               struct PrivateStreamData *psd = (struct PrivateStreamData *)Pkt->dp_Arg1;

               if (psd && !psd->psd_Write)
               {
                  LONG Bytes, error;

                  Bytes = RawRead(gd, (struct PrivateStreamData *)Pkt->dp_Arg1,
                                      (UBYTE *)Pkt->dp_Arg2,Pkt->dp_Arg3, &error);

                  ReplyDosPacket(gd, gd->gd_Port,Pkt, Bytes, error);
               }
            }
            break;

            case ACTION_WRITE:
            {
               struct PrivateStreamData *psd = (struct PrivateStreamData *)Pkt->dp_Arg1;

               if (psd && psd->psd_Write)
               {
                  LONG Bytes, error;

                  Bytes = RawWrite(gd, (struct PrivateStreamData *)Pkt->dp_Arg1,
                                       (UBYTE *)Pkt->dp_Arg2, Pkt->dp_Arg3, &error);

                  ReplyDosPacket(gd, gd->gd_Port,Pkt, Bytes, error);
               }
            }
            break;

            case ACTION_IS_FILESYSTEM:
               ReplyDosPacket(gd, gd->gd_Port,Pkt, DOSFALSE, 0);
            break;

            case ACTION_SEEK:
            case ACTION_SET_FILE_SIZE:
               ReplyDosPacket(gd, gd->gd_Port,Pkt, -1,ERROR_ACTION_NOT_KNOWN);
            break;

            case ACTION_DIE:
               if (OpenCnt==0L)
               {
                  d(("opencnt = 0\n"))

                  if (IsListEmpty(&gd->gd_Port->mp_MsgList))
                  {
                     gd->gd_DosList->dol_Task = NULL;

                     ReplyDosPacket(gd, gd->gd_Port,Pkt, DOSTRUE, 0);

                     Done=TRUE;
                  }
                  else ReplyDosPacket(gd, gd->gd_Port,Pkt, DOSFALSE,ERROR_OBJECT_IN_USE);
               }
               else ReplyDosPacket(gd, gd->gd_Port,Pkt, DOSFALSE,ERROR_OBJECT_IN_USE);
            break;

            default:
               ReplyDosPacket(gd, gd->gd_Port, Pkt, DOSFALSE, ERROR_ACTION_NOT_KNOWN);
            break;
         }
      }
   }
   else ReplyDosPacket(gd, &gd->gd_We->pr_MsgPort, Pkt, DOSFALSE, ERROR_NO_FREE_STORE);

   closeres(gd);

   d(("returning\n"))

   return RETURN_OK;
}
/*E*/

