]> pd.if.org Git - mmurtl/commitdiff
autocommit for file dated 1995-02-09 11:10:22
authorRichard Burgess <>
Thu, 9 Feb 1995 11:10:22 +0000 (11:10 +0000)
committerNathan Wagner <nw@hydaspes.if.org>
Mon, 17 Oct 2016 14:03:48 +0000 (14:03 +0000)
ossource/fsys.c [new file with mode: 0644]

diff --git a/ossource/fsys.c b/ossource/fsys.c
new file mode 100644 (file)
index 0000000..9cf0263
--- /dev/null
@@ -0,0 +1,3974 @@
+/* This is the MMURTL, MS-DOS Compatible (FAT) File system.  */\r
+\r
+/*\r
+  MMURTL Operating System Source Code\r
+  Copyright 1991,1992,1993,1994 Richard A. Burgess\r
+  ALL RIGHTS RESERVED   Version 1.0\r
+*/\r
+\r
+/*\r
+About MS-DOS disk formats and how MMURTL handles them.\r
+\r
+Physical Disk Layouts\r
+  From the disk controller's standpoint:\r
+         Cylinder numbers run from 0 to nMaxCyls-1.\r
+         Head numbers run from 0 to nMaxheads-1.\r
+         Sector numbers run from 1 to nMaxSectorsPerTrack.\r
+\r
+Physical (Absolute) Sector Numbers\r
+\r
+  Physical sector numbers (absolute) begin at Cyl 0, Head 0, Sector 1.\r
+  As the physical sector number rolls over (nMaxSectorsPerTrack+1),\r
+  the Head number is incremented which moves us to the next track\r
+  (same cylinder, next head).  When the head number rolls over\r
+  (nMaxHeads is reached) the cylinder number is incremented.\r
+\r
+  Note: Track and cylinder are NOT interchangable terms in the\r
+  above text.  If you have 6 heads on your drive, you have\r
+  6 tracks per cylinder.  This can be confusing because many\r
+  books and documents use the terms interchangably.\r
+  And you can, so long as you know that's what you're doing.\r
+\r
+Hidden Sectors\r
+\r
+  MS-DOS reserves a section of the physical hard disk. This area\r
+  is called the Hidden Sectors.  This is usually the very first\r
+  track on the disk (begins at Cyl 0, head 0, Sector 1).  The\r
+  partition tables are kept at the very end of the first sector in\r
+  this hidden area (offset 01BEh in the first sector to be exact).\r
+  The partition tables are 16 byte entries that describe\r
+  "logical" sections of the disk that can be treated as separate drives.\r
+  There are usually no "hidden sectors" on floppy disks, nor are there\r
+  any partition tables.\r
+\r
+\r
+MMURTL Logical Block Address (LBA)\r
+\r
+  MMURTL device drivers treat the entire disk as a single physical\r
+  drive.  The MMURTL file system reads the partition tables,\r
+  then sets up the device driver to span the entire physical disk\r
+  as 0 to nMaxBlockNumbers-1.  This is refered to as the Logical Block\r
+  Address (LBA) and is the value passed in to the DeviceOp call for\r
+  the MMURTL hard/floppy disk device drivers (LBAs are used with\r
+  all MMURTL devices).\r
+\r
+  Note: DO NOT confuse MMURTL's LBA for the sector number in an MS-DOS\r
+  logical drive.  MMURTL calls these logical blocks because we still\r
+  have to convert them into physical cylinder, head and sector to\r
+  retrieve the data.\r
+\r
+MS-DOS Boot Sector\r
+\r
+  The first sector of an MS-DOS logical drive will be its boot\r
+  sector. Each of the MS-DOS logical partitions will have a boot\r
+  sector although only the first will be marked as bootable (if any are).\r
+  It's position on the disk is calculated from the partition table\r
+  information.\r
+\r
+MMURTL File System Initialization\r
+\r
+  The MMURTL-FAT file system reads the partition table and saves\r
+  the starting LBA and length of each of DOS logical disk that is\r
+  found.  Armed with this information, MMURTL can access each of\r
+  the DOS logical disks as a separate disk drive. To maintain some\r
+  sanity, the MMURTL file system gives all of its logical drives\r
+  a letter just like MS-DOS.  MMURTL supports two floppy drives\r
+  (A & B) and up to eight logical hard disk (C-J).  All information\r
+  on the Logical Drives are kept in an array of records (Ldrvs).\r
+  This includes the logical letter to physical drive conversions.\r
+\r
+  Once we have the layout of each of the partitons, we read the boot\r
+  sector from the first DOS logical drive.  The boot sector contains\r
+  several pieces of important information about the drive geometry\r
+  (numbers of heads, sectors per track, etc.), which are also placed\r
+  in the Logical Drive stuctures.\r
+\r
+  Once we have the drive geometry information we setup the MMURTL\r
+  device driver.  This tells the device driver how many cylinders,\r
+  heads and sectors per track are on the physical disk.\r
+  Until this is done, the device driver assumes a minimum drive size\r
+  and you should only read the partition table (or boot sector if no\r
+  partition table is on the disk).  This provides enough\r
+  information to do a DeviceInit call to set up proper drive\r
+  geometry.\r
+\r
+  If you were building a loadable file system to replace the one that\r
+  is included in MMURTL, you would call your routine to initialize\r
+  the file system very early in the main program block.  You must\r
+  not service file system requests until this is done.\r
+\r
+*/\r
+\r
+#define U32 unsigned long\r
+#define U16 unsigned int\r
+#define U8  unsigned char\r
+#define S32 long\r
+#define S16 int\r
+#define S8  char\r
+#define TRUE 1\r
+#define FALSE 0\r
+\r
+/*********** MMURTL Public Prototypes ***************/\r
+\r
+/* From MKernel */\r
+\r
+extern far AllocExch(long *pExchRet);\r
+extern far U32 GetTSSExch(U32  *pExchRet);\r
+extern far SpawnTask(char *pEntry,\r
+                            long dPriority,\r
+                     long fDebug,\r
+                     char *pStack,\r
+                            long fOSCode);\r
+extern far long WaitMsg(long Exch, char *pMsgRet);\r
+extern far long CheckMsg(long Exch, char *pMsgRet);\r
+extern far long Request(unsigned char *pSvcName,\r
+                                               unsigned int  wSvcCode,\r
+                                               unsigned long dRespExch,\r
+                                               unsigned long *pRqHndlRet,\r
+                                               unsigned long dnpSend,\r
+                                               unsigned char *pData1,\r
+                                               unsigned long dcbData1,\r
+                                               unsigned char *pData2,\r
+                                               unsigned long dcbData2,\r
+                                               unsigned long dData0,\r
+                                               unsigned long dData1,\r
+                                               unsigned long dData2);\r
+\r
+extern far long Respond(long dRqHndl, long dStatRet);\r
+\r
+/* From MData */\r
+extern far void CopyData(U8 *pSource, U8 *pDestination, U32 dBytes);\r
+extern far void FillData(U8 *pDest, U32 cBytes, U8 bFill);\r
+extern far long CompareNCS(U8 *pS1, U8 *pS2, U32 dSize);\r
+\r
+/* From MTimer.h */\r
+extern far long GetCMOSTime(long *pTimeRet);\r
+extern far long GetCMOSDate(long *pTimeRet);\r
+extern far long GetTimerTick(long *pTickRet);\r
+\r
+/* From MVid.h */\r
+extern far long TTYOut (char *pTextOut, long ddTextOut, long ddAttrib);\r
+extern far long GetNormVid(long *pNormVidRet);\r
+\r
+#include "MKbd.h"\r
+\r
+/* From MDevDrv */\r
+extern far U32  DeviceOp(U32  dDevice,\r
+                                U32  dOpNum,\r
+                                                U32  dLBA,\r
+                                                U32  dnBlocks,\r
+                                                U8  *pData);\r
+\r
+extern far U32  DeviceStat(U32  dDevice,\r
+                                                  S8 * pStatRet,\r
+                                                  U32  dStatusMax,\r
+                                                  U32  *pdSatusRet);\r
+\r
+extern far U32  DeviceInit(U32  dDevNum,\r
+                                                  S8  *pInitData,\r
+                                                  U32   sdInitData);\r
+\r
+/* From MMemory.h */\r
+extern far U32 AllocOSPage(U32 nPages, U8 *ppMemRet);\r
+extern far U32 DeAllocPage(U8 *pOrigMem, U32 nPages);\r
+\r
+/* From MJob.h */\r
+extern far U32 GetPath(long JobNum, char *pPathRet, long *pdcbPathRet);\r
+extern far U32 RegisterSvc(S8 *pName, U32 Exch);\r
+\r
+/* NEAR support for debugging */\r
+\r
+extern long xprintf(char *fmt, ...);\r
+extern U32 Dump(unsigned char *pb, long cb);\r
+\r
+/* File System error codes */\r
+\r
+#define ErcOK                   0              /* Alls Well */\r
+#define ErcEOF                  1              /* DUH... The END */\r
+#define ErcBadSvcCode   32             /* Service doesn't handle that code */\r
+\r
+#define ErcBadFileSpec  200    /* invalid file spec (not correct format)*/\r
+#define ErcNoSuchDrive  201    /* Try another letter bozo */\r
+#define ErcNotAFile             202    /* Open a directory?? NOT */\r
+#define ErcNoSuchFile   203    /* No can do! It ain't there...*/\r
+#define ErcNoSuchDir    204    /* Ain't no such dir... */\r
+#define ErcReadOnly             205    /* You can't modify it bubba */\r
+#define ErcNoFreeFCB    206    /* We're really hurtin... */\r
+#define ErcBadOpenMode  207    /* Say what? Mode??? */\r
+#define ErcFileInUse    208    /* File is open in an incompatible mode */\r
+#define ErcNoFreeFUB    209    /* Sorry, out of File User Blocks */\r
+#define ErcBadFileHandle 210   /* WHOAAA, bad handle buddy! */\r
+#define ErcBrokenFile   211    /* Cluster chain broken on file */\r
+#define ErcBadFCB               213    /* We got REAL problems... */\r
+#define ErcStreamFile   214    /* Operation not allowed on Stream File */\r
+#define ErcBlockFile    215    /* Operation not allowed on Block File */\r
+#define ErcBeyondEOF    217    /* SetLFA or Read/WriteBlock beyond EOF */\r
+#define ErcNoParTable   218    /* No partiton table found on disk!!! */\r
+#define ErcBadFATClstr   220    /* File system screwed up (or your disk) */\r
+#define ErcRenameDrv     222   /* They have tried to rename across Dir/Vol*/\r
+#define ErcRenameDir     223   /* They have tried to rename across Dir/Vol*/\r
+#define ErcNoMatch       224   /* No matching directory entry */\r
+\r
+#define ErcWriteOnly    225    /* Attempt to read write-only device */\r
+#define ErcDupName              226    /* Name exists as a file or dir already */\r
+#define ErcNotSupported         227    /* Not supported on this file  */\r
+#define ErcRootFull             228    /* The Root Directory is Full  */\r
+#define ErcDiskFull             230    /* No more free CLUSTERS!!!  */\r
+\r
+#define ErcNewMedia             605    /* for floppy mounting from FDD */\r
+\r
+/**************** FAT Buffer control structure **********************/\r
+\r
+/*\r
+   The Fat structures are for keeping track of the FAT buffers.\r
+   We never want to have more than one copy of a FAT sector in\r
+   memory at one time, and we also never want to read one when\r
+   its already here (a waste of time)!  We also keep track\r
+   of the last time it was used and deallocate the oldest (LRU -\r
+   Least Recently Used). Initially filling out the Fat control\r
+   structure is part of the file system initialization.  If the\r
+   FAT sector we are in has been modified (data written to clusters\r
+   in it & FAT updated) we write it ASAP!\r
+   Each FAT buffer is 1 sector long, except the first one which\r
+   is 3 sectors for floppies (FAT12 types). This is because the\r
+   FAT12 entires span sectors!\r
+*/\r
+\r
+#define nFATBufs 17            /* 1 Static for floppies + 16 * 512 = 8192, 2 pages */\r
+\r
+static struct fattype {                                /* */\r
+       U8  *pBuf;                      /* points to beginning of fat buffer  */\r
+       U32 LastUsed;           /* Tick when last used (0 = Never) */\r
+       U32 LBASect;            /* LBA of first FAT sect in buf (where it came from) */\r
+       U16 iClstrStart;        /* Starting cluster for each buf  */\r
+       U8  Drive;                      /* LDrive this FAT sector is from */\r
+       U8  fModLock;           /* Bit 0 = Modified, bit 1 = Locked  */\r
+       };\r
+\r
+static struct fattype Fat[nFATBufs];   /* 16 bytes * 17 */\r
+\r
+/* We read 3 sectors worth of floppy fat buf in cause cluster\r
+entries span sectors\r
+*/\r
+\r
+U8 FatBufA[1536];  /* floppy fat buffer */\r
+\r
+#define FATMOD  0x01\r
+#define FATLOCK 0x02\r
+\r
+/**************** File Contol Block Structures (FCBs) **********/\r
+/* One FCB is allocated and filled out for each file that is open.\r
+   The actual directory entry structure from the disk is embedded\r
+   in the FCB so it can be copied directly to/from the directory\r
+   sector on the disk.\r
+*/\r
+#define nFCBs 128\r
+#define sFCB  64\r
+\r
+static struct FCB {\r
+       S8  Name[8];            /* From here to Filesize is copy of DirEnt */\r
+       S8  Ext[3];\r
+       S8  Attr;                       /* from MS-DOS */\r
+       U8  Resvd1[10];         /* ????????  */\r
+       U16 Time;                       /* Only changed when created or updated */\r
+       U16 Date;\r
+       U16 StartClstr;         /* At least one per file!! */\r
+       U32 FileSize;           /* last entry in FAT Dir Ent (32 bytes) */\r
+       U32 LBADirSect;         /* LBA of directory sector this is from */\r
+       U16 oSectDirEnt;        /* Offset in sector for the dir entry */\r
+       U8  Ldrv;                       /* Logical drive this is on (A-J, 0-9) */\r
+       U8  Mode;                       /* 0 or 1 (Read or Modify). */\r
+       U8  nUsers;                     /* Active FUBs for this file (255 MAX). 0= Free FCB */\r
+    U8  fMod;                  /* This file was modified! */\r
+       U8  Resvd[22];          /* Out to 64 bytes */\r
+       };\r
+\r
+static struct FCB *paFCB;              /* a pointer to array of allocated FCBs. */\r
+static struct FCB *pFCB;               /* pointer to one FCB */\r
+\r
+/********************** File User Blocks **************************/\r
+\r
+/* Each user of an open file is assigned a FUB.  The FUB number is the\r
+   filehandle (beginning with 3). ) 0, 1 & 2 are reserved for NUL,\r
+   KBD and VID devices.\r
+*/\r
+\r
+#define nFUBs 128\r
+#define sFUB  32\r
+\r
+/* The FUB contains information on a file related to a user's view\r
+   of the file.  It is used to hold information on files opened\r
+   in stream and block mode. Three important fields in the FUB are:\r
+   LFABuf - LFA of first byte in buffer for a stream file.\r
+   Clstr - Clstr of last block read or stream fill.\r
+   LFAClstr - LFA of first byte in Clstr.\r
+\r
+   LFAClstr and Clstr give us a relative starting point when\r
+   reading a file from disk.  If we didn't save this information\r
+   on the last access, we would have to "run" the cluster chain\r
+   everytime we wanted to read or access a file beyond the last\r
+   point we read. \r
+*/\r
+\r
+struct FUB {\r
+       U16 Job;                        /* User's Job Number. 0 if FUB is free. */\r
+       U16 iFCB;                       /* FCB number for this file (0 to nFCBs-1) */\r
+       U32 CrntLFA;            /* Current Logical File Address (File Ptr) */\r
+       U8  *pBuf;                      /* Ptr to buffer if stream mode */\r
+       U32 sBuf;                       /* Size of buffer for Stream file in bytes */\r
+       U32     LFABuf;                 /* S-First LFA in Clstr Buffer */\r
+       U32 LFAClstr;           /* LFA of Clstr (below). */\r
+       U16 Clstr;                      /* Last Cluster read */\r
+       U8  fModified;          /* Data in buffer was modified */\r
+       U8  fStream;            /* NonZero for STREAM mode */\r
+       U8  Rsvd[4];            /* Pad to 32 bytes */\r
+       };\r
+\r
+static struct FUB *paFUB;              /* a pointer to allocated FUBs. Set up at init. */\r
+static struct FUB *pFUB;               /* a pointer to allocated FUBs. Set up at init. */\r
+\r
+/* Boot sector info (62 byte structure) */\r
+struct fsbtype {\r
+         U8  Jmp[3];\r
+         U8  OEMname[8];\r
+         U16 bps;\r
+         U8  SecPerClstr;\r
+         U16 ResSectors;\r
+         U8  FATs;\r
+         U16 RootDirEnts;\r
+         U16 Sectors;\r
+         U8  Media;\r
+         U16 SecPerFAT;\r
+         U16 SecPerTrack;\r
+         U16 Heads;\r
+         U32 HiddenSecs;\r
+         U32 HugeSecs;\r
+         U8  DriveNum;\r
+         U8  Rsvd1;\r
+         U8  BootSig;\r
+         U32 VolID;\r
+         U8  VolLabel[11];\r
+         U8  FileSysType[8];           /* 62 bytes */\r
+         };\r
+static struct fsbtype  fsb;\r
+\r
+\r
+/* Partition Table Entry info. 16 bytes */\r
+struct partent {\r
+  U8  fBootable;\r
+  U8  HeadStart;\r
+  U8  SecStart;\r
+  U8  CylStart;\r
+  U8  FATType;\r
+  U8  HeadEnd;\r
+  U8  SecEnd;\r
+  U8  CylEnd;\r
+  U32 nFirstSector;\r
+  U32 nSectorsTotal;\r
+  };\r
+\r
+static struct partent partab[4];       /* 4 partition table entries 64 bytes */\r
+static U16 partsig;\r
+\r
+/* Bit definitions in attribute field for a directory entry */\r
+\r
+#define ATTRNORM  0x00\r
+#define READONLY  0x01\r
+#define HIDDEN    0x02\r
+#define SYSTEM    0x04\r
+#define VOLNAME   0x08\r
+#define DIRECTORY 0x10\r
+#define ARCHIVE   0x20\r
+\r
+/* Directory Entry Record, 32 bytes */\r
+\r
+struct dirstruct {\r
+       U8  Name[8];\r
+       U8  Ext[3];\r
+       U8  Attr;\r
+       U8  Rsvd[10];\r
+       U16 Time;\r
+       U16 Date;\r
+       U16 StartClstr;\r
+       U32 FileSize;\r
+       };\r
+\r
+static struct dirstruct  dirent;\r
+\r
+static struct dirstruct *pDirEnt;              /* a pointer to a dir entry */\r
+\r
+/* When a file is opened, the filename is parsed into an array\r
+   to facilitate searching the directory tree.  IN MS-DOS all\r
+   dir and file names are SPACE padded (20h).  The FileSpec array\r
+   contains the fully parsed path of the file.  For instance,\r
+   If you were to open "A:\Dog\Food\IsGood.txt" the FileSpec\r
+   array would look like this:\r
+   FileSpec[0] = "DOG        "\r
+   FileSpec[1] = "FOOD       "\r
+   FileSpec[2] = "ISGOOD  TXT"\r
+   FileSpec[3][0] = NULL;\r
+   Note that the DOT is not inlcuded (it's not in the DOS directory\r
+   either), and the next unused FileSpec entry contain NULL in the\r
+   first byte. SpecDepth tells us how many directories deep the\r
+   name goes.\r
+*/\r
+\r
+static U8 FDrive                                       /* Drive parsed from file operation */\r
+static U8 FileSpec[7][11];                     /* Hierarchy from file spec parsing */\r
+static U8 SpecDepth;                           /* Depth of parse (0=Root File) */\r
+\r
+/* Used for Rename */\r
+static U8 FDrive1                                      /* Drive parsed from file operation */\r
+static U8 FileSpec1[7][11];            /* Hierarchy from file spec parsing */\r
+static U8 SpecDepth1;                          /* Depth of parse (0=Root File) */\r
+\r
+/* raw sector buffer for all kinds of stuff */\r
+\r
+static U8  abRawSector[516];\r
+static U8  abTmpSector[516];\r
+static U8  abDirSectBuf[516];\r
+\r
+/* These arrays keep track of physical drive data (0-4). */\r
+#define nPDrvs 4\r
+\r
+static struct phydrv {\r
+       U32 nHeads;             /* heads per drives   */\r
+       U32 nSecPerTrk;         /* Sectors per track  */\r
+       U16 BS1Cyl;                     /* Cyl of 1st boot sector on disk */\r
+       U8  BS1Head;            /* Head of 1st boot sector on disk */\r
+       U8  BS1Sect;            /* Sector of 1st boot sector on disk */\r
+       }\r
+\r
+static struct phydrv  PDrvs[nPDrvs];\r
+\r
+/* This array of structures keeps track of logical drive data (A-J). */\r
+\r
+#define nLDrvs 10\r
+\r
+static struct ldrvtype {\r
+       U32 LBA0;                       /* lba for Start of LDrive (bootSect) */\r
+       U32 LBAData;            /* lba for Start of Data Area */\r
+       U32 LBAMax;                     /* Max lba for logical drive */\r
+       U32 LBARoot;            /* lba of the Root directory */\r
+       U32 LBAFAT;                     /* lba of first FAT */\r
+       U16 nHeads;             /* Setup after boot sector is read */\r
+       U16 nSecPerTrk;         /* Setup after boot sector is read */\r
+       U16 nRootDirEnt;        /* Number of Root directory entries */\r
+       U16 sFAT;                       /* nSectors in a FAT */\r
+       U8  DevNum;                     /* Device Number for this ldrv FF = NONE */\r
+       U8  SecPerClstr;        /* For each logical drive */\r
+       U8  nFATS;                      /* number of FATs */\r
+       U8  fFAT16;                     /* True for FAT16 else FAT12 */\r
+       };\r
+\r
+static struct ldrvtype  Ldrv[nLDrvs];\r
+\r
+/* This is the Hard Disk Device Status record.\r
+   It is peculiar to the HD Drvr */\r
+\r
+struct hddevtype{\r
+  U32 erc;\r
+  U32 blocks_done;\r
+  U32 BlocksMax;\r
+  U8  fNewMedia;\r
+  U8  type_now;                /* current fdisk_table for drive selected */\r
+  U8  resvd0[2];       /* padding for DWord align  */\r
+  U32 nCyl;                    /* total physical cylinders (we really don't care) */\r
+  U32 nHead;           /* total heads on device    */\r
+  U32 nSectors;                /* Sectors per track        */\r
+  U32 nBPS;                    /* Number of bytes per sect.  32 bytes out to here.*/\r
+  U32 LastRecalErc0;\r
+  U32 LastSeekErc0;\r
+  U8  LastStatByte0;\r
+  U8  LastErcByte0;\r
+  U8  fIntOnReset;     /* Interrupt was received on HDC_RESET */\r
+  U8  filler0;\r
+  U32 LastRecalErc1;\r
+  U32 LastSeekErc1;\r
+  U8  LastStatByte1;\r
+  U8  LastErcByte1;\r
+  U8  ResetStatByte;   /* Status Byte immediately after RESET */\r
+  U8  filler1;\r
+  U32 resvd1[2];       /* out to 64 bytes */\r
+  };\r
+\r
+static struct hddevtype   HDDevStat;\r
+\r
+/* This is the Floppy Device Status record.\r
+   It is peculiar to the FD Drvr */\r
+\r
+struct fdstattype{\r
+  U32 erc;                     /* Last Error from device */\r
+  U32 blocks_done;\r
+  U32 BlocksMax;\r
+  U8 fNewMedia;\r
+  U8 type_now;         /* current fdisk_table for drive selected */\r
+  U8 resvd1[2];                /* padding for DWord align  */\r
+  U32 nCyl;                    /* total physical cylinders */\r
+  U32 nHead;           /* total heads on device    */\r
+  U32 nSectors;                /* Sectors per track        */\r
+  U32 nBPS;                    /* Number of bytes per sect */\r
+  U8 params[16];       /* begin device specific fields */\r
+  U8 STATUS[8];                /* status returned from FDC (for user status) */\r
+  U32 resvd3;\r
+  U32 resvd4;          /* 64 bytes total */\r
+  };\r
+\r
+static struct fdstattype  FDDevStat;\r
+\r
+static long FSysStack[512];    /* 2048 byte stack for Fsys task */\r
+\r
+static long FSysExch;\r
+\r
+struct reqtype {                       /* 64 byte request block structure */\r
+       long ServiceExch;\r
+       long RespExch;\r
+       long RqOwnerJob;\r
+       long ServiceRoute;\r
+       char *pRqHndlRet;\r
+       long dData0;\r
+       long dData1;\r
+       long dData2;\r
+       int  ServiceCode;\r
+       char npSend;\r
+       char npRecv;\r
+       char *pData1;\r
+       long cbData1;\r
+       char *pData2;\r
+       long cbData2;\r
+       long RQBRsvd1;\r
+       long RQBRsvd2;\r
+       long RQBRsvd3;\r
+       };\r
+\r
+static struct reqtype *pRQB;\r
+\r
+static char *fsysname = "FILESYSM";\r
+\r
+static unsigned long keycode;                  /* for testing */\r
+\r
+/*========================== BEGIN CODE ============================*/\r
+\r
+/************************************************\r
+ Called from read_PE, this gets the starting\r
+ cylinder, head and sector for the first boot\r
+ sector on a physical drive and stores it in the\r
+ phydrv array.  d is the drive, i is the index\r
+ into the partition table we read in.\r
+*************************************************/\r
+\r
+static void GetBSInfo(U32 d, U32 i)\r
+{\r
+ PDrvs[d].BS1Head = partab[i].HeadStart;\r
+ PDrvs[d].BS1Sect = partab[i].SecStart;\r
+ PDrvs[d].BS1Cyl  = partab[i].CylStart;\r
+\r
+ if (!i) \r
+ {             /* primary partition info - use it for PDrv info */\r
+        PDrvs[d].nHeads = partab[i].HeadEnd;\r
+        PDrvs[d].nSecPerTrk = partab[i].nFirstSector & 0xff;\r
+ }\r
+}\r
+\r
+/** InitFloppy *********************************\r
+ This gets status from the floppy drive (device ld)\r
+ and sets the physical & logical drive parameters\r
+ for the type. It is called when the file system\r
+ is first initialized and when there has been\r
+ an error on the floppy.\r
+*************************************************/\r
+\r
+static U32 StatFloppy(U8 ld)\r
+{\r
+U32 erc, i;\r
+\r
+/* Set gets status for the floppy type from the FDD and\r
+   sets logical paramters for Ldrvs.\r
+*/\r
+\r
+Ldrv[0].DevNum= 10;            /* Device Numbers for floppies */\r
+Ldrv[1].DevNum= 11;\r
+\r
+erc = DeviceStat(ld+10, &FDDevStat, 64, &i);\r
+if (!erc) \r
+{\r
+       PDrvs[ld].nHeads = FDDevStat.nHead;\r
+       PDrvs[ld].nSecPerTrk = FDDevStat.nSectors;\r
+       Ldrv[ld].LBA0 = 0;              /* Floppy Boot Sector - always 0 */\r
+       Ldrv[ld].LBAMax= FDDevStat.BlocksMax-1; /* Max lba for logical drive 0 */\r
+\r
+       Ldrv[ld].nHeads = FDDevStat.nHead;\r
+       Ldrv[ld].nSecPerTrk = FDDevStat.nSectors;\r
+\r
+    erc = 0;\r
+}\r
+else\r
+   Ldrv[ld].DevNum = 0xff;\r
+\r
+ return erc;\r
+}\r
+\r
+/************************************************\r
+ Reads the partition table entries from hard\r
+ drives and sets up some of the the logical\r
+ drive array variables for hard Disks.\r
+ It also saves first cylinder, head and sector\r
+ of the first partiton on each physical drive\r
+ so we can get more info for the LDrv arrays\r
+ from the boot sector of that partition.\r
+*************************************************/\r
+\r
+static U32 read_PE(void)\r
+{\r
+U32 erc, ercD12, ercD13, i, j;\r
+U8 fFound1, fFound2;\r
+\r
+fFound1 = 0;           /* Have we found first valid partition on drive */\r
+fFound2 = 0;\r
+\r
+/* Set defaults for 4 physical drives. This info will be set\r
+   correctly when the partition table and boot sectors are read.\r
+*/\r
+\r
+for (i=2; i< nLDrvs; i++) \r
+{      /* default to no logical hard drives */\r
+   Ldrv[i].DevNum = 0xff;\r
+}\r
+\r
+i = 2;         /* first Logical Number for hard drives "C" */\r
+\r
+for (j=2; j<4; j++) \r
+{      /* Array index Numbers for 2 physical hard Disks */\r
+\r
+  erc = DeviceOp(j+10, 1, 0, 1, abRawSector); /* add 10 for Disk device nums */\r
+  if (j==2) ercD12 = erc;\r
+  else ercD13 = erc;\r
+\r
+  if (!erc) \r
+  {\r
+    CopyData(&abRawSector[0x01fe], &partsig, 2);\r
+\r
+       /* It MUST have a partition table or we can't use it! */\r
+\r
+       if (partsig != 0xAA55) return ErcNoParTable;\r
+\r
+    CopyData(&abRawSector[0x01be], &partab[0].fBootable, 64);\r
+\r
+/*\r
+        Dump(&partab[0].fBootable, 64);\r
+        ReadKbd(&keycode, 1);\r
+*/\r
+\r
+    if (partab[0].nSectorsTotal > 0) \r
+    {\r
+     Ldrv[i].LBA0 =partab[0].nFirstSector;     /* lba for Start of LDrv (bootSect) */\r
+     Ldrv[i].LBAMax =partab[0].nSectorsTotal;  /* Max lba for logical drive */\r
+        if (partab[0].FATType > 3)\r
+        Ldrv[i].fFAT16 = 1;\r
+     Ldrv[i].DevNum = j+10;\r
+     if ((j==2) && (!fFound1)) \r
+     { GetBSInfo(2, 0); fFound1=1; }\r
+       if ((j==3) && (!fFound2))\r
+     { GetBSInfo(3, 0); fFound2=1; }\r
+       i++;                                    /* if valid partition go to next LDrv */\r
+     }\r
+\r
+    if (partab[1].nSectorsTotal > 0) \r
+    {\r
+     Ldrv[i].LBA0   = partab[1].nFirstSector;\r
+     Ldrv[i].LBAMax = partab[1].nSectorsTotal;\r
+        if (partab[1].FATType > 3)\r
+        Ldrv[i].fFAT16 = 1;\r
+     Ldrv[i].DevNum = j+10;\r
+     if ((j==2) && (!fFound1)) { GetBSInfo(2, 1); fFound1=1; }\r
+     if ((j==3) && (!fFound2)) { GetBSInfo(3, 1); fFound2=1; }\r
+     i++;                                      /* if we had a valid partition go to next */\r
+    }\r
+\r
+    if (partab[2].nSectorsTotal > 0) \r
+    {\r
+     Ldrv[i].LBA0   = partab[2].nFirstSector;\r
+     Ldrv[i].LBAMax = partab[2].nSectorsTotal;\r
+        if (partab[2].FATType > 3)\r
+        Ldrv[i].fFAT16 = 1;\r
+     Ldrv[i].DevNum = j+10;\r
+     if ((j==2) && (!fFound1)) { GetBSInfo(2, 2); fFound1=1; }\r
+     if ((j==3) && (!fFound2)) { GetBSInfo(3, 2); fFound2=1; }\r
+     i++;                                      /* if we had a valid partition go to next */\r
+    }\r
+\r
+    if (partab[3].nSectorsTotal > 0) \r
+    {\r
+     Ldrv[i].LBA0   = partab[3].nFirstSector;\r
+     Ldrv[i].LBAMax = partab[3].nSectorsTotal;\r
+        if (partab[3].FATType > 3)\r
+        Ldrv[i].fFAT16 = 1;\r
+     Ldrv[i].DevNum = j+10;\r
+     if ((j==2) && (!fFound1)) \r
+     { \r
+       GetBSInfo(2, 3); \r
+       fFound1=1; \r
+     }\r
+     if ((j==3) && (!fFound2)) \r
+     { \r
+       GetBSInfo(3, 3); \r
+       fFound2=1; \r
+     }\r
+     i++;                                      /* if we had a valid partition go to next */\r
+     }\r
+    }\r
+  }\r
+\r
+ if (ercD12) return ercD12;            /* there may be no Device 13 */\r
+ else return 0;\r
+}\r
+\r
+/********************************************************************\r
+  Reads in the first boot sector from each physical drive to get\r
+  drive geometry info not available in partition table.  This includes\r
+  number of heads and sectors per track.  Then we call DeviceInit\r
+  for each physical device to set its internal drive geometry.\r
+  This must be done before we even try to read the other boot sectors\r
+  if the disk has mulitple partitions (otherwise it fails).\r
+*********************************************************************/\r
+\r
+static U32 SetDriveGeometry(U32 d)             /* d is the device number (12 or 13) */\r
+{\r
+U32 erc, i;\r
+\r
+  if (d==12) \r
+  {\r
+       erc =  DeviceStat(12, &HDDevStat, 64, &i);\r
+       if (!erc) \r
+       {\r
+         HDDevStat.nHead = PDrvs[2].nHeads;\r
+         HDDevStat.nSectors = PDrvs[2].nSecPerTrk;\r
+      erc = DeviceInit(12, &HDDevStat, 64); /* Set up drive geometry */\r
+    }\r
+  }\r
+\r
+  if (d==13) \r
+  {\r
+       erc =  DeviceStat(13, &HDDevStat, 64, &i);\r
+       if (!erc) \r
+       {\r
+         HDDevStat.nHead = PDrvs[3].nHeads;\r
+         HDDevStat.nSectors = PDrvs[3].nSecPerTrk;\r
+      erc = DeviceInit(13, &HDDevStat, 64); /* Set up drive geometry */\r
+    }\r
+  }\r
+\r
+return erc;\r
+}\r
+\r
+/********************************************************************\r
+  Read boot sector from logical drive (i) and sets up logical and\r
+  physical drive array variables for the FAT file system found on\r
+  the logical drive (described in the boot sector).\r
+*********************************************************************/\r
+\r
+static U32 read_BS(U32 i)\r
+{\r
+U32 erc, j;\r
+\r
+if (Ldrv[i].DevNum != 0xff) \r
+{\r
+\r
+    j = Ldrv[i].DevNum;                        /* j is MMURTL Device number */\r
+\r
+       erc = DeviceOp(j, 1, Ldrv[i].LBA0, 1, abRawSector);\r
+\r
+       if ((erc==ErcNewMedia) && (i<2)) \r
+       {\r
+               erc = DeviceOp(j, 1, Ldrv[i].LBA0, 1, abRawSector);\r
+       }\r
+\r
+       CopyData(abRawSector, &fsb.Jmp, 62);\r
+\r
+    if (erc==0) \r
+    {\r
+       Ldrv[i].LBARoot     = fsb.ResSectors + Ldrv[i].LBA0 +\r
+                            (fsb.FATs * fsb.SecPerFAT);\r
+       Ldrv[i].nRootDirEnt = fsb.RootDirEnts;  /* n Root dir entries */\r
+       Ldrv[i].SecPerClstr = fsb.SecPerClstr;\r
+       Ldrv[i].nHeads      = fsb.Heads;\r
+       Ldrv[i].nSecPerTrk  = fsb.SecPerTrack;\r
+       Ldrv[i].sFAT        = fsb.SecPerFAT;            /* nSectors in a FAT */\r
+       Ldrv[i].nFATS       = fsb.FATs;                 /* number of FATs */\r
+       Ldrv[i].LBAFAT      = Ldrv[i].LBA0 + fsb.ResSectors;\r
+       Ldrv[i].LBAData     = Ldrv[i].LBARoot + (fsb.RootDirEnts / 16);\r
+          if (fsb.FileSysType[4] == '2')\r
+         Ldrv[i].fFAT16 = 0;\r
+\r
+    } /* if erc */\r
+} /* if valid logical device */\r
+return 0;\r
+}\r
+\r
+/*******************************************************\r
+  This gets the CMOS date & time and converts it into the\r
+  format for the DOS FAT file system. This is two words\r
+  with bits representing Year/Mo/day & Hr/Min/SecDIV2.\r
+********************************************************/\r
+static void GetFATTime(U16 *pTimeRet, U16 *pDateRet)\r
+{\r
+U32 date, time;\r
+U16 DDate, DTime, w;\r
+\r
+       GetCMOSDate(&date);\r
+       GetCMOSTime(&time);\r
+       /* Do the date */\r
+       DDate = (((date >> 12) & 0x0f) * 10) + ((date >> 8) & 0x0f); /* day */\r
+       w = (((date >> 20) & 0x0f) * 10) + ((date>>16) & 0x0f) + 2;      /* month */\r
+       DDate |= (w << 4);\r
+       w = (((date >> 28) & 0x0f) * 10) + ((date >> 24)  & 0x0f);       /* year */\r
+       DDate |= (w + 1900 - 1980) << 9;\r
+       /* Do the time */\r
+       DTime = (((((time >> 4) & 0x0f) * 10) + (time & 0x0f))/2);      /* secs/2 */\r
+       w = (((time >> 12) & 0x0f) * 10) + ((time >> 8) & 0x0f);\r
+       DTime |= (w << 5);                                                                                      /* mins */\r
+       w = (((time >> 20) & 0x0f) * 10) + ((time >> 16) & 0x0f);       /* hours */\r
+       DTime |= (w << 11);\r
+       *pTimeRet = DTime;\r
+       *pDateRet = DDate;\r
+}\r
+\r
+\r
+/*******************************************************\r
+  This updates a directory entry by reading in the\r
+  sector it came from and placing the modifed entry\r
+  into it then writing it back to disk.  The date is\r
+  also updated at this time.\r
+********************************************************/\r
+static U32 UpdateDirEnt(U32 iFCB)\r
+{\r
+U32 erc, i, j;\r
+U8 Drive;\r
+       Drive = paFCB[iFCB]->Ldrv;              /* What logical drive are we on? */\r
+       i = paFCB[iFCB]->LBADirSect;    /* Sector on disk */\r
+       j = paFCB[iFCB]->oSectDirEnt;   /* offset in sector */\r
+\r
+       /* update time in dir entry */\r
+       GetFATTime(&paFCB[iFCB].Time, &paFCB[iFCB].Date);\r
+\r
+       /* Read sector into a buffer */\r
+       erc = DeviceOp(Ldrv[Drive].DevNum, 1, i, 1, abDirSectBuf);\r
+\r
+       if (!erc) \r
+       {\r
+           CopyData(&paFCB[iFCB], &abDirSectBuf[j], 32);\r
+               erc = DeviceOp(Ldrv[Drive].DevNum, 2, i, 1, abDirSectBuf);\r
+       }\r
+  return erc;\r
+}\r
+\r
+\r
+/*******************************************************\r
+  Checks the validity of the a file handle and also\r
+  returns the index to the FCB if the handle is OK.\r
+  The function return OK (0) if handle is good, else\r
+  a proper error code is returned.\r
+********************************************************/\r
+static U32 ValidateHandle(U32 dHandle, U32 *iFCBRet)\r
+{\r
+  /* get some checks out of the way first */\r
+\r
+  if (dHandle < 4) return ErcBadFileHandle;\r
+  if (dHandle >= nFUBs) return ErcBadFileHandle;\r
+  if (!paFUB[dHandle].Job) return ErcBadFileHandle;\r
+\r
+  /* Looks like a valid handle */\r
+  *iFCBRet = paFUB[dHandle]->iFCB;\r
+  return 0;\r
+}\r
+\r
+/*********************************************\r
+  Returns absolute disk address for the\r
+  cluster number you specify. This gives us\r
+  the LBA of the first sector of data that\r
+  the cluster number represents.\r
+  The sector number is returned from the fucntion.\r
+  Uses: Ldrv[CrntDrv].LBAData\r
+               Ldrv[CrntDrv].SecPerClstr\r
+**********************************************/\r
+\r
+static U32 ClsToLBA(U16 Clstr, U8 Drive)\r
+{\r
+U32 LBA;\r
+\r
+ Clstr-=2;             /* Minus 2 cause 0 and 1 are reserved clusters */\r
+ LBA = Ldrv[Drive].SecPerClstr * Clstr;\r
+ LBA += Ldrv[Drive].LBAData;\r
+ return LBA;\r
+}\r
+\r
+\r
+/*******************************************************\r
+  This writes out the specified FAT sector back into\r
+  the FAT. It also checks to see if there is more\r
+  than one copy of the fat and updates the second copy\r
+  if it exists.\r
+********************************************************/\r
+static U32 UpdateFAT(U32 iFAT)\r
+{\r
+U32 erc, i, k;\r
+U8 Drive;\r
+\r
+  erc = 0;\r
+  if (Fat[iFAT].fModLock & FATMOD)\r
+  {    /* Modified?? */\r
+\r
+       Drive = Fat[iFAT].Drive;                /* What logical drive are we on? */\r
+    i = Fat[iFAT].LBASect;                             /* Where to write it back */\r
+\r
+       if (!iFAT)\r
+       {                       /* This is the floppy buffer [0] */\r
+               /* set up to write upto 3 sectors from the buffer */\r
+\r
+               if (i+2 < Ldrv[Drive].sFAT + Ldrv[Drive].LBAFAT)\r
+                       k = 3;\r
+               else if (i+1 < Ldrv[Drive].sFAT + Ldrv[Drive].LBAFAT)\r
+                       k = 2;\r
+               else\r
+                       k = 1;\r
+       }\r
+       else\r
+               k=1;\r
+\r
+       erc = DeviceOp(Ldrv[Drive].DevNum, 2, i, k, Fat[iFAT].pBuf);\r
+       if (!erc)\r
+       {\r
+               Fat[iFAT].fModLock &= ~FATMOD;  /* Not modified anymore */\r
+               if (Ldrv[Drive].nFATS > 1)  \r
+               {       /* 2 FATS! */\r
+                       /* if we have two FATS we must update the second fat\r
+                       also. This will be located directly aftrer the first\r
+                       FAT (by exactly LDrv.sFat sectors).\r
+                       */\r
+                       i+= Ldrv[Drive].sFAT;\r
+                       erc = DeviceOp(Ldrv[Drive].DevNum, 2, i, k, Fat[iFAT].pBuf);\r
+               }\r
+       }\r
+  }\r
+  return erc;\r
+}\r
+\r
+/*******************************************************\r
+ Reads in the FAT sector that contains the Cluster we\r
+ specified into a FAT buffer if it isn't already in\r
+ one.  The index to the FAT buffer is returned.\r
+ Returns Error if not in FAT.\r
+ Uses: Ldrv[LDrive].LBAFAT\r
+          Ldrv[LDrive].fFAT16\r
+          Ldrv[LDrive].DevNum\r
+ Each sector of the FAT contains 256 cluster entries\r
+ for FAT16 types. To find it, we  Divide the cluster\r
+ number by the count of entries (256), and add this\r
+ to the beginning sector of the FAT.  It is SOOO\r
+ important (for speed) to have the FAT sectors in\r
+ memory, that we allocate the FAT buffers on a Least\r
+ Recently Used (LRU) basis for hard disk drives.\r
+\r
+ It's more complicated for a FAT12 types (floppies)\r
+ because cluster entries span fat sectors (they have\r
+ an odd number of nibbles). For this reason, we have\r
+ one 3 sector fat buffer for fat12 devices (floppies).\r
+ We fill it with up to 3 sectors. This is because the\r
+ last entry may span the sectors and we must be able\r
+ to read it.  There are 1024 cluster entries in\r
+ a FAT12 3 sector buffer.\r
+*******************************************************/\r
+\r
+static U32 FindFatSect(U8 Drive, U16 Clstr, U32 *piFatRecRet, U8 fLock)\r
+{\r
+U32 i, j, k;\r
+U32 first, oSector, erc, LRU, iLRU, iFound, Tick;\r
+U16 MaxClstr;\r
+\r
+  if (Ldrv[Drive].fFAT16)\r
+       MaxClstr = 0xfff8;\r
+  else\r
+       MaxClstr = 0xff8;       /* FAT12 */\r
+\r
+ if (Clstr >= MaxClstr)\r
+       return(ErcEOF);\r
+\r
+ if (Clstr < 2)\r
+ {\r
+       return (ErcBadFATClstr);\r
+ }\r
+\r
+ GetTimerTick(&Tick);\r
+\r
+ erc = 0;              /* default to no error */\r
+\r
+ /* Set oSector to offset of sector in FAT\r
+    There are 256 cluster entries in 1 sector of a FAT16,\r
+    and 1024 in a FAT12 (3 sectors)\r
+ */\r
+\r
+ if (Ldrv[Drive].fFAT16)\r
+ {\r
+       oSector = Clstr/256;\r
+       first = Clstr-(Clstr%256);\r
+\r
+       /* Set i to LBA of FAT sector we need by adding\r
+          offset to beginning of FAT\r
+       */\r
+\r
+       i = oSector + Ldrv[Drive].LBAFAT;\r
+\r
+       /* If FAT sector is out of range there's a BAD problem... */\r
+\r
+       if (i >= Ldrv[Drive].sFAT + Ldrv[Drive].LBAFAT)\r
+       {\r
+               return (ErcBadFATClstr);\r
+       }\r
+       else\r
+       {   /* Else we get it for them */\r
+\r
+               /* Loop through the Fat bufs and see if its in one already. */\r
+               /* Save the index of the LRU in case it's not there. */\r
+               /* Set iFound to index of FatBuf (if found). */\r
+               /* Otherwise, Set up iLRU to indicate what the oldest buffer is  */\r
+\r
+               iFound = 0xffffffff;\r
+               LRU = 0xffffffff;       /* saves tick of oldest one so far */\r
+               iLRU = 1;                       /* default */\r
+               for (j=1; j<nFATBufs; j++)\r
+               {\r
+                       if (Fat[j].LastUsed > 0)\r
+                       {               /* Valid ? (ever been used) */\r
+                               if ((first == Fat[j].iClstrStart) &&\r
+                                       (Drive == Fat[j].Drive))\r
+                                       {\r
+                                               iFound = j;\r
+                                               if (fLock)\r
+                                   Fat[j].fModLock |= FATLOCK;\r
+                                               break;          /* Already IN! */\r
+                                       }\r
+                       }\r
+                       if (Fat[j].LastUsed < LRU)\r
+                       {\r
+                               LRU = Fat[j].LastUsed;\r
+                               iLRU = j;\r
+                       }\r
+               }\r
+\r
+               if (iFound != 0xffffffff)\r
+               {                                                               /* Its already in memory */\r
+               Fat[j].LastUsed = Tick;         /* update LRU */\r
+               }\r
+               else\r
+               {                                               /* else put into oldest buffer */\r
+                       j = iLRU;\r
+\r
+                       /* Check to see if Fat[iLRU] is valid and has been\r
+                          modified. If it is, write it out before we read\r
+                          the next one into this buffer. This done by\r
+                          calling UpdateFAT(iFatRec).\r
+                       */\r
+               if (Fat[j].fModLock & FATMOD)\r
+                       erc = UpdateFAT(j);\r
+\r
+                       if (!erc)\r
+                       {\r
+                               erc = DeviceOp(Ldrv[Drive].DevNum, 1, i, 1, Fat[j].pBuf);\r
+                               Fat[j].Drive = Drive;                   /* Update Drive */\r
+                   Fat[j].LastUsed = Tick;                     /* update LRU */\r
+                       Fat[j].iClstrStart = first;             /* update first cluster num */\r
+                       Fat[j].LBASect = i;                             /* LBA this FAt sect came from */\r
+                       }\r
+               }\r
+       }\r
+ }\r
+\r
+       /* This is for FAT12s */\r
+\r
+ else\r
+ {\r
+       oSector = (Clstr/1024) * 3;      /* X3 cause we read 3 at a time */\r
+       first = Clstr-(Clstr%1024);\r
+\r
+       /* Set i to LBA of FAT sector we need by adding offset (oSector)\r
+          to beginning of FAT */\r
+\r
+       i = oSector + Ldrv[Drive].LBAFAT;\r
+       j = 0;\r
+\r
+       /* If FAT sector is out of range there's a BAD problem... */\r
+\r
+       if (i >= Ldrv[Drive].sFAT)\r
+               return (ErcBadFATClstr);\r
+       else\r
+       {   /* Else we get it for them */\r
+\r
+               /* Check the single floppy fat buf and see if its already there. */\r
+               /* Set iFound to index of FatBuf (if found). */\r
+\r
+               iFound = 0xffffffff;\r
+\r
+               if (Fat[0].LastUsed > 0)\r
+               {               /* Valid ? (nonzero means it's been used) */\r
+                       if ((first == Fat[0].iClstrStart) &&\r
+                               (Drive == Fat[0].Drive))\r
+                       {\r
+                               iFound = 0;\r
+                               if (fLock)\r
+                                       Fat[0].fModLock |= FATLOCK;\r
+                       }\r
+               }\r
+\r
+               if (iFound == 0xffffffff)\r
+               {\r
+                       /* It's not the one we want or isn't there.\r
+                          Check to see if Fat[0] is valid and has been\r
+                          modified. If it is, write it out before we read\r
+                          the one we want into the buffer. This done by\r
+                          calling UpdateFAT(iFatRec).\r
+                       */\r
+                       if (Fat[0].fModLock & FATMOD)\r
+                               erc = UpdateFAT(0);\r
+\r
+                       /* set up to read upto 3 sectors into buffer */\r
+\r
+                       if (i+2 < Ldrv[Drive].sFAT + Ldrv[Drive].LBAFAT)\r
+                               k = 3;\r
+                       else if (i+1 < Ldrv[Drive].sFAT + Ldrv[Drive].LBAFAT)\r
+                               k = 2;\r
+                       else\r
+                               k = 1;\r
+\r
+                       if (!erc)\r
+                       {\r
+                               erc = DeviceOp(Ldrv[Drive].DevNum, 1, i, k, Fat[0].pBuf);\r
+\r
+                               Fat[0].Drive = Drive;                   /* Update Drive */\r
+                       Fat[0].LastUsed = Tick;                 /* update LRU */\r
+                       Fat[0].iClstrStart = first;             /* update first cluster num */\r
+                       Fat[0].LBASect = i;             /* LBA this FAT sect came from */\r
+                       }\r
+               }\r
+       }\r
+ }\r
+\r
+\r
+ *piFatRecRet = j;  /* Buffer that holds the sector(s) */\r
+\r
+ return (erc);         /* Disk error Bad news */\r
+}\r
+\r
+/*********************************************\r
+  Returns the value found for this cluster\r
+  entry in a fat sector buffer.  Values can be:\r
+                              FAT16     FAT12\r
+       Next entry in the chain (0002-FFF0 (002-FF0)\r
+       Last entry in chain     (FFF8     )(FF8    )\r
+       Available cluster       (0        )(0      )\r
+       Bad Cluster             (FFF7     )(FF7    )\r
+       (other vlaues are reserved).\r
+**********************************************/\r
+\r
+static U32 GetClstrValue(U16 Clstr, U8 Drive, U8 fLock,\r
+                         U16 *pValRet, U32 *iFatBufRet)\r
+{\r
+U32 erc, oClstr, iFat;\r
+U16 ClstrVal, *pClstr;\r
+\r
+       erc = FindFatSect(Drive, Clstr, &iFat, fLock);\r
+\r
+       if (erc)\r
+       {\r
+               *pValRet= 0;\r
+               return(erc);\r
+       }\r
+\r
+       pClstr = Fat[iFat].pBuf;\r
+       oClstr = Clstr - Fat[iFat].iClstrStart;    /* offset into FatBuf */\r
+\r
+       if (Ldrv[Drive].fFAT16)\r
+       {       /* if drive is FAT16 type */\r
+               pClstr += oClstr * 2;                                   /* WORDS in */\r
+               ClstrVal = *pClstr;\r
+       }\r
+               /* FAT12 entries are 1.5 bytes long (what a pain).\r
+                  This means we get the offset and see whether it\r
+                  is an odd or even byte, then take the proper nibble\r
+                  by ANDing or shifting.\r
+               */\r
+       else\r
+       {                                               /* a FAT12... */\r
+               pClstr += oClstr + (oClstr/2);                  /* 1.5 bytes in */\r
+               ClstrVal = *pClstr;                                     /* We have 16 bits */\r
+               if (Clstr & 1)                                  /* Odd, must shift */\r
+                       ClstrVal >>= 4;\r
+               ClstrVal &= 0xfff;\r
+       }\r
+       *pValRet= ClstrVal;\r
+    *iFatBufRet = iFat;\r
+\r
+       return(erc);\r
+}\r
+\r
+\r
+/*************************************************\r
+  Sets the value in Clstr to the value in\r
+  NextClstr which will be one of the following\r
+  values:                     FAT16     FAT12\r
+       Next entry in the chain (0002-FFEF (002-FEF)\r
+       Last entry in chain     (FFFF     )(FFF    )\r
+       Available cluster       (0        )(0      )\r
+       Bad Cluster             (FFF7     )(FF7    )\r
+       (other vlaues are reserved).\r
+  This marks the associated fat buffer as modified.\r
+  This is the ONLY call that modifies a FAT buffer!\r
+**************************************************/\r
+\r
+static U32 SetClstrValue(U16 Clstr, U16 NewClstrVal, U8 Drive, U32 *iFatBufRet)\r
+{\r
+U32 erc, oClstr, iFat;\r
+U16 ClstrVal, *pClstr, ClstrSave;\r
+\r
+       erc = FindFatSect(Drive, Clstr, &iFat, 0);\r
+       if (erc)\r
+       {\r
+               *iFatBufRet = 0;\r
+               return(erc);\r
+       }\r
+\r
+       pClstr = Fat[iFat].pBuf;\r
+       oClstr = Clstr - Fat[iFat].iClstrStart;    /* offset into FatBuf*/\r
+       if (Ldrv[Drive].fFAT16) \r
+       {       /* if drive is FAT16 type */\r
+               pClstr += oClstr * 2;                                   /* WORDS in */\r
+               *pClstr = NewClstrVal;\r
+       }\r
+               /* FAT12 entries are 1.5 bytes long (remember??).\r
+                  SAVE THE CORRECT NIBBLE OF THE ADJACENT CLUSTER!!\r
+               */\r
+       else\r
+       {                                               /* a FAT12... */\r
+               pClstr += oClstr + (oClstr/2);                  /* 1.5 bytes in */\r
+               ClstrSave = *pClstr;                                            /* We have 16 bits */\r
+               if (Clstr & 1) \r
+               {                                       /* Odd, must shift */\r
+                       NewClstrVal <<= 4;\r
+                       NewClstrVal &= 0xfff0;\r
+                       ClstrVal = (ClstrSave & 0x0F) | NewClstrVal;\r
+               }\r
+               else \r
+               {\r
+                       NewClstrVal &= 0x0fff;\r
+                       ClstrVal = (ClstrSave & 0xf000) | NewClstrVal;\r
+               }\r
+               *pClstr = ClstrVal;\r
+       }\r
+    Fat[iFat].fModLock |= FATMOD;\r
+    *iFatBufRet = iFat;\r
+       return(erc);\r
+}\r
+\r
+\r
+/*************************************************\r
+ Read the FAT and get the cluster number for the\r
+ next cluster in the chain for the Clstr specifed.\r
+ This returns 0 and an error if failed.\r
+ 0 is an illegal cluster number.\r
+ Remember, the cluster you are on is actually the\r
+ number of the next cluster in a linked list!\r
+**************************************************/\r
+\r
+static U32 NextFATClstr(U8 Drive, U16 Clstr, U16 *pNextClstrRet)\r
+{\r
+U32 erc, i;\r
+U16 NextClstr;\r
+\r
+       erc = GetClstrValue(Clstr, Drive, 0, &NextClstr, &i);\r
+\r
+       if (erc)\r
+       {\r
+               *pNextClstrRet = 0;\r
+               return(erc);\r
+       }\r
+       *pNextClstrRet = NextClstr;\r
+       return(0);\r
+}\r
+\r
+/*************************************************\r
+ This allocates the next empty cluster on the disk\r
+ to the tail of the clstr that is passed in.\r
+ LastClstr is a valid cluster of a file or \r
+ directory (and MUST be the last one).  \r
+ We error out if it isn't!\r
+ This returns 0 and an error if it fails.\r
+ Remember, the cluster you are on is actually the\r
+ number of the next cluster in a linked list!\r
+ This looks through the current and succesive\r
+ FAT sectors (if needed) to add to the file.\r
+ A cluster is available to allocate if it is\r
+ 0.  This is strictly a first fit algorithm.\r
+**************************************************/\r
+\r
+static U32 ExtendClstrChain(U8 Drive, U16 LastClstr, U16 *pNextClstrRet)\r
+{\r
+U32 erc, i, j, k;\r
+U16 ClstrValue, MaxClstr, CrntClstr;\r
+U8 fFound;\r
+\r
+       if (Ldrv[Drive].fFAT16)\r
+               MaxClstr = 0xfff8;\r
+       else\r
+               MaxClstr = 0xff8;       /* FAT12 */\r
+\r
+       /* i is index to Fat with last sector of current chain */\r
+\r
+       erc = GetClstrValue(LastClstr, Drive, 1, &ClstrValue, &i);\r
+       if (erc) \r
+       {\r
+               *pNextClstrRet = 0;\r
+               return(erc);\r
+       }\r
+\r
+       if (ClstrValue < MaxClstr) \r
+       {               /* no need to extend it */\r
+       *pNextClstrRet = ClstrValue;\r
+        Fat[i].fModLock &= ~FATLOCK;           /* unlock it */\r
+               return(0);\r
+       }\r
+\r
+       /* OK... now we have the Fat sector and the offset in the Fat\r
+       buf of the last cluster allocated to this file.  Let's go\r
+       further into the buffer and try to get an empty one.\r
+       */\r
+\r
+       CrntClstr = LastClstr;\r
+       fFound = 0;\r
+       while (!fFound) \r
+       {\r
+               ++CrntClstr;            /* next cluster */\r
+               erc = GetClstrValue(CrntClstr, Drive, 0, &ClstrValue, &j);\r
+               if (erc) \r
+               {\r
+                       *pNextClstrRet = 0;\r
+               Fat[i].fModLock &= ~FATLOCK;    /* unlock previous lastclstr */\r
+                       return(erc);\r
+               }\r
+               if (!ClstrValue) \r
+               {\r
+                       fFound = 1;     /* found an empty one */\r
+               }\r
+       }\r
+\r
+       if (fFound) \r
+       {       /* CrntClstr is index to empty one */\r
+\r
+               /* Set the LastCluster to point to the new cluster found */\r
+\r
+               erc = SetClstrValue(LastClstr, CrntClstr, Drive, &k);\r
+               if (erc) \r
+               {\r
+                       *pNextClstrRet = 0;\r
+               Fat[i].fModLock &= ~FATLOCK;    /* unlock previous lastclstr */\r
+                       return(erc);\r
+               }\r
+        Fat[k].fModLock &= ~FATLOCK;           /* unlock it */\r
+\r
+               /* Set the newcluster to "end Cluster" chain value */\r
+\r
+               erc = SetClstrValue(CrntClstr, 0xFFFF, Drive, &j);\r
+       }\r
+       *pNextClstrRet = CrntClstr;\r
+       return(erc);\r
+}\r
+\r
+/*************************************************\r
+ This truncates the file chain to the cluster\r
+ specified (makes it the last cluster).\r
+ This means we walk the rest of the chain setting\r
+ all the entries to 0 (so they can be reallocated).\r
+ This returns an error if failed.\r
+**************************************************/\r
+\r
+static U32 TruncClstrChain(U8 Drive, U16 Clstr)\r
+{\r
+U32 erc, i;\r
+U16 MaxClstr, NextClstr, CrntClstr;\r
+\r
+       if (Ldrv[Drive].fFAT16)\r
+               MaxClstr = 0xfff8;\r
+       else\r
+               MaxClstr = 0xff8;       /* FAT12 */\r
+\r
+       /* i will be index to FatRec with last sector of current chain */\r
+\r
+       erc = GetClstrValue(Clstr, Drive, 0, &NextClstr, &i);\r
+       if (erc)\r
+               return(erc);\r
+\r
+       if (NextClstr >= MaxClstr)\r
+       {               /* no need to truncate it */\r
+               return(0);                                              /* It's already the end. */\r
+       }\r
+\r
+       /* OK... now we cut it off all the way down the chain.\r
+       We start by placing MaxClstr in the last sector and\r
+       then 0 in all entries to the end of the chain.\r
+       */\r
+\r
+       erc = GetClstrValue(Clstr, Drive, 0, &NextClstr, &i);\r
+       if (erc)\r
+               return(erc);\r
+       erc = SetClstrValue(Clstr, 0xFFFF, Drive, &i);  /* new end of chain */\r
+       if (erc)\r
+               return(erc);\r
+\r
+       while ((NextClstr) && (NextClstr < MaxClstr)) \r
+       {\r
+               CrntClstr = NextClstr;\r
+               erc = GetClstrValue(CrntClstr, Drive, 0, &NextClstr, &i);\r
+               if (erc)\r
+                       return(erc);\r
+               erc = SetClstrValue(CrntClstr, 0, Drive, &i);  /* Free it up */\r
+               if (erc)\r
+                       return(erc);\r
+       }\r
+\r
+       /* DONE! */\r
+       return(0);\r
+}\r
+\r
+/********************************************************\r
+  This finds the absolute cluster you want from the\r
+  LFA in a particular file. The file handle must already\r
+  be validated!  It also returns the relative LFA of\r
+  the beginning of this cluster.\r
+*********************************************************/\r
+\r
+static U32 GetAbsoluteClstr(U32 dHandle, U32 dLFA,\r
+                                                       U16 *pClstrRet, U32 *prLFARet)\r
+{\r
+U32 erc, iFCB, spc, bpc, rLFA;\r
+U16 rClstrWant, rClstrNow, Clstr, MaxClstr;\r
+U8 Drive;\r
+\r
+  iFCB = paFUB[dHandle]->iFCB;\r
+  Drive = paFCB[iFCB]->Ldrv;           /* What logical drive are we on? */\r
+  spc = Ldrv[Drive].SecPerClstr;       /* sectors per cluster */\r
+  bpc = spc * 512;                                     /* bytes per cluster */\r
+\r
+ if (Ldrv[Drive].fFAT16)\r
+       MaxClstr = 0xfff8;\r
+ else\r
+       MaxClstr = 0xff8;       /* FAT12 */\r
+\r
+/*\r
+  Calculate relative by dividing cluster size in bytes by dLFA.\r
+  If zero, we want the 1st cluster which is listed in the FCB.\r
+  If it is greater than zero, we have to "walk the FAT cluster\r
+  chain" until we reach the one we want, then read it in.\r
+\r
+  The FUB fields LFAClstr and Clstr store the file LFA of the last\r
+  cluster in this file that was read or written.  This means if the\r
+  LFA is higher than the last read or written, we don't waste the\r
+  time reading the whole chain. We start from where we are.\r
+\r
+  The major difference is we may not be reading the first sector\r
+  in the cluster. We figure this out from the dLFA as compared to\r
+  LFAClstr.\r
+\r
+*/\r
+\r
+  rClstrWant = dLFA / bpc;                                     /* Relative clstr they want */\r
+  rClstrNow = paFUB[dHandle]->LFAClstr / bpc;  /* Rel 'Clstr' in FUB */\r
+\r
+  if (rClstrWant < rClstrNow)\r
+  {                    /* Is it earlier in the file? */\r
+       Clstr = paFCB[iFCB]->StartClstr;                /* Yes, start at the beginning */\r
+       rClstrNow = 0;\r
+       rLFA = 0;\r
+  }\r
+  else\r
+  {\r
+       Clstr = paFUB[dHandle]->Clstr;                  /* No, start at current cluster */\r
+       rLFA = paFUB[dHandle]->LFAClstr;                /* LFA of this cluster */\r
+  }\r
+\r
+  /* We need to run the cluster chain if rClstrNow < ClstrWant */\r
+\r
+  while ((rClstrNow < rClstrWant) &&   /* haven't reach it yet */\r
+         (Clstr < MaxClstr) &&                 /* Not last cluster */\r
+         (Clstr))\r
+ {                                     /* A valid cluster */\r
+         erc = NextFATClstr(Drive, Clstr, &Clstr);\r
+         if (erc)\r
+               return(erc);\r
+         ++rClstrNow;\r
+         rLFA += bpc;\r
+  }\r
+\r
+  if (rClstrNow != rClstrWant)                 /* Cluster chain appears broken... */\r
+    return ErcBrokenFile;\r
+\r
+  *pClstrRet = Clstr;\r
+  *prLFARet = rLFA;\r
+  return(0);\r
+}\r
+\r
+\r
+/*******************************************************\r
+  SetFileSize sets the FileSize entry in the FCB for\r
+  the handle specified.  This means we will allocate\r
+  or deallocate clusters as necessary to satisfy this\r
+  request. The file MUST be open in MODE MODIFY.\r
+  This must be done before a file can be written\r
+  to beyond current FileSize (Block and Stream types).\r
+********************************************************/\r
+\r
+static U32 SetFileSizeM(U32 dHandle, U32 dSize)\r
+{\r
+U32 erc, i, iFCB, rLFA, lfaEOF;\r
+U32 CrntSize, nCrntClstrs, spc, bpc, nClstrsWant;\r
+U16 Clstr;\r
+U8 Drive;\r
+\r
+  erc = ValidateHandle(dHandle, &iFCB);\r
+  if (erc)\r
+       return erc;\r
+  if (!paFCB[iFCB]->Mode)\r
+       return ErcReadOnly;\r
+\r
+  Drive = paFCB[iFCB]->Ldrv;           /* What logical drive are we on? */\r
+  spc = Ldrv[Drive].SecPerClstr;       /* sectors per cluster */\r
+  bpc = spc * 512;                                     /* bytes per cluster */\r
+\r
+  /* Looks like it's valid to change the size */\r
+\r
+  CrntSize = paFCB[iFCB]->FileSize;\r
+  if (CrntSize)\r
+         lfaEOF = CrntSize - 1;\r
+  else\r
+         lfaEOF = 0;\r
+\r
+  if (CrntSize == dSize)               /* No need to do anything! */\r
+       return(0);\r
+\r
+  nCrntClstrs = CrntSize/bpc;  /* nClusters currently  */\r
+  if (CrntSize%bpc)\r
+       nCrntClstrs++;\r
+\r
+  if (!CrntSize)\r
+       nCrntClstrs = 1;                        /* ZERO length files have 1 Clstr! */\r
+\r
+  nClstrsWant = dSize/bpc;     /* nClusters they we need  */\r
+  if (dSize%bpc)\r
+       nClstrsWant++;\r
+\r
+  if (!dSize)\r
+       nClstrsWant = 1;                        /* ZERO length files have 1 Clstr! */\r
+\r
+\r
+  if (nClstrsWant == nCrntClstrs)\r
+       erc = 0;\r
+\r
+  else if (nClstrsWant > nCrntClstrs)\r
+  {    /* Need to extend allocation */\r
+\r
+       /* get the last cluster in the file */\r
+       erc = GetAbsoluteClstr(dHandle, lfaEOF, &Clstr, &rLFA);\r
+       i = nCrntClstrs;\r
+       while ((!erc) && (i < nClstrsWant))\r
+       {\r
+                       erc = ExtendClstrChain(Drive, Clstr, &Clstr);\r
+                       i++;\r
+       }\r
+  }\r
+  else if (nClstrsWant < nCrntClstrs)\r
+  { /* Need to truncate it */\r
+\r
+       /* Get to cluster where it should be truncated. Set lfaEOF\r
+       to NEW lfaEOF to find where last valid cluster should be. */\r
+\r
+       if (dSize)\r
+               lfaEOF = dSize - 1;\r
+       else\r
+         lfaEOF = 0;\r
+\r
+       erc = GetAbsoluteClstr(dHandle, lfaEOF, &Clstr, &rLFA);\r
+       if (!erc)\r
+               erc = TruncClstrChain(Drive, Clstr);\r
+\r
+       /* Now we must ensure that the cluster helper is NOT\r
+       beyond EOF!\r
+       */\r
+    if (paFUB->LFAClstr >= dSize)\r
+    {\r
+        paFUB->LFAClstr = 0;\r
+        paFUB[dHandle]->Clstr = paFCB[iFCB]->StartClstr;\r
+       }\r
+\r
+  }\r
+  if (!erc)\r
+  {\r
+       paFCB[iFCB]->FileSize = dSize;\r
+       paFCB[iFCB]->fMod = 1;\r
+  }\r
+\r
+  return erc;\r
+}\r
+\r
+\r
+\r
+/*******************************************************************\r
+ This searches a directory beginning at Clstr for pName and returns\r
+ a pointer to the 32 byte dir entry which is in a temporary buffer.\r
+ If not found, returns NIL. When searching directories, if the\r
+ filename begins with a NULL the search need go no futher!\r
+********************************************************************/\r
+\r
+static U32 GetDirEnt(U8  *pName,\r
+              U8  Drive,\r
+              U16 Clstr,\r
+              U32 *pLBARet,\r
+              U32 *poEntRet,\r
+              U8  **pEntRet)\r
+{\r
+unsigned long sector, i, j, k, erc;\r
+U8 fFound, fEnd, *pEnt, *pStart;\r
+U16 MaxClstr;\r
+\r
+ j = Ldrv[Drive].SecPerClstr;          /* How many sectors per cluster */\r
+ sector = ClsToLBA(Clstr, Drive);      /* absolute sector of first dir sector */\r
+ if (Ldrv[Drive].fFAT16)\r
+       MaxClstr = 0xfff8;\r
+ else\r
+       MaxClstr = 0xff8;       /* FAT12 */\r
+ i = 0;\r
+ fEnd=0;\r
+ fFound=0;\r
+\r
+  fFound= 0;\r
+  while ((!fFound) && (!fEnd)) \r
+  {            /* while there are valid entries */\r
+       if (i==j) \r
+       {               /* reached last dir sector of this cluster */\r
+               erc = NextFATClstr(Drive, Clstr, &Clstr);\r
+               if (!erc) \r
+               {\r
+                       if (Clstr >= MaxClstr)                          /* last sector */\r
+                               return(ErcNoSuchFile);                  /* not found */\r
+                       sector = ClsToLBA(Clstr, Drive);        /* LBA of next dir sector */\r
+                       i=0;\r
+               }\r
+               else \r
+               {\r
+                       *pEntRet = 0;\r
+                       return(erc);\r
+               }\r
+       }\r
+\r
+       erc = DeviceOp(Ldrv[Drive].DevNum, 1, sector++, 1, abDirSectBuf);\r
+       if (erc)\r
+               return(erc);\r
+\r
+       ++i;            /* next sector in cluster */\r
+\r
+    pEnt = &abDirSectBuf[0];\r
+    pStart = pEnt;\r
+\r
+       for (k=0; k<16; k++) \r
+       {               /* 16 entries per sector */\r
+               if (*pEnt==0) \r
+               {                       /* 0 in a DirEnt stops search */\r
+                       fEnd=1;\r
+                       break;\r
+               }\r
+\r
+               if (CompareNCS(pEnt, pName, 11) == -1)\r
+               {\r
+                       fFound=1;\r
+            *pLBARet = sector-1;               /* tell em what LBA of DirEnt */\r
+            *poEntRet = pEnt-pStart;   /* Tell em offset in LBA */\r
+                       break;\r
+               }\r
+               pEnt+=32;       /* 32 byte per entry */\r
+               }\r
+       }\r
+  if (fFound) \r
+  {\r
+       *pEntRet = pEnt;\r
+       return(0);\r
+  }\r
+  else return (ErcNoSuchFile);\r
+}\r
+\r
+\r
+/*****************************************************\r
+ This searches the ROOT directory for pName\r
+ and returns a pointer to the 32 byte entry which\r
+ is in a temporary buffer. If not found, returns NIL;\r
+ When searching directories, if the filename begins\r
+ with a NULL the search need go no futher!\r
+*****************************************************/\r
+\r
+static U32 GetRootEnt(U8  *pName,\r
+               U8  Drive,\r
+               U32 *pLBARet,\r
+               U32 *poEntRet,\r
+               U8  **pEntRet)\r
+{\r
+unsigned long i, j, k, erc;\r
+U8 fFound, fEnd, *pEnt, *pStart;\r
+\r
+ i = Ldrv[Drive].LBARoot;\r
+ j = Ldrv[Drive].nRootDirEnt;\r
+\r
+ fFound = 0;\r
+ fEnd = 0;\r
+ while ((j) && (!fFound) && (!fEnd)) \r
+ {     /* while there are valid entries */\r
+\r
+       erc = DeviceOp(Ldrv[Drive].DevNum, 1, i++, 1, abRawSector);\r
+\r
+       if (erc)\r
+               return(erc);\r
+\r
+    pEnt = abRawSector;\r
+    pStart = pEnt;\r
+       for (k=0; k<16; k++) \r
+       {\r
+               if (*pEnt==0) \r
+               {                       /* 0 in a DirEnt stops search */\r
+                       fEnd=1;\r
+                       break;\r
+               }\r
+               if (CompareNCS(pEnt, pName, 11) == -1) \r
+               {\r
+                       fFound=1;\r
+            *pLBARet = i-1;                             /* tell em what LBA of DirEnt */\r
+            *poEntRet = pEnt-pStart;    /* Tell em offset in LBA */\r
+                       break;\r
+               }\r
+               --j;            /* one less dir ent */\r
+               pEnt+=32;       /* 32 byte per entry */\r
+       }\r
+ }\r
+ if (fFound) \r
+ {\r
+       *pEntRet = pEnt;\r
+       return(0);\r
+ }\r
+ else\r
+       return (ErcNoSuchFile);\r
+}\r
+\r
+/********************************************\r
+RAB -  This builds a full file specification from\r
+  pName and places it in pDest based on the\r
+  path from iJob. cbDestRet is set to size.\r
+  If the name starts with "DRIVE:"\r
+  then the path from the JCB is NOT used. Otherwise\r
+  the pName is cancatenated to the job's path.\r
+  If it begins with "\" only DRIVE: (first two\r
+  chars of path) are used.\r
+*********************************************/\r
+\r
+static void BuildSpec(char *pName,\r
+                          long cbName,\r
+                          char *pDest,\r
+                          long *cbDestRet,\r
+                          long iJob)\r
+{\r
+long i;\r
+char pathtmp[70];\r
+\r
+ if ((cbName) && (pName) && (pName[1] == ':'))\r
+ {             /* Do NOT use path */\r
+        CopyData(pName, pDest, cbName);\r
+        i = cbName;\r
+ }\r
+    /* use only drive and semicolon */\r
+ else if ((cbName) && (pName) && (pName[0] == 0x5C)) /* begins with backslash */\r
+ {\r
+       GetPath(iJob, pathtmp, &i);\r
+       pDest[0] = pathtmp[0];\r
+       pDest[1] = pathtmp[1];\r
+       i = 2;\r
+       CopyData(pName, &pDest[2], cbName);\r
+       i += cbName;\r
+ }\r
+ else\r
+ {                                                             /* Use whole path as prefix */\r
+       i = 0;\r
+       GetPath(iJob, pDest, &i);\r
+       if ((cbName) && (pName))\r
+       {\r
+               if ((pName) && (cbName))\r
+               {\r
+                       CopyData(pName, &pDest[i], cbName);\r
+                       i += cbName;\r
+               }\r
+       }\r
+ }\r
+ *cbDestRet = i;\r
+}\r
+\r
+\r
+\r
+/*****************************************************\r
+ The parses out the path name into directories, filename,\r
+ and extension  (Example):\r
+ C:\TEMP\TEST.TXT  (with TEMP being a dir in the root).\r
+ This also uses the current path for the job to build\r
+ the filename.\r
+ SpecDepth is set to the level of the last valid\r
+ 11 character string (Specdepth is 0-6).\r
+*****************************************************/\r
+\r
+static U32 ParseName(U8 *pName, U32 cbName, U32 iJob)\r
+{\r
+unsigned long i, j, k, erc;\r
+U8 c, *pPart;\r
+char Spec[70];\r
+U32 cbSpec;\r
+\r
+ erc = 0;\r
+ FDrive = 0;\r
+\r
+ FillData(FileSpec, (7*11), ' ');              /* Fill parse table with spaces */\r
+\r
+ if ((cbName) && (*pName == ' '))\r
+       erc = ErcBadFileSpec;\r
+\r
+ BuildSpec(pName, cbName, Spec, &cbSpec, iJob);\r
+\r
+ j = 0;                /* index into crnt part of spec */\r
+ k = 0;                /* index into crnt tree level   */\r
+ pPart = Spec;\r
+ for (i=0; i < cbSpec; i++)\r
+ {\r
+       switch (c = *pPart++)\r
+       {\r
+               case 0x5c  :    /* '\' separates dir or fname */\r
+                       if (j>0)\r
+                       {  /* if it's not the first one */\r
+                               ++k;\r
+                               j=0;\r
+                       }\r
+                       break;\r
+               case ':' :\r
+                       if ((j==1) && (k==0) && (FDrive==0))\r
+                       {\r
+                               FDrive = FileSpec[0][0] & 0xdf;  /* Make drive Upper*/\r
+                FileSpec[0][0] = ' ';\r
+                               j=0;                    /* back to beginning of part */\r
+                               k=0;\r
+                       }\r
+                       else erc = ErcBadFileSpec;\r
+                       break;\r
+               case '.'  :                     /* . can only appear once in dir or fname */\r
+                       if (j>8) erc = ErcBadFileSpec;\r
+                       else j=8;                                               /* move to extension */\r
+                       break;\r
+               case '>'  :                                                     /* not allowed in spec */\r
+               case '<'  :\r
+               case ','  :\r
+               case '+'  :\r
+               case '|'  :\r
+               case ']'  :\r
+               case '['  :\r
+               case '+'  :\r
+               case '='  :\r
+               case '@'  :\r
+               case '*'  :\r
+               case '?'  :\r
+                        erc = ErcBadFileSpec;\r
+                       break;\r
+               default   :                                                     /* make chars upper */\r
+                       if (j>10)\r
+                               erc = ErcBadFileSpec;\r
+                       else\r
+                       {\r
+                               if (((c >= 'A') && (c <= 'Z')) ||\r
+                                   ((c >= 'a') && (c <= 'z')))\r
+                                          c &= 0xdf;\r
+                   FileSpec[k][j] = c;\r
+                   ++j;\r
+               }\r
+                       break;\r
+               }\r
+\r
+       if (erc) break;         /* bad news. Exit for loop */\r
+       }\r
+SpecDepth = k;\r
+return erc;\r
+}\r
+\r
+\r
+/*** Get Directory Sector *******************************\r
+ Gets a 512 byte directory sector in sequence number from\r
+ the directory path given. SectNum = 0 to nMax for Dir.\r
+ This also returns the LBA for sector itself for\r
+ internal users of this call.\r
+********************************************************/\r
+\r
+static U32 GetDirSectorM(char *pPath,\r
+                                long cbPath,\r
+                                char *pSectRet,\r
+                                long cbRetMax,\r
+                                long SectNum,\r
+                                long *LBARet,\r
+                                U16  *ClstrRet,\r
+                                long iJob)\r
+{\r
+U32 sector, i, j, k, erc, spc, level, iSect;\r
+U16 MaxClstr, Clstr, rClstr;\r
+U8  fFound, *pEnt, Drive;\r
+\r
+  if (cbRetMax > 512)  /* WHOA Bub, 1 Sector at a time! */\r
+       cbRetMax = 512;\r
+\r
+  erc = ParseName(pPath, cbPath, iJob);\r
+\r
+       /* The entire path has now been parsed out into an array of\r
+          arrays, each 11 bytes long that contain each directory name\r
+          in the path for the sector they want.\r
+          The first is always the root (entry 0).\r
+          The drive will be a letter in FDrive.\r
+       */\r
+\r
+  if ((FDrive > 0x40) && (FDrive < 0x52))              /* A to J */\r
+       Drive = FDrive - 0x41;                                          /* Make it 0-9 */\r
+  else\r
+       return(ErcNoSuchDrive);\r
+\r
+  if (Drive < 2)\r
+  {\r
+       StatFloppy(Drive);\r
+       erc= read_BS(Drive);\r
+  }\r
+\r
+  if (Ldrv[Drive].DevNum == 0xff)\r
+       return(ErcNoSuchDrive);\r
+\r
+  i = Ldrv[Drive].LBARoot;\r
+  j = Ldrv[Drive].nRootDirEnt;\r
+\r
+  if (FileSpec[0][0] == ' ')\r
+  {            /* They want sector in root */\r
+       if (SectNum > j/32)                             /* Beyond Root entries! */\r
+               return(ErcNoMatch);\r
+\r
+       /* Else we can give them the sector NOW */\r
+       erc = DeviceOp(Ldrv[Drive].DevNum, 1, i+SectNum, 1, abRawSector);\r
+       if (!erc) \r
+       {\r
+               *LBARet = i+SectNum;\r
+               CopyData(abRawSector, pSectRet, cbRetMax);\r
+       }\r
+       return(erc);\r
+  }\r
+\r
+       /* We have to run the root for a dir name... */\r
+\r
+  fFound = 0;\r
+  while ((j) && (!fFound))\r
+  {    /* while there are valid entries */\r
+       erc = DeviceOp(Ldrv[Drive].DevNum, 1, i++, 1, abRawSector);\r
+       if (erc)\r
+               return(erc);\r
+    pEnt = abRawSector;                /* Point to first entry */\r
+       for (k=0; k<16; k++) \r
+       {\r
+               if (CompareNCS(pEnt, FileSpec[0], 11) == -1)\r
+               {\r
+                       fFound=1;\r
+                       break;\r
+               }\r
+               --j;            /* one less dir ent */\r
+               pEnt+=32;       /* 32 byte per entry */\r
+       }\r
+ }\r
+ if (!fFound)\r
+       return (ErcNoMatch);\r
+\r
+  pDirEnt = pEnt;              /* Entry we just found in root was dir */\r
+\r
+  if (!(pDirEnt->Attr & DIRECTORY))\r
+  {\r
+       return(ErcNoSuchDir);\r
+  }\r
+\r
+  if (Ldrv[Drive].fFAT16)\r
+       MaxClstr = 0xfff8;\r
+  else\r
+       MaxClstr = 0xff8;       /* FAT12 */\r
+\r
+  spc = Ldrv[Drive].SecPerClstr;       /* How many sectors per cluster */\r
+  Clstr = pDirEnt->StartClstr;\r
+\r
+  level = 1;   /* start at this directory+1, compare to FileSpec */\r
+\r
+  while (!erc) \r
+  {    /* looking for Dir */\r
+\r
+       if (FileSpec[level][0] == ' ')\r
+       { /* They want sector in this dir */\r
+\r
+               if (!(pDirEnt->Attr & DIRECTORY))  \r
+               {\r
+                       return(ErcNoSuchDir);\r
+               }\r
+               rClstr = SectNum /spc;  /* calc relative cluster from start clstr */\r
+               iSect  = SectNum % spc;  /* Add this to cluster start for sector */\r
+               sector = ClsToLBA(Clstr, Drive);        /* sector of first dir sector */\r
+               while ((rClstr--) && (!erc))\r
+                       erc = NextFATClstr(Drive, Clstr, &Clstr);\r
+               if (erc)\r
+                       return(erc);\r
+               sector = ClsToLBA(Clstr, Drive);  /* LBA of this clstr */\r
+               sector += iSect;\r
+               erc = DeviceOp(Ldrv[Drive].DevNum, 1, sector, 1, abRawSector);\r
+               if (!erc) \r
+               {\r
+                       CopyData(abRawSector, pSectRet, cbRetMax);\r
+                       *LBARet = sector;\r
+                       *ClstrRet = Clstr;\r
+               }\r
+               return(erc);\r
+       }\r
+       else \r
+       {  /* Else we must find this sub dir name */\r
+\r
+               sector = ClsToLBA(Clstr, Drive);        /* sector of first dir sector */\r
+               fFound=0;\r
+               i = 0;\r
+\r
+               while (!fFound)\r
+               {               /* while there are valid entries */\r
+\r
+                       if (i==spc) \r
+                       {               /* reached last dir sector of this cluster */\r
+                               erc = NextFATClstr(Drive, Clstr, &Clstr);\r
+                               if (!erc) \r
+                               {\r
+                                       if (Clstr >= MaxClstr)                   /* last sector */\r
+                                               return(ErcNoSuchFile);           /* not found */\r
+                                       sector = ClsToLBA(Clstr, Drive); /* LBA of next sector */\r
+                                       i=0;\r
+                               }\r
+                               else\r
+                                       return(erc);\r
+                       }\r
+\r
+                       erc = DeviceOp(Ldrv[Drive].DevNum, 1, sector++, 1, abRawSector);\r
+                       if (erc)\r
+                               return(erc);\r
+                       i++;    /* Next sector in this cluster */\r
+\r
+                   pEnt = &abRawSector[0];\r
+                       for (k=0; k<16; k++)\r
+                       {               /* 16 entries per sector */\r
+                               if (CompareNCS(pEnt, FileSpec[level], 11) == -1)\r
+                               {\r
+                                       fFound=1;\r
+                                       break;\r
+                               }\r
+                               pEnt+=32;       /* 32 byte per entry */\r
+                       }\r
+               }\r
+               pDirEnt = pEnt;                           /* Entry we just found */\r
+           Clstr = pDirEnt->StartClstr;  /* Clstr @ start of dir entry */\r
+       }\r
+       ++level;                /* next level of parsed filespec */\r
+  }\r
+return (erc);\r
+}\r
+\r
+/*******************************************************\r
+ This is the BLOCK read for the MMURTL DOS file system.\r
+ It reads whole sectors and returns them to pBytesRet.\r
+ There are NO internal filesystem buffers for this call.\r
+ Data is returned directly to your buffer from the Disk.\r
+ This is the fastest method for reading a file.\r
+ This is also used internally to fill a stream buffer.\r
+********************************************************/\r
+\r
+static U32 ReadBlockM(U32 dHandle,\r
+               U8  *pBytesRet,\r
+               U32 nBytes,\r
+               U32 dLFA,\r
+               U32 *pdBytesRet,\r
+               U8  fFill)                      /* TRUE if filling a stream buffer */\r
+{\r
+U32 erc, j, LBA, iFCB, bpc, spc, nDone, rLFA, nLeft, nBlks;\r
+U16 Clstr, MaxClstr, ClstrSav;\r
+U8 Drive;\r
+\r
+  erc = ValidateHandle(dHandle, &iFCB);                /* Sets iFCB if OK */\r
+  if (erc) return erc;\r
+\r
+  /* Certain FUB fields have different meanings in stream */\r
+\r
+  if ((paFUB[dHandle].fStream) && (!fFill))\r
+  {\r
+       *pdBytesRet = 0;\r
+       return ErcStreamFile;\r
+  }\r
+\r
+  /* set these up in advance */\r
+\r
+  nBlks = nBytes/512;                                  /* nBytes MUST be multiple of 512 */\r
+  Drive = paFCB[iFCB]->Ldrv;                   /* What logical drive are we on? */\r
+  spc = Ldrv[Drive].SecPerClstr;               /* sectors per cluster */\r
+  bpc = 512 * spc;                     /* Bytes per cluster */\r
+  if (Ldrv[Drive].fFAT16)\r
+       MaxClstr = 0xfff8;\r
+  else\r
+       MaxClstr = 0xff8;       /* FAT12 */\r
+\r
+\r
+  /* Call to find the absolute cluster on the the logical disk,\r
+     and also the relative LFA of the cluster in question.\r
+  */\r
+\r
+  erc = GetAbsoluteClstr(dHandle, dLFA, &Clstr, &rLFA);\r
+\r
+  LBA = ClsToLBA(Clstr, Drive);                /* Get LBA of the target cluster */\r
+\r
+  /* Now LBA equals beginning of cluster that dLFA resides in.\r
+     We must see which sector in Clstr is the starting LBA.  To do this\r
+     we MOD (dLFA in sectors) by (sectors per cluster) and\r
+     add the leftover amount (should be 0 to nSPC-1) to LBA before we\r
+     read.  For example: if dLFA was 2560 and spc was 4, this\r
+     would be 5 % 4 = 1.  We would add 1 to the LBA.\r
+     This is only done for the first read in the loop.\r
+\r
+     We also set nleft which is how many sectors are left in the\r
+     current cluster we are reading from.\r
+  */\r
+\r
+  LBA += (dLFA/512) % spc;\r
+  nLeft = spc - ((dLFA/512) % spc);\r
+  nDone = 0;\r
+\r
+  while ((nBlks) && (!erc))\r
+  {    /* while buffer isn't full and no error */\r
+       if (nBlks > nLeft)\r
+               j = nLeft;\r
+       else j = nBlks;\r
+\r
+       paFUB[dHandle]->Clstr = Clstr;                  /* Save Current cluster */\r
+       paFUB[dHandle]->LFAClstr = rLFA;                /* Save LFA for Clstr in FUB */\r
+\r
+       erc = DeviceOp(Ldrv[Drive].DevNum, 1, LBA, j, pBytesRet);\r
+       if (erc)\r
+               break;\r
+       pBytesRet += j * 512;   /* further into their buffer */\r
+       nBlks -= j;\r
+       nLeft -= j;\r
+       nDone += j;\r
+\r
+       if ((nBlks) && (!nLeft))\r
+       {               /* current cluster has none left */\r
+               nLeft = spc;\r
+               ClstrSav = Clstr;\r
+               erc = NextFATClstr(Drive, Clstr, &Clstr);       /* next FAT cluster */\r
+               if (erc)\r
+               {\r
+                       *pdBytesRet = nDone*512;\r
+                       return(erc);\r
+               }\r
+               rLFA += bpc;                                             /* Update rel LFA of new cluster*/\r
+           if (Clstr >= MaxClstr)\r
+               erc = ErcEOF; /* Last cluster */\r
+           if (!Clstr)\r
+               erc = ErcBrokenFile;     /* No good next cluster! */\r
+               LBA = ClsToLBA(Clstr, Drive);            /* Get LBA of the target cluster*/\r
+       }\r
+  }\r
+  *pdBytesRet = nDone*512;\r
+\r
+  return erc;          /* WE'RE DONE, return the error (if any) */\r
+}\r
+\r
+\r
+/*** Write Block ***************************************\r
+ This is the BLOCK write for the MMURTL FAT file system.\r
+ dLFA must be the LFA of a valid portion of the file and\r
+ nBlks must not extend beyond the last cluster allocated\r
+ to the file. IOW, you must call SetFileSize first.\r
+ In a block write dLFA is always written on\r
+ a sector boundry. We must make sure that the filesize\r
+ will accomidate the sectors we want to write!\r
+********************************************************/\r
+\r
+static U32 WriteBlockM(U32 dHandle, char *pData, U32 nBytes,\r
+                U32 dLFA, U32 *pdnBytesRet)\r
+{\r
+U32 erc, i, j, LBA, iFCB, bpc, spc, nDone, rLFA, nLeft, nBlks;\r
+U32 nLeft1, LBA1, nBlks1;\r
+U16 Clstr, MaxClstr;\r
+U8 Drive;\r
+\r
+  erc = ValidateHandle(dHandle, &iFCB);                /* Sets iFCB if OK */\r
+  if (erc) return erc;\r
+\r
+  nBlks = nBytes/512;\r
+  dLFA = (dLFA/512)*512;               /* round LFA down to nearest sector */\r
+\r
+  if (!paFCB[iFCB]->Mode)              /* Is it open in Modify?? */\r
+       return(ErcReadOnly);\r
+\r
+  i = (paFCB[iFCB]->FileSize/512);     /* Set i nBlks in file max */\r
+  if (paFCB[iFCB]->FileSize%512)\r
+       i++;\r
+\r
+  j = (dLFA/512) + nBlks;      /* blocks to write past dLFA*/\r
+\r
+  if (j > i)\r
+       return(ErcBeyondEOF);\r
+\r
+  /* It seems OK to write the blocks out, so now let's DO IT! */\r
+\r
+  Drive = paFCB[iFCB]->Ldrv;                   /* What logical drive are we on? */\r
+  spc = Ldrv[Drive].SecPerClstr;               /* sectors per cluster */\r
+  bpc = 512 * spc;                     /* Bytes per cluster */\r
+  if (Ldrv[Drive].fFAT16)\r
+       MaxClstr = 0xfff8;\r
+  else\r
+       MaxClstr = 0xff8;       /* FAT12 */\r
+\r
+  erc = GetAbsoluteClstr(dHandle, dLFA, &Clstr, &rLFA);\r
+  if (erc)\r
+       return(erc);\r
+\r
+  LBA = ClsToLBA(Clstr, Drive);                /* Get LBA of the target cluster */\r
+\r
+  /* Now LBA equals beginning of cluster that dLFA resides in.\r
+     We must see which sector in Clstr is the starting LBA.  To do this\r
+     we MOD (dLFA in sectors) by (sectors per cluster) and\r
+     add the leftover amount (should be 0 to nSPC-1) to LBA before we\r
+     write.  For example: if dLFA was 2560 and spc was 4, this\r
+     would be 5 % 4 = 1.  We would add 1 to the LBA.\r
+     This is only done for the first write in the loop.\r
+     We also set nleft which is how many sectors are left in the\r
+     current cluster we are writing to so we know when to\r
+     move to the next cluster.\r
+  */\r
+\r
+  LBA += (dLFA/512) % spc;\r
+  LBA1 = LBA;\r
+  nLeft = spc - ((dLFA/512) % spc);\r
+  nLeft1 = nLeft;\r
+  nBlks1 = nBlks;\r
+  nDone = 0;\r
+\r
+  while ((nBlks) && (!erc))\r
+  {    /* while blocks are left to write */\r
+       if (nBlks > nLeft)\r
+               j = nLeft;\r
+       else j = nBlks;\r
+\r
+       paFUB[dHandle]->Clstr = Clstr;                  /* Save Current cluster */\r
+       paFUB[dHandle]->LFAClstr = rLFA;                /* Save LFA for Clstr in FUB */\r
+\r
+       erc = DeviceOp(Ldrv[Drive].DevNum, 2, LBA, j, pData);\r
+       if (erc)\r
+               break;\r
+       pData += (j * 512);     /* Update address */\r
+       nDone += j;                     /* Total blocks done so far */\r
+       nBlks -= j;\r
+       nLeft -= j;\r
+\r
+       if ((nBlks) && (!nLeft))\r
+       {               /* done with current cluster */\r
+               nLeft = spc;\r
+               erc = NextFATClstr(Drive, Clstr, &Clstr);       /* next FAT cluster */\r
+               if (erc)\r
+                       return(erc);\r
+               rLFA += bpc;                                             /* Update rel LFA of new cluster*/\r
+           if ((Clstr >= MaxClstr) && (nBlks))  /* Problem! */\r
+           {\r
+               erc = ErcBeyondEOF;                      /* Last cluster & they want more*/\r
+               }\r
+           if (!Clstr) erc = ErcBrokenFile;     /* No good next cluster! */\r
+               LBA = ClsToLBA(Clstr, Drive);            /* Get LBA of the target cluster*/\r
+       }\r
+  }\r
+  *pdnBytesRet = nDone * 512;\r
+  return erc;          /* WE'RE DONE, return the error (if any) */\r
+}\r
+\r
+\r
+/*********************************************************\r
+ Fills the stream buffer for the Current LFA in the FUB.\r
+ We simply figure out which relative LBA we want in the\r
+ buffer and call ReadBlockM to fill it.  We then set\r
+ LFABuf in the FUB to show the LFA of the first byte\r
+ in the buffer.\r
+**********************************************************/\r
+\r
+static U32 FillStreamBuff(U32 dHandle, U8 fInitial)\r
+{\r
+U32 erc, i, LFA, cLFA, LFABuf, iFCB;\r
+U32 sBuf;\r
+U8  *pBuf;\r
+\r
+  erc = 0;\r
+\r
+  /* Set these up in advance */\r
+\r
+  cLFA =  paFUB[dHandle]->CrntLFA;             /* Find out where we want to be */\r
+  LFABuf = paFUB[dHandle]->LFABuf;             /* LFA of first byte in buffer now */\r
+  pBuf = paFUB[dHandle]->pBuf;                 /* Local ptr to buffer */\r
+  sBuf = paFUB[dHandle]->sBuf;                 /* size of buffer */\r
+  iFCB = paFUB[dHandle]->iFCB;                 /* FCB for this FUB */\r
+\r
+       /*      If the file was just opened we fill with LFA 0 */\r
+\r
+  if (fInitial)\r
+  {\r
+       erc = ReadBlockM(dHandle, pBuf, sBuf, 0, &i, TRUE);\r
+    paFUB[dHandle]->LFABuf = 0;\r
+  }\r
+\r
+       /* Else If the LFA is already in the buffer we just exit OK */\r
+\r
+  else if ((cLFA >= LFABuf) && (cLFA < (LFABuf + sBuf)))\r
+  {\r
+       erc = 0;\r
+  }\r
+\r
+       /* ELSE We must figure out what starting LFA we want and fill the buffer */\r
+\r
+  else\r
+  {\r
+       LFA = (cLFA/512) * 512;         /* Round down to nearest sector */\r
+       erc = ReadBlockM(dHandle, pBuf, sBuf, LFA, &i, 1);\r
+    paFUB[dHandle]->LFABuf = LFA;\r
+  }\r
+\r
+  if (erc == ErcEOF)           /* We ignore this when filling the buffer */\r
+       erc = 0;\r
+  return erc;          /* WE'RE DONE, return the error (if any, except EOF) */\r
+}\r
+\r
+/*******************************************************\r
+ This is the STREAM read for the MMURTL FAT file system.\r
+ Used in conjunction with Get & Set FileLFA, you can\r
+ move to any portion of the file to read one or more bytes.\r
+ Data is buffered in Page sized chunks by the file\r
+ system. This is the easiest method for reading a file.\r
+********************************************************/\r
+\r
+static U32 ReadBytesM(U32 dHandle, U8 *pBytesRet, U32 nBytes, U32 *pdReadRet)\r
+{\r
+U32 erc, iFCB, sBuf, cLFA, fSize;\r
+U32 nBytesDone,        /* Total read so far */\r
+       lfaEOB,                 /* LFA end of Buffer */\r
+       nBytesOut;              /* Number of bytes to copy (this buffer full) */\r
+U8  *pOut, *pBuf;      /* Next data to send caller from buffer. Local pbuf */\r
+\r
+  erc = ValidateHandle(dHandle, &iFCB);                /* Sets iFCB if OK */\r
+  if (erc) return erc;\r
+\r
+  /* Certain FUB fields have different meanings in stream type file */\r
+\r
+  if (!paFUB[dHandle]->fStream)\r
+       return ErcBlockFile;\r
+\r
+  cLFA = paFUB[dHandle]->CrntLFA;                      /* local cLFA */\r
+  fSize = paFCB[iFCB]->FileSize;                       /* local size */\r
+\r
+  /* check and see if we are at EOF */\r
+\r
+  if (cLFA >= fSize)\r
+  {                                            /* early out */\r
+    *pdReadRet = 0;\r
+       return ErcEOF;\r
+  }\r
+\r
+  /* We are not at EOF so we need to fill stream buff\r
+     and then calculate how many bytes we can give them\r
+     from this buffer full of data.\r
+  */\r
+\r
+  pBuf = paFUB[dHandle]->pBuf;                 /* Local ptr to buffer/size */\r
+  sBuf = paFUB[dHandle]->sBuf;\r
+  nBytesDone = 0;\r
+\r
+  while        ((nBytesDone < nBytes) &&\r
+               (cLFA < fSize) &&\r
+               (!erc))\r
+  {\r
+       erc = FillStreamBuff(dHandle, 0);                               /* Fill the buff */\r
+\r
+       /* Find out the LFA at the end of the buffer since we\r
+          just filled it.  It may be less than the end since\r
+          we may be near EOF.\r
+       */\r
+\r
+    lfaEOB = paFUB[dHandle]->LFABuf + sBuf -1; /* LFA at End of Buffer */\r
+    if (lfaEOB > fSize)                                                        /* Beyond EOF? */\r
+       lfaEOB = fSize - 1;\r
+\r
+       /* Calculate pointer to the next chunk out from stream buffer */\r
+\r
+       pOut = pBuf + (cLFA - paFUB[dHandle]->LFABuf);\r
+\r
+       /* Calc how many bytes we can get out of buffer (belongs to file) */\r
+\r
+       nBytesOut = lfaEOB - cLFA + 1;\r
+       if (nBytesOut > nBytes-nBytesDone)\r
+               nBytesOut = nBytes-nBytesDone;\r
+\r
+       /* Send bytes to pBytesRet */\r
+\r
+       CopyData(pOut, pBytesRet, nBytesOut);\r
+\r
+       pBytesRet += nBytesOut;         /* Update pBytesRet */\r
+       nBytesDone += nBytesOut;                /* Update nBytesDone */\r
+       cLFA += nBytesOut;                              /* update CrntLFA */\r
+       paFUB[dHandle]->CrntLFA = cLFA;\r
+  }\r
+\r
+  *pdReadRet = nBytesDone;                     /* Tell em how many bytes they got */\r
+  if (!erc)\r
+       if (cLFA == fSize)\r
+               erc = 1;\r
+\r
+  return erc;\r
+}\r
+\r
+/*********************************************************\r
+ Flushes the stream buffer if it has been modified.\r
+ We read the current LBA of the buffer and write it\r
+ to disk. Then reset the flag in the FCB.\r
+ This uses WriteBlockM.  We ensure we do not call\r
+ WriteBlockM with more sectors than are allocated\r
+ to the file cause the buffer may extend past the\r
+ actual allocated amount of clusters!\r
+**********************************************************/\r
+\r
+static U32 FlushStreamBuff(U32 dHandle)\r
+{\r
+U32 erc, i, j, LFABuf, iFCB, size;\r
+U32 sBuf;\r
+U8  *pBuf;\r
+\r
+  erc = 0;\r
+\r
+  if (paFUB[dHandle]->fModified)\r
+  {\r
+         LFABuf = paFUB[dHandle]->LFABuf;              /* LFA of first byte in buf now */\r
+         pBuf = paFUB[dHandle]->pBuf;                  /* Local ptr to buffer */\r
+         sBuf = paFUB[dHandle]->sBuf;                  /* size of buffer */\r
+         iFCB = paFUB[dHandle]->iFCB;                  /* to check filesize */\r
+         size = paFCB[iFCB]->FileSize;\r
+\r
+         i = (sBuf/512);                       /* Total blocks in buff */\r
+         j = (size-LFABuf)/512;        /* Blocks in buf belonging to file */\r
+         if ((size-LFABuf)%512)        /* Odd bytes, add one more block */\r
+               j++;\r
+         if (j < i)\r
+               i = j;\r
+\r
+         erc = WriteBlockM(dHandle, pBuf, i*512, LFABuf, &i);\r
+      paFUB[dHandle]->fModified = 0;\r
+  }\r
+  return erc;\r
+}\r
+\r
+\r
+/*** Write Bytes (Stream) ******************************\r
+ This is the STREAM write for the MMURTL FAT file system.\r
+ This uses FillStreamBuff to set up the buffer, and\r
+ FlushStreamBuff to write modified stream buffers.\r
+ This also calls SetFileSizeM to extend the\r
+ filelength when necessary (writing at EOF).\r
+********************************************************/\r
+\r
+static U32 WriteBytesM(U32 dHandle, char *pData, U32 nBytes, U32 *nBytesRet)\r
+{\r
+U32 erc, iFCB, sBuf, cLFA, fSize;\r
+U32 nBytesDone,        /* Total written so far */\r
+       lfaEOB,                 /* LFA end of Buffer */\r
+       nBytesOut;              /* Number of bytes to copy (this buffer full) */\r
+U8  *pOut, *pBuf;      /* Next data to send caller from buffer. Local pbuf */\r
+\r
+       erc = ValidateHandle(dHandle, &iFCB);           /* Sets iFCB if OK */\r
+       if (erc) return erc;\r
+\r
+       /* Certain FUB fields have different meanings in stream type file */\r
+\r
+       if (!paFUB[dHandle]->fStream)\r
+               return(ErcBlockFile);\r
+\r
+       if (!paFCB[iFCB]->Mode)         /* Is it open in Modify?? */\r
+               return(ErcReadOnly);\r
+\r
+       /* Set up local vars to values for current stream buffer.\r
+          These are in effect for this call on entry!\r
+       */\r
+\r
+       pBuf = paFUB[dHandle]->pBuf;            /* Local ptr to buffer/size */\r
+       sBuf = paFUB[dHandle]->sBuf;\r
+       cLFA = paFUB[dHandle]->CrntLFA;         /* local cLFA */\r
+       fSize = paFCB[iFCB]->FileSize;          /* local size */\r
+\r
+       /* check and see if we are at EOF or will go past it\r
+       when we write. If so, we make the file larger.\r
+       */\r
+\r
+       if (cLFA + nBytes > fSize)\r
+       {               /* Must set file size first */\r
+               erc = SetFileSizeM(dHandle, cLFA + nBytes);\r
+               if (erc)\r
+               {\r
+                       return(erc);\r
+               }\r
+               fSize = paFCB[iFCB]->FileSize;                  /* local size */\r
+       }\r
+\r
+       lfaEOB = paFUB[dHandle]->LFABuf + sBuf -1;      /* LFA at End of Buffer */\r
+    if (lfaEOB > fSize)                                                        /* EOF before EOB? */\r
+       lfaEOB = fSize - 1;\r
+\r
+       /* Now we loop writing the bytes to the stream buffer\r
+     filling it with each new section of the file.\r
+     As this occurs we must call fillStreamBuff with\r
+     each new section of the file if not already in the\r
+     buff to ensure proper continuity of the file in case\r
+     we are overwriting existing sections of the file.\r
+       */\r
+\r
+       nBytesDone = 0;\r
+\r
+       while ((nBytesDone < nBytes) &&\r
+                  (cLFA < fSize) &&\r
+                  (!erc))\r
+       {\r
+               /* If the next byte to write goes outside the current buffer\r
+               (before or after it),\r
+               we call FlushStreamBuff which will write the current\r
+               buffer and reset the fModified flag (if needed),\r
+               then call FillStreamBuff for file continuity.\r
+               */\r
+\r
+               if ((cLFA > lfaEOB) ||\r
+                       (cLFA < paFUB[dHandle]->LFABuf))\r
+               {\r
+                       erc = FlushStreamBuff(dHandle);\r
+                               if (erc)\r
+                               {\r
+                                       return(erc);\r
+                               }\r
+                       erc = FillStreamBuff(dHandle, 0);       /* Fill the buff */\r
+                               if (erc)\r
+                               {\r
+                                       return(erc);\r
+                               }\r
+               }\r
+\r
+               /* Find out the LFA at the end of the buffer since we\r
+                  just filled it.  It may be less than the end since\r
+                  we may be near EOF.\r
+               */\r
+\r
+           lfaEOB = paFUB[dHandle]->LFABuf + sBuf -1;  /* LFA at End of Buffer */\r
+           if (lfaEOB > fSize)                                                 /* Beyond EOF? */\r
+               lfaEOB = fSize - 1;\r
+\r
+               /* Calc pointer to where next chunk goes in stream buffer */\r
+\r
+               pOut = pBuf + (cLFA - paFUB[dHandle]->LFABuf);\r
+\r
+               /* Calc how many bytes we can write to buffer. We must\r
+               ensure we don't exceed buffer size. */\r
+\r
+               nBytesOut = (lfaEOB + 1) - cLFA;\r
+               if (nBytesOut > nBytes-nBytesDone)\r
+                       nBytesOut = nBytes-nBytesDone;\r
+\r
+               /* Get bytes from pData into stream buffer */\r
+\r
+               CopyData(pData, pOut, nBytesOut);\r
+               paFUB[dHandle]->fModified = 1;\r
+\r
+               pData += nBytesOut;                     /* Update pData pointer */\r
+               nBytesDone += nBytesOut;                /* Update nBytesDone */\r
+               cLFA += nBytesOut;                              /* update CrntLFA */\r
+               paFUB[dHandle]->CrntLFA = cLFA;\r
+       }\r
+\r
+       *nBytesRet = nBytesDone;                        /* Tell em how many bytes we wrote */\r
+       return erc;\r
+}\r
+\r
+/*******************************************************\r
+  GetFileSize returns the FileSize entry from the FCB\r
+  for the handle specified.  This means the file must be\r
+  OPEN.  This call would NOT be used for a directory\r
+  listing function as you would have to open each file\r
+  to get the information.  Use ReadDirSector instead.\r
+********************************************************/\r
+\r
+static U32 GetFileSizeM(U32 dHandle, U32 *pdSizeRet)\r
+{\r
+U32 erc, iFCB;\r
+  erc = ValidateHandle(dHandle, &iFCB);\r
+  if (erc) return erc;\r
+  *pdSizeRet = paFCB[iFCB]->FileSize;\r
+  return 0;\r
+}\r
+\r
+\r
+/*******************************************************\r
+  SetFileLFA sets a Stream mode file pointer to dLFA.\r
+  If attempted on a block mode file an error is returned.\r
+  -1 (0xffffffff) is equal to EOF.  The Stream Buffer\r
+  is filled with the sectors that contains the LFA.\r
+********************************************************/\r
+\r
+static U32 SetFileLFAM(U32 dHandle, S32 dLFA)\r
+{\r
+U32 erc, iFCB;\r
+\r
+  erc = ValidateHandle(dHandle, &iFCB);\r
+  if (erc) return erc;\r
+\r
+  if (!paFUB[dHandle]->fStream)\r
+       return ErcBlockFile;\r
+\r
+  if (paFCB[iFCB]->Mode)       /* Modify mode - Flush will flush if needed */\r
+               erc = FlushStreamBuff(dHandle);\r
+\r
+  /* -1 = Set file ptr to EOF */\r
+\r
+  if (dLFA == -1)\r
+       dLFA = paFCB[iFCB]->FileSize;\r
+\r
+  if (dLFA > paFCB[iFCB]->FileSize)\r
+    erc = ErcBeyondEOF;\r
+\r
+  if (!erc)\r
+  {\r
+         paFUB[dHandle]->CrntLFA = dLFA;       /* Set where we are in the file */\r
+         erc = FillStreamBuff(dHandle, 0);\r
+  }\r
+\r
+  return erc;\r
+}\r
+\r
+\r
+/*******************************************************\r
+  GetFileLFA gets a Stream mode file pointer for caller\r
+  returning it to pdLFARet.\r
+  If attempted on a block mode file an error is returned.\r
+  EOF is returned as the FileSize.\r
+********************************************************/\r
+\r
+static U32 GetFileLFAM(U32 dHandle, U32 *pdLFARet)\r
+{\r
+U32 erc, iFCB;\r
+  erc = ValidateHandle(dHandle, &iFCB);\r
+  if (erc) return erc;\r
+  if (!paFUB[dHandle]->fStream)\r
+       return ErcBlockFile;\r
+  *pdLFARet = paFUB[dHandle]->CrntLFA;\r
+\r
+  return erc;\r
+}\r
+\r
+/*****************************************************\r
+ This calls parse to validate the filename and separate\r
+ it into directories and a filename.  It then walks\r
+ up the tree until it either finds and opens the file,\r
+ or errors out.  The handle that is returned is\r
+ 4 higher than the index to the FUB.  This is becuase\r
+ 0, 1, 2 & 3 are reserved for NUL, KBD, VID, and LPT\r
+ which are  only accesible from the blocking File calls.\r
+*****************************************************/\r
+\r
+static U32 OpenFileM(U8 *pName,\r
+                         U32 cbName,\r
+                         U8 Mode,\r
+                         U8 fStream,\r
+                         U32 *pdHandleRet,\r
+                         U32 iJob)                     /* make use of iJob later!!! */\r
+{\r
+U32 erc, level, i, iFCB, iFUB, LBADirEnt, EntOffset;\r
+U16 Clstr;\r
+U8 fFound, *pMem, Drive;\r
+\r
+  if (Mode > 1)\r
+       return ErcBadOpenMode;\r
+\r
+  level = 0;   /* start at the root, compare to SpecDepth */\r
+\r
+/* RAB B */\r
+ if (((cbName) && (*pName == ' ')) ||\r
+     (!cbName))\r
+       return(ErcBadFileSpec);\r
+/* RAB E */\r
+\r
+  erc = ParseName(pName, cbName, iJob);\r
+\r
+       /* The entire path has now been parsed out into an array of\r
+          arrays, each 11 bytes long that contain each directory name\r
+          up to and inlcuding the filename.  The first is always\r
+          the root (entry 0). The drive will be a letter in FDrive.\r
+       */\r
+\r
+  if ((FDrive > 0x40) && (FDrive < 0x52))      /* A to J */\r
+       Drive = FDrive - 0x41;                                                  /* Make it 0-9 */\r
+  else erc = ErcNoSuchDrive;\r
+\r
+  if (Ldrv[Drive].DevNum == 0xff)\r
+    erc = ErcNoSuchDrive;\r
+\r
+  if ((Drive < 2) && (!erc))\r
+  {\r
+       StatFloppy(Drive);\r
+       erc= read_BS(Drive);\r
+  }\r
+\r
+  if (!erc)\r
+  {                                    /* Get Root dir entry */\r
+       erc = GetRootEnt(FileSpec[level],\r
+                                        Drive,\r
+                                        &LBADirEnt,\r
+                                        &EntOffset,\r
+                                        &pDirEnt);\r
+\r
+       if (erc == ErcNoSuchFile)\r
+       {\r
+               if (level == SpecDepth) erc = ErcNoSuchFile;\r
+               else erc = ErcNoSuchDir;\r
+       }\r
+       if (erc)\r
+               return(erc);\r
+\r
+       if (!erc)\r
+           Clstr = pDirEnt->StartClstr;  /* Clstr = beginning of file or dir */\r
+\r
+       while ((level < SpecDepth) && (!erc))\r
+       {       /* looking for Dir, not file yet */\r
+\r
+               ++level;                                        /* next level of parsed filespec */\r
+\r
+               erc = GetDirEnt(FileSpec[level],\r
+                                               Drive,\r
+                                               Clstr,\r
+                                               &LBADirEnt,\r
+                                               &EntOffset,\r
+                                               &pDirEnt);\r
+               if (erc == ErcNoSuchFile)\r
+               {\r
+                       if (level == SpecDepth)\r
+                               erc = ErcNoSuchFile;\r
+                       else erc = ErcNoSuchDir;\r
+               }\r
+               else if (erc)\r
+                       return(erc);\r
+               else\r
+                   Clstr = pDirEnt->StartClstr;  /* Clstr @ start of dir entry */\r
+       }\r
+\r
+       /* if we got here with no error we've got a file or a DIR.\r
+          If it's DIR then it's an error.\r
+          pDirEnt points to its directory entry, and Clstr\r
+          is the starting cluster of the file\r
+       */\r
+\r
+       if (!erc) \r
+       {\r
+               /* If Attributes say it's not a file then ERROR */\r
+\r
+               if (pDirEnt->Attr & (VOLNAME | DIRECTORY))\r
+                       return ErcNotAFile;\r
+\r
+               /* If ModeModify and File is readOnly then ERROR */\r
+\r
+               if ((Mode) && (pDirEnt->Attr & READONLY))\r
+                       return ErcReadOnly;\r
+\r
+       /* We check to see if it's already open by looking through the\r
+          valid FCBs to see if we have a Drive, StartClstr& name match.\r
+          If so, we must see if the modes are compatible.\r
+          A valid FCB is one where nUsers > 0.\r
+       */\r
+\r
+       fFound = 0;\r
+       i=0;\r
+       while ((i<nFCBs) && (!fFound)) \r
+       {\r
+\r
+               if ((paFCB[i]->nUsers) &&\r
+                       (paFCB[i]->Ldrv == Drive) &&\r
+                   (paFCB[i]->StartClstr == pDirEnt->StartClstr) &&\r
+                   (CompareNCS(&paFCB[i],\r
+                                       FileSpec[SpecDepth], 11) == 0xffffffff))\r
+                        fFound = 1;\r
+               else\r
+                   ++i;\r
+       }\r
+\r
+       if (fFound)\r
+       {                       /* it's open already.  i is index into FCBs  */\r
+               if (paFCB[i]->Mode)             /* it's open in Modify already */\r
+                       return ErcFileInUse;\r
+               else\r
+               {\r
+                       iFCB = i;                       /* Index to this FCB */\r
+                       pFCB = &paFCB[i];       /* make pFCB point to FCB found */\r
+                       pFCB->nUsers++;         /* One more user */\r
+               }\r
+       }\r
+       else\r
+       {                                       /* It not already open. Find empty FCB */\r
+               i = 0;\r
+               while ((i<nFCBs) && (paFCB[i]->nUsers)) ++i;    /* Find new FCB */\r
+\r
+               if (i==nFCBs) return ErcNoFreeFCB;                      /* Couldn't */\r
+\r
+               /* i now indexes into FCBs for a free FCB.  New we copy the\r
+                  directory entry for the file into the FCB and set up\r
+                  the other FCB values. */\r
+\r
+               iFCB = i;                                               /* used to add an FUB */\r
+               pFCB = &paFCB[i];                               /* make pFCB point to FCB found */\r
+               CopyData(pDirEnt, pFCB, 32);    /* Copy Dir Ent into FCB */\r
+               pFCB->Ldrv = Drive;                             /* Set Drive */\r
+               pFCB->nUsers++;                                 /* Now in use */\r
+               pFCB->Mode = Mode;                              /* Open mode */\r
+               pFCB->LBADirSect =      LBADirEnt;      /* So we know where it came from */\r
+               pFCB->oSectDirEnt = EntOffset;  /* "  "  */\r
+       }\r
+\r
+       /* Now we have an FCB (either existing or we just built it).\r
+          Now add an FUB and fill in the info for the user\r
+          so we can return a handle to it.  The Job fields is 0\r
+          for free FUBs.\r
+       */\r
+\r
+       i = 4;\r
+       while ((i<nFUBs) && (paFUB[i]->Job)) ++i;       /* Find new FUB */\r
+       if (i==nFUBs) \r
+       {\r
+               pFCB->nUsers--;                                 /* Make FCB correct */\r
+               return ErcNoFreeFUB;                    /* Couldn't */\r
+\r
+       }\r
+\r
+       /* If we got here, i is an index to a free FUB. */\r
+\r
+       iFUB = i;\r
+       paFUB[iFUB]->Job = iJob;                /* Job Owner */\r
+       paFUB[iFUB]->iFCB = iFCB;               /* Set index to FCB for this file */\r
+       paFUB[iFUB]->CrntLFA = 0;               /* Current Logical File Address */\r
+       paFUB[iFUB]->fModified = 0;             /* Stream buf was modified */\r
+       paFUB[iFUB]->fStream = fStream; /* NonZero for STREAM mode */\r
+       paFUB[iFUB]->Clstr = pDirEnt->StartClstr;       /* Start Cluster */\r
+       paFUB[iFUB]->LFAClstr = 0;              /* Rel LFA to 0 */\r
+       paFUB[iFUB]->LFABuf = 0;                /* First LFA in Buffer */\r
+       paFUB[iFUB]->sBuf = 0;                  /* Default to No Buf */\r
+\r
+       if (fStream) \r
+       {               /* allocate/fill buffer and set rest of FUB */\r
+\r
+               erc = AllocOSPage(1, &pMem);            /* Stream Buf is 4K */\r
+\r
+           if (erc) \r
+           {                                                   /* No MEM left... Bummer */\r
+                       pFCB->nUsers--;                                 /* Return FCB to pool */\r
+                       paFUB[iFUB]->Job = 0;                   /* Return FUB to pool */\r
+                       return erc;                                             /* Return Erc to user... */\r
+               }\r
+\r
+               paFUB[iFUB]->pBuf = pMem;               /* Ptr to buffer if stream mode */\r
+               paFUB[iFUB]->sBuf = 4096;               /* Size of buffer */\r
+\r
+               erc = FillStreamBuff(iFUB, 1);  /* fInitial to TRUE */\r
+\r
+               if (erc) \r
+               {\r
+                       pFCB->nUsers--;                         /* Return FCB to pool */\r
+                       paFUB[iFUB]->Job = 0;           /* Return FUB to pool */\r
+                       DeAllocPage(pMem, 1);           /* Free memory for buffer */\r
+                       return erc;                                     /* Return Erc to user... */\r
+               }\r
+       }\r
+\r
+    *pdHandleRet = iFUB;               /* File handle */\r
+       }\r
+  }\r
+\r
+return erc;\r
+\r
+}\r
+\r
+\r
+/*******************************************************\r
+  CLOSE FILE for the MMURTL DOS file system. This finds\r
+  the FUB, checks to see if the buffer should be flushed\r
+  and deallocated (only for stream mode), invalidates\r
+  the FUB, and the FCB (if this is the last or only user).\r
+********************************************************/\r
+\r
+static U32 CloseFileM (U32 dHandle)\r
+{\r
+U32 erc, iFCB, i;\r
+\r
+       erc = ValidateHandle(dHandle, &iFCB);\r
+       if (erc) return erc;\r
+\r
+       if (paFCB[iFCB]->Mode)\r
+       {  /* Modify mode */\r
+               if (paFUB[dHandle]->fStream)\r
+               {\r
+                       erc = FlushStreamBuff(dHandle);\r
+               }\r
+               UpdateDirEnt(iFCB);                     /* ignore error */\r
+       }\r
+\r
+       if (paFUB[dHandle]->fStream)\r
+               DeAllocPage(paFUB[dHandle]->pBuf, 1);   /* Free buffer */\r
+\r
+       /* This means the FS is screwed up. This shouldn't happen... */\r
+       if (!paFCB[iFCB]->nUsers)\r
+               erc = ErcBadFCB;\r
+       else\r
+               paFCB[iFCB]->nUsers--;\r
+\r
+         /* Now we should be able to close it and free the the FUB.\r
+        If the FCB.nUsers flips to 0 it will be free too\r
+         */\r
+\r
+       paFUB[dHandle]->Job = 0;\r
+\r
+       /* This will write all modified fat sectors */\r
+\r
+       for (i=0; i<nFATBufs; i++)\r
+                       UpdateFAT(i);\r
+\r
+  return erc;\r
+}\r
+\r
+\r
+/*** Create File ***************************************\r
+ This is Create File for the MMURTL FAT file system.\r
+ This is also used internally to create directories.\r
+********************************************************/\r
+\r
+static U32 CreateFileM(char *pName,\r
+                               long cbName,\r
+                               long attrib,\r
+                               long iJob)\r
+{\r
+unsigned long dHandle, i, j, k, erc, LBA, spc;\r
+char Path[70];\r
+long cbPath;\r
+char filename[12];\r
+U16 CrntClstr, ClstrValue, iStart, DirClstr;\r
+U8 fFound, Drive, fDir;\r
+\r
+       /* First we try to open it to see if it exists. If we get back\r
+               ErcOK or ErcFileInUse the name is already\r
+               in use as a file and we give them ErcDupName.\r
+               We return other errors as we find them.\r
+       */\r
+       if (attrib & DIRECTORY)\r
+       {\r
+               fDir = 1;\r
+               erc = 0;\r
+       }\r
+       else\r
+               fDir = 0;\r
+\r
+       erc = OpenFileM(pName, cbName, 0, 0, &dHandle, iJob);\r
+\r
+       switch (erc)\r
+       {\r
+       case ErcOK:\r
+               CloseFileM(dHandle);\r
+               erc = ErcDupName;\r
+               break;\r
+       case ErcFileInUse:\r
+               erc = ErcDupName;\r
+               break;\r
+       case ErcNotAFile:       /* It a directory... */\r
+               break;\r
+       case ErcNoSuchFile:\r
+       {       /* OK, this means we can try to create it! */\r
+\r
+               erc = 0;\r
+\r
+               BuildSpec(pName, cbName, Path, &cbPath, iJob);\r
+\r
+               erc = ParseName(Path, cbPath, iJob);\r
+               if (erc)\r
+                       return(erc);\r
+\r
+               /* FDrive was set up on Parse */\r
+               Drive = FDrive - 0x41;                  /* Make it 0-9 */\r
+\r
+               /* First we setup the filename from what was parsed out\r
+               during the Parse call. Then eliminate it from Path.\r
+               */\r
+\r
+               CopyData(FileSpec[SpecDepth], filename, 11); /* filename */\r
+\r
+               filename[11] = 0;\r
+\r
+               /* Hack the filename from Path so we can search the\r
+               directory path properly */\r
+\r
+               while ((cbPath) && (Path[cbPath-1] != 0x5C)) \r
+               {\r
+                       cbPath--;\r
+               }\r
+\r
+               /* Each directory sector has 16 32 byte entries.\r
+               We will now walk thru each sector until we find one\r
+               that is a deleted or empty entry.\r
+               A deleted entry has E5h as its first character in the name.\r
+               An unused entry has 00h as its first character in the name.\r
+               */\r
+\r
+               fFound = 0;\r
+               i = 0;                                                  /* i = sectornum */\r
+               while ((!fFound) && (!erc)) \r
+               {\r
+                       erc = GetDirSectorM(Path, cbPath, abTmpSector,\r
+                                                               512, i++, &LBA, &DirClstr, iJob);\r
+                       if (!erc) \r
+                       {\r
+                               k = 0;\r
+               pDirEnt = abTmpSector;\r
+                               while (k<16) \r
+                               {\r
+                                       if ((pDirEnt->Name[0] == 0xE5) ||\r
+                           (!pDirEnt->Name[0]))\r
+                           {\r
+                               fFound = 1;\r
+                               break;\r
+                                       }\r
+                                       pDirEnt += 32;\r
+                                       k++;\r
+                               }\r
+                       }\r
+               }\r
+               /* When we get here, we have either found an entry or\r
+               we have run out of sectors!  If we run out of sectors\r
+               and this is the root, we error out (RootFull), otherwise\r
+               we extend the directory.\r
+               */\r
+\r
+               if ((erc == ErcNoMatch) && (!SpecDepth))\r
+                       return (ErcRootFull);\r
+\r
+        else if (erc == ErcEOF)\r
+        {              /* reach end of dir! */\r
+\r
+                       /* We must now extend the cluster chain for this\r
+                          directory entry. DirClstr holds the last\r
+                          valid sector of the directory.\r
+                       */\r
+\r
+                 FillData(abTmpSector, 512, 0);\r
+                 spc = Ldrv[Drive].SecPerClstr;                /* sectors per cluster */\r
+                 erc = ExtendClstrChain(Drive, DirClstr, &DirClstr);\r
+                 if (erc)\r
+                       return(erc);\r
+                 LBA = ClsToLBA(DirClstr, Drive);\r
+                 j = LBA;\r
+                 i = spc;\r
+                 erc = 0;\r
+                 while ((i--) && (!erc))\r
+                 {\r
+                       erc = DeviceOp(Ldrv[Drive].DevNum, 2, j++,\r
+                                                  1, abTmpSector);\r
+                 }\r
+                 pDirEnt = abTmpSector;  /* first entry in new sector */\r
+                 fFound = 1;\r
+\r
+                       /* We now have a new cluster on the end of the\r
+                       directory and it is all zeros!. The first sector\r
+                       is pointed to by LBA and abTmpSector is still zeros\r
+                       just as an new dir sector should be with pDirEnt\r
+                       pointing to the first entry.\r
+                       */\r
+               }\r
+\r
+               if ((!erc) && (fFound)) \r
+               {               /* Let's DO IT! */\r
+\r
+                       /* pDirEnt points to the entry we will use and\r
+                          abTmpSector still has the entire sector in it,\r
+                          so we find an empty clstr on the disk, allocate\r
+                          to this file, fill in the rest of the dir\r
+                          entry and we are almost done.\r
+                       */\r
+\r
+                       /* Find a fat buf already in memory for this drive */\r
+                       /* One WILL be here! */\r
+\r
+                       k = 0;\r
+                       CrntClstr = 0;\r
+                       while ((k<nFATBufs) && (!CrntClstr)) \r
+                       {\r
+                               if ((Drive == Fat[k].Drive) && (Fat[k].LastUsed))\r
+                    CrntClstr = Fat[k].iClstrStart;    /* valid cluster */\r
+                               k++;\r
+                       }\r
+\r
+                       if (!CrntClstr)\r
+                               CrntClstr = 2;          /* Can't find it so start at beginning */\r
+\r
+                       iStart = CrntClstr;             /* where we started looking for empties */\r
+\r
+                       fFound = 0;\r
+                       while (!fFound) \r
+                       {\r
+                               ++CrntClstr;            /* next cluster */\r
+                               if (CrntClstr == iStart)\r
+                                       return(ErcDiskFull);\r
+\r
+                               erc = GetClstrValue(CrntClstr, Drive, 0, &ClstrValue, &j);\r
+\r
+                               if ((!erc) && (!ClstrValue))\r
+                                               fFound = 1;     /* found an empty one */\r
+\r
+                               else if (erc == ErcBadFATClstr) \r
+                               { /* off the end */\r
+                                               /* we started AFTER beginning of disk so\r
+                                                 we will go back and look for empties\r
+                                                 from beginning to where we started.\r
+                                               */\r
+\r
+                                       if (iStart > 2)\r
+                                               CrntClstr = 2;\r
+                                       else\r
+                                               return(ErcDiskFull);\r
+                               }\r
+                               else if (erc)\r
+                                               return(erc);\r
+                       }\r
+\r
+                       /* If we got here, we found an empty cluster */\r
+\r
+            CopyData(filename, pDirEnt, 11);\r
+                       if (!fDir)\r
+                               pDirEnt->Attr =\r
+                                       attrib & (READONLY | HIDDEN | SYSTEM | ARCHIVE);\r
+                       else\r
+                pDirEnt->Attr = attrib;\r
+                       GetFATTime(&pDirEnt->Time, &pDirEnt->Date);\r
+                       pDirEnt->StartClstr = CrntClstr;\r
+                       pDirEnt->FileSize = 0;\r
+                       erc = SetClstrValue(CrntClstr, 0xFFFF, Drive, &i);\r
+                       /* Now we write the dir sector back to disk */\r
+                       if (!erc)\r
+                               erc = DeviceOp(Ldrv[Drive].DevNum, 2, LBA,\r
+                                                               1, abTmpSector);\r
+\r
+                       /* If we were creating a directory, we must add\r
+                       the two deafult directory entries . and ..\r
+                       This is done by filling out abTmpSector as\r
+                       the first sector of an empty directory and writing\r
+                       it out to the allocated cluster.\r
+                       We then zewro it out and write it to the rest\r
+                       of the sectors in the new cluster.\r
+                       */\r
+\r
+                       if (fDir)\r
+                       {\r
+                         FillData(abTmpSector, 512, 0);\r
+                         pDirEnt = abTmpSector;  /* first entry in new sector */\r
+\r
+                               /* do the current dir entry (.) */\r
+\r
+                         CopyData(".          ", pDirEnt, 11);\r
+                         pDirEnt->Attr = DIRECTORY;\r
+                         GetFATTime(&pDirEnt->Time, &pDirEnt->Date);\r
+                         pDirEnt->StartClstr = CrntClstr;\r
+                         pDirEnt->FileSize = 0;\r
+\r
+                               /* do the previous current dir entry (.) */\r
+\r
+                         pDirEnt += 32;\r
+                         CopyData("..         ", pDirEnt, 11);\r
+                         pDirEnt->Attr = DIRECTORY;\r
+                         GetFATTime(&pDirEnt->Time, &pDirEnt->Date);\r
+                         pDirEnt->StartClstr = DirClstr;\r
+                         pDirEnt->FileSize = 0;\r
+\r
+                         spc = Ldrv[Drive].SecPerClstr;                /* sectors per cluster */\r
+                         LBA = ClsToLBA(CrntClstr, Drive);\r
+                         j = LBA;\r
+\r
+                         /* Write this sector out to disk */\r
+                         erc = DeviceOp(Ldrv[Drive].DevNum, 2, j++,\r
+                                                        1, abTmpSector);\r
+\r
+                         FillData(abTmpSector, 512, 0); /* zero the rest */\r
+\r
+                         erc = 0;\r
+                         i = spc-1;    /* less one cause we wrote the first */\r
+                         while ((i--) && (!erc))\r
+                         {\r
+                               erc = DeviceOp(Ldrv[Drive].DevNum, 2, j++,\r
+                                                          1, abTmpSector);\r
+                         }\r
+\r
+                       }\r
+                       /* This will write all modified fat sectors */\r
+\r
+                       for (i=0; i<nFATBufs; i++)\r
+                               UpdateFAT(i);\r
+\r
+                       return(erc);\r
+\r
+               }\r
+               else\r
+                       return(erc);\r
+       }\r
+       default: ;\r
+\r
+       } /* switch */\r
+       return (erc);\r
+}\r
+\r
+/*** Delete File ***************************************\r
+ This is Delete File for the MMURTL FAT file system.\r
+ The file must be opened in mode modify which gives\r
+ the caller exclusive access. The file is closed\r
+ even if the Delete fails.\r
+********************************************************/\r
+\r
+static U32 DeleteFileM(long *dHandle)\r
+{\r
+U32 erc, iFCB, i;\r
+U16 iStart;\r
+U8 Drive;\r
+\r
+       erc = ValidateHandle(dHandle, &iFCB);\r
+       if (erc) return erc;\r
+\r
+       Drive = paFCB[iFCB]->Ldrv;                      /* What logical drive are we on? */\r
+\r
+       if (!paFCB[iFCB]->Mode) \r
+       {  /* Modify mode? */\r
+               CloseFileM(dHandle);\r
+               return ErcReadOnly;\r
+       }\r
+       if (paFUB[dHandle]->fStream)\r
+               DeAllocPage(paFUB[dHandle]->pBuf, 1);   /* Free buffer */\r
+\r
+       iStart = paFCB[iFCB]->StartClstr;\r
+       if (iStart) \r
+       {\r
+               erc = TruncClstrChain(Drive, iStart);\r
+               if (!erc)\r
+                       erc = SetClstrValue(iStart, 0, Drive, &i);\r
+       }\r
+\r
+       paFCB[iFCB]->Name[0] = 0xE5;\r
+       UpdateDirEnt(iFCB);                     /* ignore error */\r
+\r
+       /* This means the FS is screwed up. This shouldn't happen... */\r
+       if (!paFCB[iFCB]->nUsers)\r
+               erc = ErcBadFCB;\r
+       else\r
+               paFCB[iFCB]->nUsers--;\r
+\r
+         /* Now we should be able to close it and free the the FUB.\r
+        If the FCB.nUsers flips to 0 it will be free too\r
+         */\r
+\r
+       paFUB[dHandle]->Job = 0;\r
+\r
+       /* This writes all modified fat sectors */\r
+\r
+       for (i=0; i<nFATBufs; i++)\r
+                       UpdateFAT(i);\r
+\r
+       return erc;\r
+}\r
+\r
+/*** Rename File ***************************************\r
+ This is Rename File for the MMURTL FAT file system.\r
+********************************************************/\r
+\r
+static U32 RenameFileM(char *pCrntName, long dcbCrntName,\r
+                char *pNewName, long dcbNewName, U32 iJob)\r
+{\r
+U32 dHandle, erc, erc1, iFCB;\r
+\r
+       erc = OpenFileM(pCrntName, dcbCrntName, 1, 0, &dHandle, iJob);\r
+       if (!erc) \r
+       {\r
+               FDrive1 = FDrive;\r
+               CopyData(FileSpec, FileSpec1, 77);\r
+               SpecDepth1 = SpecDepth;\r
+               erc = ParseName(pNewName, dcbNewName, iJob);\r
+               if (!erc)\r
+                       if ((FDrive1 != FDrive) || (SpecDepth1 != SpecDepth))\r
+                               erc = ErcRenameDrv;                     /* No Rename across drives */\r
+               if (!erc)\r
+                       if (SpecDepth)          /* Compare upper tree */\r
+                               if (CompareNCS(FileSpec, FileSpec1, SpecDepth * 11) != -1)\r
+                                       erc = ErcRenameDir;             /* No Rename across dirs */\r
+               if (!erc) \r
+               { /* OK to rename */\r
+                       iFCB = paFUB[dHandle]->iFCB;            /* FCB for this FUB */\r
+                       CopyData(FileSpec[SpecDepth], &paFCB[iFCB], 11);\r
+                       erc = UpdateDirEnt(iFCB);\r
+               }\r
+               erc1 = CloseFileM(dHandle);\r
+               if (!erc)\r
+                       erc = erc1;\r
+       }\r
+       return (erc);\r
+}\r
+\r
+/*** Create Dir ***************************************\r
+ This is Create Directory for the MMURTL FAT file system.\r
+********************************************************/\r
+\r
+static U32     CreateDirM(char *pPath, long cbPath, long iJob)\r
+{\r
+long erc;\r
+\r
+  erc = CreateFileM(pPath, cbPath, DIRECTORY, iJob);\r
+  return(erc);\r
+\r
+}\r
+/*** Delete Directory ***********************************\r
+ This is Delete Directory for the MMURTL FAT file system.\r
+********************************************************/\r
+\r
+static U32     DeleteDirM(char *pPath, long cbPath, long fAllFiles, long iJob)\r
+{\r
+       pPath = 0;\r
+       cbPath = 0;\r
+       fAllFiles = 0;\r
+       iJob = 0;\r
+}\r
+\r
+\r
+/*******************************************************\r
+ This is the File system task. All file system requests\r
+ end up here to be serviced.\r
+********************************************************/\r
+\r
+static void FSysTask(void)\r
+{\r
+U32 FMsg[2], merc, erc, i;\r
+U16 i16;\r
+\r
+while (1) \r
+{\r
+  erc = WaitMsg(FSysExch, FMsg);\r
+  if (!erc)\r
+  {\r
+\r
+       pRQB = FMsg[0];         /* first DD in Msg is pointer to RQBlock */\r
+\r
+       switch (pRQB->ServiceCode)\r
+       {\r
+               case 0 :                /* JobAbort - CLOSE ALL FILES FOR THIS JOB! */\r
+                       i = 4;\r
+                       while (i<nFUBs)\r
+                       {\r
+                   if (paFUB[i]->Job == pRQB->dData0)\r
+                   {\r
+                                       CloseFileM(i);\r
+                               }\r
+                               ++i;    /* next FUB */\r
+                       }\r
+\r
+                       erc = 0;\r
+                       break;\r
+               case 1 :                /* OpenFile */\r
+                       erc = OpenFileM(pRQB->pData1,       /* pFilename */\r
+                                       pRQB->cbData1,      /* dcbFilename */\r
+                                                   pRQB->dData0,       /* Mode */\r
+                                                   pRQB->dData1,       /* Type */\r
+                                                   pRQB->pData2,       /* pdHandleRet */\r
+                                                   pRQB->RqOwnerJob);  /* iJob Owner */\r
+                       break;\r
+               case 2 :                /* CloseFile */\r
+                       erc = CloseFileM(pRQB->dData0);     /* Handle */\r
+                       break;\r
+               case 3 :                /* ReadBlock */\r
+                       erc = ReadBlockM(pRQB->dData0,       /* Handle */\r
+                                        pRQB->pData1,       /* pDataRet */\r
+                                        pRQB->cbData1,      /* nByes */\r
+                                                    pRQB->dData1,       /* dLFA */\r
+                                                    pRQB->pData2,       /* pdnBytesRet */\r
+                                                    0);                 /* NOT internal */\r
+                       break;\r
+               case 4 :                /* WriteBlock */\r
+                       erc = WriteBlockM(pRQB->dData0,      /* Handle */\r
+                                         pRQB->pData1,      /* pData */\r
+                                         pRQB->cbData1,     /* nBytes */\r
+                                                     pRQB->dData1,      /* dLFA */\r
+                                                     pRQB->pData2);     /* pdBytesRet */\r
+                       break;\r
+               case 5 :                /* ReadBytes */\r
+                       erc = ReadBytesM(pRQB->dData0,       /* Handle */\r
+                                        pRQB->pData1,       /* pDataRet */\r
+                                        pRQB->cbData1,      /* nBytes */\r
+                                                    pRQB->pData2);      /* pdnBytesRet */\r
+                       break;\r
+               case 6 :                /* WriteBytes */\r
+                       erc = WriteBytesM(pRQB->dData0,      /* Handle */\r
+                                         pRQB->pData1,      /* pData */\r
+                                         pRQB->cbData1,     /* nBytes */\r
+                                                     pRQB->pData2);     /* pdnBytesRet */\r
+                       break;\r
+               case 7 :                /* GetFileLFA */\r
+                       erc = GetFileLFAM(pRQB->dData0,      /* Handle */\r
+                                         pRQB->pData1);     /* pdLFARet */\r
+                       break;\r
+               case 8 :                /* SetFileLFA */\r
+                       erc = SetFileLFAM(pRQB->dData0,      /* Handle */\r
+                                        pRQB->dData1);     /* dNewLFA */\r
+                       break;\r
+               case 9 :                /* GetFileSize */\r
+                       erc = GetFileSizeM(pRQB->dData0,     /* Handle */\r
+                                         pRQB->pData1);    /* pdSizeRet */\r
+                       break;\r
+               case 10 :               /* SetFileSize */\r
+                       erc = SetFileSizeM(pRQB->dData0,     /* Handle */\r
+                                          pRQB->dData1);    /*  dSize */\r
+                       break;\r
+               case 11 :               /* CreateFile */\r
+                       erc = CreateFileM(pRQB->pData1,      /* pFilename  */\r
+                                         pRQB->cbData1,     /* cbFilename */\r
+                                         pRQB->dData0,      /* Attributes */\r
+                                                         pRQB->RqOwnerJob); /* iJob Owner */\r
+                       break;\r
+               case 12 :               /* RenameFile */\r
+                       erc = RenameFileM(pRQB->pData1,      /* pCrntName  */\r
+                                         pRQB->cbData1,     /* cbCrntName */\r
+                                         pRQB->pData2,      /* pNewName */\r
+                                         pRQB->cbData2,     /* dcbNewName */\r
+                                         pRQB->RqOwnerJob); /* JobNum */\r
+                       break;\r
+               case 13 :               /* DeleteFile */\r
+                       erc = DeleteFileM(pRQB->dData0);     /* Handle  */\r
+                       break;\r
+               case 14 :               /* CreateDirectory */\r
+                       erc = CreateDirM(pRQB->pData1,      /* pPath */\r
+                                        pRQB->cbData1,     /* cbPath */\r
+                                        pRQB->RqOwnerJob); /* JobNum */\r
+                       break;\r
+               case 15 :               /* DeleteDirectory */\r
+                       erc = DeleteDirM(pRQB->pData1,      /* pPath */\r
+                                        pRQB->cbData1,     /* cbPath */\r
+                                        pRQB->dData0,      /* fAllFiles */\r
+                                        pRQB->RqOwnerJob); /* JobNum */\r
+                       break;\r
+               case 16 :               /* GetDirSector */\r
+                       erc = GetDirSectorM(pRQB->pData1,      /* pPath    */\r
+                                           pRQB->cbData1,     /* cbPath   */\r
+                                                           pRQB->pData2,      /* pSectRet */\r
+                                           pRQB->cbData2,     /* cbRetMax */\r
+                                           pRQB->dData0,      /* SectNum */\r
+                                           &i,                            /* for LBARet */\r
+                                           &i16,                          /* for DirClstr */\r
+                                           pRQB->RqOwnerJob); /* JobNum   */\r
+                       break;\r
+               default :\r
+                       erc = ErcBadSvcCode;\r
+                       break;\r
+       }\r
+\r
+       merc = Respond(FMsg[0], erc);\r
+\r
+  }\r
+}              /* forever */\r
+}\r
+\r
+/***************** PUBLIC BLOCKING CALLS FOR FILESYSM *************\r
+ These calls query the TSS Exhange and use it to make Requests\r
+ to the file system service on the behalf of the caller.\r
+ These calls are fully reentrant! No static data!\r
+*******************************************************************/\r
+\r
+U32 far _OpenFile(char *pFilename,\r
+                            long dcbFilename,\r
+                                long Mode,\r
+                                long Type,\r
+                                long *pdHandleRet)\r
+{\r
+long erc, exch, rqhndl, i, msg[2];\r
+       if (dcbFilename == 3)\r
+       {\r
+               if (CompareNCS(pFilename, "NUL" , 3) == -1)\r
+               {\r
+            *pdHandleRet = 0;\r
+            return(0);\r
+               }\r
+               else if (CompareNCS(pFilename, "KBD" , 3) == -1)\r
+               {\r
+            *pdHandleRet = 1;\r
+            return(0);\r
+               }\r
+               else if (CompareNCS(pFilename, "VID" , 3) == -1)\r
+               {\r
+            *pdHandleRet = 2;\r
+            return(0);\r
+               }\r
+               else if (CompareNCS(pFilename, "LPT" , 3) == -1)\r
+               {\r
+                       erc = DeviceOp(3, 10, 0,    0,  &i);   /* 10=Open */\r
+                       if (!erc)\r
+                   *pdHandleRet = 3;\r
+            return(erc);\r
+               }\r
+       }\r
+\r
+       GetTSSExch(&exch);              /* No error will come back! */\r
+    erc = Request(fsysname, 1, exch, &rqhndl,\r
+                  1,                                                   /* 1 Send ptr */\r
+                  pFilename, dcbFilename,\r
+                  pdHandleRet, 4,\r
+                  Mode, Type, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _CloseFile(unsigned long dHandle)\r
+{\r
+long erc, exch, rqhndl, i, msg[2];\r
+\r
+       if (dHandle < 3)\r
+           return(0);\r
+       else if (dHandle == 3)\r
+       {\r
+               erc = DeviceOp(3,  11,   0,   0,  &i);    /* 11 = Close */\r
+               return(erc);\r
+       }\r
+\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 2, exch, &rqhndl,\r
+                   0,                                                          /* 0 Send ptr */\r
+                   0, 0,\r
+                   0, 0,\r
+                   dHandle, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _ReadBlock(long dHandle,\r
+                  char *pDataRet,\r
+                  long nBlks,\r
+                                 long dLFA,\r
+                                 long *pdnBlksRet)\r
+{\r
+long erc, exch, rqhndl, msg[2], i;\r
+       if (dHandle < 4)\r
+               return(ErcNotSupported);\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 3, exch, &rqhndl,\r
+                  1,                                                   /* 1 Send ptr */\r
+                  pDataRet, nBlks,\r
+                  pdnBlksRet, 4,\r
+                  dHandle, dLFA, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc)\r
+               return(erc);\r
+       if(msg[1]) \r
+       {\r
+               DeviceStat(10, &FDDevStat, 64, &i);\r
+       }\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _WriteBlock(long dHandle,\r
+                   char *pData,\r
+                   long nBlks,\r
+                   long dLFA,\r
+                   long *pdnBlksRet)\r
+\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       if (dHandle < 4)\r
+               return(ErcNotSupported);\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 4, exch, &rqhndl,\r
+                   1,                                                          /* 1 Send ptr */\r
+                   pData, nBlks,\r
+                   pdnBlksRet, 4,\r
+                   dHandle, dLFA, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _ReadBytes(long dHandle,\r
+                                 char *pDataRet,\r
+                                 long nBytes,\r
+                                 long *pdnBytesRet)\r
+{\r
+long erc, exch, rqhndl, msg[2], i;\r
+       if (dHandle == 0)\r
+       {\r
+        *pdnBytesRet = 0;\r
+               return(0);\r
+       }\r
+       else if (dHandle == 1)\r
+       {\r
+               i = 0;\r
+               while (i < nBytes)\r
+               {\r
+                       ReadKbd(*pDataRet++, 1);\r
+                       i++;\r
+               }\r
+           *pdnBytesRet = i;\r
+               return(0);\r
+       }\r
+       else if ((dHandle == 2) || (dHandle == 3))\r
+       {\r
+        *pdnBytesRet = 0;\r
+               return(ErcWriteOnly);\r
+       }\r
+\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 5, exch, &rqhndl,\r
+                   1,                                                          /* 1 Send ptr */\r
+                   pDataRet, nBytes,\r
+                   pdnBytesRet, 4,\r
+                   dHandle, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _WriteBytes(long dHandle,\r
+                   char *pData,\r
+                   long nBytes,\r
+                   long *pdnBytesRet)\r
+\r
+{\r
+long erc, exch, rqhndl, VidAttr, msg[2];\r
+\r
+       if (dHandle == 0) \r
+       {\r
+        *pdnBytesRet = nBytes;\r
+               return(0);\r
+       }\r
+       else if (dHandle == 1)\r
+       {\r
+        *pdnBytesRet = 0;\r
+               return(ErcReadOnly);\r
+       }\r
+       else if (dHandle == 2)\r
+       {\r
+               GetNormVid(&VidAttr);\r
+               TTYOut(pData, nBytes, VidAttr);\r
+           *pdnBytesRet = nBytes;\r
+               return(0);\r
+       }\r
+       else if (dHandle == 3)\r
+       {\r
+               erc = DeviceOp(3, 2, 0,  nBytes, pData);   /* 2 = CmdWriteRec */\r
+               return(erc);\r
+       }\r
+\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 6, exch, &rqhndl,\r
+                   1,                                                          /* 1 Send ptr */\r
+                   pData, nBytes,\r
+                   pdnBytesRet, 4,\r
+                   dHandle, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far         _GetFileLFA(long dHandle,\r
+                    long *pdLFARet)\r
+\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       if (dHandle < 4)\r
+               return(ErcEOF);\r
+\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 7, exch, &rqhndl,\r
+                   0,                                                          /* 0 Send ptrs */\r
+                   pdLFARet, 4,\r
+                   0, 0,\r
+                   dHandle, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _SetFileLFA(long dHandle,\r
+                   long dNewLFA)\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       if (dHandle < 4)\r
+               return(ErcNotSupported);\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 8, exch, &rqhndl,\r
+                   0,                                                          /* 0 Send ptrs */\r
+                   0, 0,\r
+                   0, 0,\r
+                   dHandle, dNewLFA, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _GetFileSize(long dHandle,\r
+                    long *pdSizeRet)\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       if (dHandle < 4)\r
+               return(ErcNotSupported);\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 9, exch, &rqhndl,\r
+                   0,                                                          /* 0 Send ptrs */\r
+                   pdSizeRet, 4,\r
+                   0, 0,\r
+                   dHandle, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _SetFileSize(long dHandle,\r
+                    long dSize)\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       if (dHandle < 4)\r
+               return(ErcNotSupported);\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 10, exch, &rqhndl,\r
+                   0,                                                          /* 0 Send ptrs */\r
+                   0, 0,\r
+                   0, 0,\r
+                   dHandle, dSize, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _CreateFile(char *pFilename,\r
+                                  long cbFilename,\r
+                                  long Attribute)\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 11, exch, &rqhndl,\r
+                   1,                                                          /* 1 Send ptrs */\r
+                   pFilename, cbFilename,\r
+                   0, 0,\r
+                   Attribute, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _RenameFile(char *pCrntName,\r
+                                  long cbCrntName,\r
+                                  char *pNewName,\r
+                                  long cbNewName)\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 12, exch, &rqhndl,\r
+                   2,                                                          /* 2 Send ptrs */\r
+                   pCrntName, cbCrntName,\r
+                   pNewName, cbNewName,\r
+                   0, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _DeleteFile(long dHandle)\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       if (dHandle < 4)\r
+               return(ErcNotSupported);\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 13, exch, &rqhndl,\r
+                   0,                                                          /* 0 Send ptrs */\r
+                   0, 0,\r
+                   0, 0,\r
+                   dHandle, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _CreateDir(char *pPath,\r
+                                  long cbPath)\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 14, exch, &rqhndl,\r
+                   1,                                                          /* 1 Send ptrs */\r
+                   pPath, cbPath,\r
+                   0, 0,\r
+                   0, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _DeleteDir(char *pPath,\r
+                                  long cbPath,\r
+                                  long fAllFiles)\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 15, exch, &rqhndl,\r
+                   1,                                                          /* 1 Send ptrs */\r
+                   pPath, cbPath,\r
+                   0, 0,\r
+                   fAllFiles, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/*************************************/\r
+U32 far _GetDirSector(char *pPathSpec,\r
+                                    long cbPathSpec,\r
+                                        char *pEntRet,\r
+                                    long cbEntRet,\r
+                                    long SectNum)\r
+{\r
+long erc, exch, rqhndl, msg[2];\r
+       GetTSSExch(&exch);\r
+    erc = Request(fsysname, 16, exch, &rqhndl,\r
+                   1,                                                          /* 1 Send ptrs */\r
+                   pPathSpec, cbPathSpec,\r
+                   pEntRet, cbEntRet,\r
+                   SectNum, 0, 0);\r
+       if (!erc) erc = WaitMsg(exch, msg);\r
+       if (erc) return(erc);\r
+       return(msg[1]);\r
+}\r
+\r
+/********************************************\r
+ Initialization Routine for the File system.\r
+ This is called        the Monitor where any errors\r
+ will be reported.\r
+**********************************************/\r
+\r
+U32 InitFS(void)\r
+{\r
+U32 erc, i, j;\r
+U8 *pMem;\r
+\r
+  /* Allocate FAT buffers and initialize FAT related structures.\r
+     We will allocate 24Kb worth of buffers (6 Pages, 16 buffers).\r
+   */\r
+\r
+  Fat[0].pBuf = FatBufA;  /* floppy fat buffers */\r
+\r
+  erc = AllocOSPage(2, &pMem);\r
+\r
+  if (!erc)                             /* hard disk fat buffers from allocated mem */\r
+    for (i=1; i<nFATBufs; i++)\r
+    {\r
+         Fat[i].pBuf = pMem;                   /* Make pBuf point to each buffer */\r
+         pMem += 512;                                  /* Next buffer in allocated memory */\r
+       }\r
+\r
+  /* Allocate and initialize FCBs and FUBs. This is enough FCBs and\r
+     FUBS for 128 openfiles.  (Good enough to start...)\r
+  */\r
+\r
+  if (!erc)\r
+         erc = AllocOSPage(2, &paFCB); /* a pointer to allocated FCBs. */\r
+  if (!erc)\r
+         FillData(paFCB, 8192, 0);\r
+  if (!erc)\r
+         erc = AllocOSPage(1, &paFUB); /* a pointer to allocated FUBs. */\r
+  if (!erc)\r
+         FillData(paFUB, 4096, 0);\r
+\r
+  if (!erc)    erc = read_PE();        /* reads partition tables */\r
+\r
+  if (!erc)    erc = SetDriveGeometry(12);\r
+\r
+  if (!erc)    \r
+  {\r
+       erc = SetDriveGeometry(13);\r
+    if (erc == 663) erc = 0;           /* may be invalid drive */\r
+  }\r
+\r
+  StatFloppy(0);\r
+  StatFloppy(1);\r
+\r
+       /* read all logical drive boot sectors */\r
+\r
+  if (!erc) \r
+  {\r
+       for (i=0; i< nLDrvs; i++) \r
+       {\r
+         if (Ldrv[i].DevNum != 0xff) \r
+         {\r
+                       read_BS(i);\r
+         }\r
+       }\r
+  }\r
+\r
+  for (i=0; i<nLDrvs; i++)\r
+       if (Ldrv[i].DevNum != 0xff) \r
+       {\r
+       j=12;\r
+         if (Ldrv[i].fFAT16)\r
+                 j=16;\r
+         xprintf("%c: Heads %d, Sec/Trk %d, Sec/Clstr %d, Dev %d, FAT%d \r\n",\r
+                       i+0x41,\r
+                       Ldrv[i].nHeads,\r
+                       Ldrv[i].nSecPerTrk,\r
+                       Ldrv[i].SecPerClstr,\r
+                       Ldrv[i].DevNum,\r
+                       j);\r
+       }\r
+\r
+  if (!erc)\r
+         erc = AllocExch(&FSysExch);\r
+\r
+       /* Start the filesystem task at a decently high priority (5).\r
+       This should be higher than the Monitor status task and even the\r
+       Keyboard. */\r
+\r
+  if (!erc)\r
+       erc = SpawnTask(&FSysTask, 5, 0, &FSysStack[511], 1);\r
+\r
+  if (!erc)\r
+   erc = RegisterSvc(fsysname, FSysExch);\r
+\r
+return erc;\r
+\r
+}\r