/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    fatboot.c

Abstract:

    This module implements the FAT boot file system used by the operating
    system loader.

Author:

    Gary Kimura (garyki) 29-Aug-1989

Revision History:

--*/

#include "fwp.h"
#include "stdio.h"
//#include "mrcf.h"

//BOOTFS_INFO FatBootFsInfo={L"fastfat"};

//
//  Conditional debug print routine
//

#ifdef FATBOOTDBG

#define FatDebugOutput(X,Y,Z) {                                      \
    if (BlConsoleOutDeviceId) {                                      \
        CHAR _b[128];                                                \
        ULONG _c;                                                    \
        sprintf(&_b[0], X, Y, Z);                                    \
        ArcWrite(BlConsoleOutDeviceId, &_b[0], strlen(&_b[0]), &_c); \
    }                                                                \
}

#else

#define FatDebugOutput(X,Y,Z) {NOTHING;}

#endif // FATBOOTDBG


//
//  Low level disk I/O procedure prototypes
//

ARC_STATUS
FatDiskRead (
    IN ULONG DeviceId,
    IN LBO Lbo,
    IN ULONG ByteCount,
    IN PVOID Buffer
    );

ARC_STATUS
FatDiskWrite (
    IN ULONG DeviceId,
    IN LBO Lbo,
    IN ULONG ByteCount,
    IN PVOID Buffer
    );

//
//  VOID
//  DiskRead (
//      IN ULONG DeviceId,
//      IN LBO Lbo,
//      IN ULONG ByteCount,
//      IN PVOID Buffer
//      IN BOOLEAN IsDoubleSpace
//      );
//

#ifdef DBLSPACE_LEGAL
#define DiskRead(A,B,C,D,ISDBLS) { ARC_STATUS _s;                                       \
    if (!ISDBLS) {                                                                      \
        if ((_s = FatDiskRead(A,B,C,D)) != ESUCCESS) { return _s; }                     \
    } else {                                                                            \
        ARC_STATUS DblsReadVfp(ULONG FileId, LBO Lbo, ULONG ByteCount, PUCHAR Buffer ); \
        if ((_s = DblsReadVfp(A,B,C,(PUCHAR)D)) != ESUCCESS) { return _s; }             \
    }                                                                                   \
}
#else
#define DiskRead(A,B,C,D,ignored) { ARC_STATUS _s;               \
    if ((_s = FatDiskRead(A,B,C,D)) != ESUCCESS) { return _s; }  \
}
#endif // def DBLSPACE_LEGAL

#define DiskWrite(A,B,C,D) { ARC_STATUS _s;                      \
    if ((_s = FatDiskWrite(A,B,C,D)) != ESUCCESS) { return _s; } \
}


//
//  Cluster/Index routines
//

typedef enum _CLUSTER_TYPE {
    FatClusterAvailable,
    FatClusterReserved,
    FatClusterBad,
    FatClusterLast,
    FatClusterNext
} CLUSTER_TYPE;

CLUSTER_TYPE
FatInterpretClusterType (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN FAT_ENTRY Entry
    );

ARC_STATUS
FatLookupFatEntry (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId,
    IN FAT_ENTRY FatIndex,
    OUT PFAT_ENTRY FatEntry,
    IN BOOLEAN IsDoubleSpace
    );

ARC_STATUS
FatSetFatEntry (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId,
    IN FAT_ENTRY FatIndex,
    IN FAT_ENTRY FatEntry
    );

ARC_STATUS
FatFlushFatEntries (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId
    );

LBO
FatIndexToLbo (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN FAT_ENTRY FatIndex
    );

#define LookupFatEntry(A,B,C,D,E) { ARC_STATUS _s;                      \
    if ((_s = FatLookupFatEntry(A,B,C,D,E)) != ESUCCESS) { return _s; } \
}

#define SetFatEntry(A,B,C,D) { ARC_STATUS _s;                      \
    if ((_s = FatSetFatEntry(A,B,C,D)) != ESUCCESS) { return _s; } \
}

#define FlushFatEntries(A,B) { ARC_STATUS _s;                      \
    if ((_s = FatFlushFatEntries(A,B)) != ESUCCESS) { return _s; } \
}


//
//  Directory routines
//

ARC_STATUS
FatSearchForDirent (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId,
    IN FAT_ENTRY DirectoriesStartingIndex,
    IN PFAT8DOT3 FileName,
    OUT PDIRENT Dirent,
    OUT PLBO Lbo,
    IN BOOLEAN IsDoubleSpace
    );

ARC_STATUS
FatCreateDirent (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId,
    IN FAT_ENTRY DirectoriesStartingIndex,
    IN PDIRENT Dirent,
    OUT PLBO Lbo
    );

VOID
FatSetDirent (
    IN PFAT8DOT3 FileName,
    IN OUT PDIRENT Dirent,
    IN UCHAR Attributes
    );

#define SearchForDirent(A,B,C,D,E,F,G) { ARC_STATUS _s;                      \
    if ((_s = FatSearchForDirent(A,B,C,D,E,F,G)) != ESUCCESS) { return _s; } \
}

#define CreateDirent(A,B,C,D,E) { ARC_STATUS _s;                      \
    if ((_s = FatCreateDirent(A,B,C,D,E)) != ESUCCESS) { return _s; } \
}


//
//  Allocation and mcb routines
//

ARC_STATUS
FatLoadMcb (
    IN ULONG FileId,
    IN VBO StartingVbo,
    IN BOOLEAN IsDoubleSpace
    );

ARC_STATUS
FatVboToLbo (
    IN ULONG FileId,
    IN VBO Vbo,
    OUT PLBO Lbo,
    OUT PULONG ByteCount,
    IN BOOLEAN IsDoubleSpace
    );

ARC_STATUS
FatIncreaseFileAllocation (
    IN ULONG FileId,
    IN ULONG ByteSize
    );

ARC_STATUS
FatTruncateFileAllocation (
    IN ULONG FileId,
    IN ULONG ByteSize
    );

ARC_STATUS
FatAllocateClusters (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId,
    IN ULONG ClusterCount,
    IN FAT_ENTRY Hint,
    OUT PFAT_ENTRY AllocatedEntry
    );

#define LoadMcb(A,B,C) { ARC_STATUS _s;                      \
    if ((_s = FatLoadMcb(A,B,C)) != ESUCCESS) { return _s; } \
}

#define VboToLbo(A,B,C,D) { ARC_STATUS _s;                            \
    if ((_s = FatVboToLbo(A,B,C,D,FALSE)) != ESUCCESS) { return _s; } \
}

#define IncreaseFileAllocation(A,B) { ARC_STATUS _s;                      \
    if ((_s = FatIncreaseFileAllocation(A,B)) != ESUCCESS) { return _s; } \
}

#define TruncateFileAllocation(A,B) { ARC_STATUS _s;                      \
    if ((_s = FatTruncateFileAllocation(A,B)) != ESUCCESS) { return _s; } \
}

#define AllocateClusters(A,B,C,D,E) { ARC_STATUS _s;                      \
    if ((_s = FatAllocateClusters(A,B,C,D,E)) != ESUCCESS) { return _s; } \
}


//
//  Miscellaneous routines
//

VOID
FatFirstComponent (
    IN OUT PSTRING String,
    OUT PFAT8DOT3 FirstComponent
    );

#define AreNamesEqual(X,Y) (                                                      \
    ((*(X))[0]==(*(Y))[0]) && ((*(X))[1]==(*(Y))[1]) && ((*(X))[2]==(*(Y))[2]) && \
    ((*(X))[3]==(*(Y))[3]) && ((*(X))[4]==(*(Y))[4]) && ((*(X))[5]==(*(Y))[5]) && \
    ((*(X))[6]==(*(Y))[6]) && ((*(X))[7]==(*(Y))[7]) && ((*(X))[8]==(*(Y))[8]) && \
    ((*(X))[9]==(*(Y))[9]) && ((*(X))[10]==(*(Y))[10])                            \
)

#define ToUpper(C) ((((C) >= 'a') && ((C) <= 'z')) ? (C) - 'a' + 'A' : (C))

#define FlagOn(Flags,SingleFlag)        ((Flags) & (SingleFlag))
#define BooleanFlagOn(Flags,SingleFlag) ((BOOLEAN)(((Flags) & (SingleFlag)) != 0))
#define SetFlag(Flags,SingleFlag)       { (Flags) |= (SingleFlag); }
#define ClearFlag(Flags,SingleFlag)     { (Flags) &= ~(SingleFlag); }

#define FatFirstFatAreaLbo(B) ( (B)->ReservedSectors * (B)->BytesPerSector )

#define Minimum(X,Y) ((X) < (Y) ? (X) : (Y))
#define Maximum(X,Y) ((X) < (Y) ? (Y) : (X))

//
//  The following types and macros are used to help unpack the packed and
//  misaligned fields found in the Bios parameter block
//

typedef union _UCHAR1 { UCHAR  Uchar[1]; UCHAR  ForceAlignment; } UCHAR1, *PUCHAR1;
typedef union _UCHAR2 { UCHAR  Uchar[2]; USHORT ForceAlignment; } UCHAR2, *PUCHAR2;
typedef union _UCHAR4 { UCHAR  Uchar[4]; ULONG  ForceAlignment; } UCHAR4, *PUCHAR4;

//
//  This macro copies an unaligned src byte to an aligned dst byte
//

#define CopyUchar1(Dst,Src) {                                \
    __builtin_memcpy((Dst), (Src), sizeof(UCHAR)); \
    }

//
//  This macro copies an unaligned src word to an aligned dst word
//

#define CopyUchar2(Dst,Src) {                                \
    __builtin_memcpy((Dst), (Src), sizeof(SHORT)); \
    }

//
//  This macro copies an unaligned src longword to an aligned dsr longword
//

#define CopyUchar4(Dst,Src) {                                \
    __builtin_memcpy((Dst), (Src), sizeof(ULONG)); \
    }

//
// DirectoryEntry routines
//

VOID
FatDirToArcDir (
    IN PDIRENT FatDirent,
    OUT PDIRECTORY_ENTRY ArcDirent
    );


//
// Define global data.
//

//
// File entry table - This is a structure that provides entry to the FAT
//      file system procedures. It is exported when a FAT file structure
//      is recognized.
//

BL_DEVICE_ENTRY_TABLE FatDeviceEntryTable;


PBL_DEVICE_ENTRY_TABLE
IsFatFileStructure (
    IN ULONG DeviceId,
    IN PVOID StructureContext
    )

/*++

Routine Description:

    This routine determines if the partition on the specified channel
    contains a FAT file system volume.

Arguments:

    DeviceId - Supplies the file table index for the device on which
        read operations are to be performed.

    StructureContext - Supplies a pointer to a FAT file structure context.

Return Value:

    A pointer to the FAT entry table is returned if the partition is
    recognized as containing a FAT volume.  Otherwise, NULL is returned.

--*/

{
    PPACKED_BOOT_SECTOR BootSector;
    UCHAR Buffer[sizeof(PACKED_BOOT_SECTOR)+256];

    PFAT_STRUCTURE_CONTEXT FatStructureContext;

    FatDebugOutput("IsFatFileStructure\r\n", 0, 0);

    //
    //  Clear the file system context block for the specified channel and
    //  establish a pointer to the context structure that can be used by other
    //  routines
    //

    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)StructureContext;
    RtlZeroMemory(FatStructureContext, sizeof(FAT_STRUCTURE_CONTEXT));

    //
    //  Setup and read in the boot sector for the potential fat partition
    //

    BootSector = (PPACKED_BOOT_SECTOR)ALIGN_BUFFER( &Buffer[0] );

    if (FatDiskRead(DeviceId, 0, sizeof(PACKED_BOOT_SECTOR), BootSector) != ESUCCESS) {

        return NULL;
    }

    //
    //  Unpack the Bios parameter block
    //

    FatUnpackBios(&FatStructureContext->Bpb, &BootSector->PackedBpb);

    //
    //  Check if it is fat
    //

    if ((BootSector->Jump[0] != 0xeb) &&
        (BootSector->Jump[0] != 0xe9)) {

        return NULL;

    } else if ((FatStructureContext->Bpb.BytesPerSector !=  128) &&
               (FatStructureContext->Bpb.BytesPerSector !=  256) &&
               (FatStructureContext->Bpb.BytesPerSector !=  512) &&
               (FatStructureContext->Bpb.BytesPerSector != 1024)) {

        return NULL;

    } else if ((FatStructureContext->Bpb.SectorsPerCluster !=  1) &&
               (FatStructureContext->Bpb.SectorsPerCluster !=  2) &&
               (FatStructureContext->Bpb.SectorsPerCluster !=  4) &&
               (FatStructureContext->Bpb.SectorsPerCluster !=  8) &&
               (FatStructureContext->Bpb.SectorsPerCluster != 16) &&
               (FatStructureContext->Bpb.SectorsPerCluster != 32) &&
               (FatStructureContext->Bpb.SectorsPerCluster != 64) &&
               (FatStructureContext->Bpb.SectorsPerCluster != 128)) {

        return NULL;

    } else if (FatStructureContext->Bpb.ReservedSectors == 0) {

        return NULL;

    } else if (FatStructureContext->Bpb.Fats == 0) {

        return NULL;

    } else if (FatStructureContext->Bpb.RootEntries == 0) {

        return NULL;

    } else if (((FatStructureContext->Bpb.Sectors == 0) && (FatStructureContext->Bpb.LargeSectors == 0)) ||
               ((FatStructureContext->Bpb.Sectors != 0) && (FatStructureContext->Bpb.LargeSectors != 0))) {

        return NULL;

    } else if (FatStructureContext->Bpb.SectorsPerFat == 0) {

        return NULL;

    } else if ((FatStructureContext->Bpb.Media != 0xf0) &&
               (FatStructureContext->Bpb.Media != 0xf8) &&
               (FatStructureContext->Bpb.Media != 0xf9) &&
               (FatStructureContext->Bpb.Media != 0xfc) &&
               (FatStructureContext->Bpb.Media != 0xfd) &&
               (FatStructureContext->Bpb.Media != 0xfe) &&
               (FatStructureContext->Bpb.Media != 0xff)) {

        return NULL;
    }

    //
    //  Initialize the file entry table and return the address of the table.
    //

    FatDeviceEntryTable.Open  = FatOpen;
    FatDeviceEntryTable.Close = FatClose;
    FatDeviceEntryTable.Read  = FatRead;
    FatDeviceEntryTable.Seek  = FatSeek;
    FatDeviceEntryTable.Write = FatWrite;
    FatDeviceEntryTable.GetFileInformation = FatGetFileInformation;
    FatDeviceEntryTable.SetFileInformation = FatSetFileInformation;
    FatDeviceEntryTable.Rename = FatRename;
    FatDeviceEntryTable.GetDirectoryEntry   = FatGetDirectoryEntry;
    //FatDeviceEntryTable.BootFsInfo = &FatBootFsInfo;


    return &FatDeviceEntryTable;
}

ARC_STATUS
FatClose (
    IN ULONG FileId
    )

/*++

Routine Description:

    This routine closes the file specified by the file id.

Arguments:

    FileId - Supplies the file table index.

Return Value:

    ESUCCESS if returned as the function value.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    PFAT_STRUCTURE_CONTEXT FatStructureContext;
    ULONG DeviceId;

    FatDebugOutput("FatClose\r\n", 0, 0);

    //
    //  Load our local variables
    //

    FileTableEntry = &BlFileTable[FileId];
    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
    DeviceId = FileTableEntry->DeviceId;

    //
    //  Mark the file closed
    //

    BlFileTable[FileId].Flags.Open = 0;

    //
    //  Check if the fat is dirty and flush it out if it is.
    //

    if (FatStructureContext->CachedFatDirty) {

        FlushFatEntries( FatStructureContext, DeviceId );
    }

    //
    //  Check if the current mcb is for this file and if it is then zero it out.
    //  By setting the file id for the mcb to be the table size we guarantee that
    //  we've just set it to an invalid file id.
    //

    if (FatStructureContext->FileId == FileId) {

        FatStructureContext->FileId = BL_FILE_TABLE_SIZE;
        FatStructureContext->Mcb.InUse = 0;
    }

    return ESUCCESS;
}


ARC_STATUS
FatGetDirectoryEntry (
    IN ULONG FileId,
    IN DIRECTORY_ENTRY *DirEntry,
    IN ULONG NumberDir,
    OUT PULONG CountDir
    )

/*++

Routine Description:

    This routine implements the GetDirectoryEntry operation for the
    FAT file system.

Arguments:

    FileId - Supplies the file table index.

    DirEntry - Supplies a pointer to a directory entry structure.

    NumberDir - Supplies the number of directory entries to read.

    Count - Supplies a pointer to a variable to receive the number
            of entries read.

Return Value:

    ESUCCESS is returned if the read was successful, otherwise
    an error code is returned.

--*/

{
    //
    // define local variables
    //

    ARC_STATUS Status;                 // ARC status
    ULONG Count = 0;                   // # of bytes read
    ULONG Position;                    // file position
    PFAT_FILE_CONTEXT pContext;        // FAT file context
    ULONG RunByteCount = 0;            // max sequential bytes
    ULONG RunDirCount;                 // max dir entries to read per time
    ULONG i;                           // general index
    PDIRENT FatDirEnt;                 // directory entry pointer
    UCHAR Buffer[ 16 * sizeof(DIRENT) + 32 ];
    LBO Lbo;
    BOOLEAN EofDir = FALSE;            // not end of file

    //
    // initialize local variables
    //

    pContext = &BlFileTable[ FileId ].u.FatFileContext;
    FatDirEnt = (PDIRENT)ALIGN_BUFFER( &Buffer[0] );

    //
    // if not directory entry, exit with error
    //

    if ( !FlagOn(pContext->Dirent.Attributes, FAT_DIRENT_ATTR_DIRECTORY) ) {

        return EBADF;
    }

    //
    // Initialize the output count to zero
    //

    *CountDir = 0;

    //
    // if NumberDir is zero, return ESUCCESS.
    //

    if ( !NumberDir ) {

        return ESUCCESS;
    }

    //
    // read one directory at a time.
    //

    do {

        //
        // save position
        //

        Position = BlFileTable[ FileId ].Position.LowPart;

        //
        //  Lookup the corresponding Lbo and run length for the current position
        //

        if ( !RunByteCount ) {

            if (Status = FatVboToLbo( FileId, Position, &Lbo, &RunByteCount, FALSE )) {

                if ( Status == EINVAL ) {

                    break;                      // eof has been reached

                } else {

                    return Status;              // I/O error
                }
            }
        }

        //
        // validate the # of bytes readable in sequance (exit loop if eof)
        // the block is always multiple of a directory entry size.
        //

        if ( !(RunDirCount = Minimum( RunByteCount/sizeof(DIRENT), 16)) ) {

            break;
        }

        //
        //  issue the read
        //

        if ( Status = FatDiskRead( BlFileTable[ FileId ].DeviceId,
                                   Lbo,
                                   RunDirCount * sizeof(DIRENT),
                                   (PVOID)FatDirEnt )) {

            BlFileTable[ FileId ].Position.LowPart = Position;
            return Status;
        }

        for ( i=0; i<RunDirCount; i++ ) {

            //
            // exit from loop if logical end of directory
            //

            if ( FatDirEnt[i].FileName[0] == FAT_DIRENT_NEVER_USED ) {

                EofDir = TRUE;
                break;
            }

            //
            // update the current position and the number of bytes transfered
            //

            BlFileTable[ FileId ].Position.LowPart += sizeof(DIRENT);
            Lbo += sizeof(DIRENT);
            RunByteCount -= sizeof(DIRENT);

            //
            // skip this entry if the file or directory has been erased
            //

            if ( FatDirEnt[i].FileName[0] == FAT_DIRENT_DELETED ) {

                continue;
            }

            //
            // skip this entry if this is a valume label
            //

            if (FlagOn( FatDirEnt[i].Attributes, FAT_DIRENT_ATTR_VOLUME_ID )) {

                continue;
            }

            //
            // convert FAT directory entry in ARC directory entry
            //

            FatDirToArcDir( &FatDirEnt[i], DirEntry++ );

            //
            // update pointers
            //

            if ( ++*CountDir >= NumberDir ) {

                break;
            }
        }

    } while ( !EofDir  &&  *CountDir < NumberDir );

    //
    // all done
    //

    return *CountDir ? ESUCCESS : ENOTDIR;
}


ARC_STATUS
FatGetFileInformation (
    IN ULONG FileId,
    OUT PFILE_INFORMATION Buffer
    )

/*++

Routine Description:

    This procedure returns to the user a buffer filled with file information

Arguments:

    FileId - Supplies the File id for the operation

    Buffer - Supplies the buffer to receive the file information.  Note that
        it must be large enough to hold the full file name

Return Value:

    ESUCCESS is returned if the open operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    UCHAR Attributes;
    ULONG i;

    FatDebugOutput("FatGetFileInformation\r\n", 0, 0);

    //
    //  Load our local variables
    //

    FileTableEntry = &BlFileTable[FileId];
    Attributes = FileTableEntry->u.FatFileContext.Dirent.Attributes;

    //
    //  Zero out the buffer, and fill in its non-zero values.
    //

    RtlZeroMemory(Buffer, sizeof(FILE_INFORMATION));

    Buffer->EndingAddress.LowPart = FileTableEntry->u.FatFileContext.Dirent.FileSize;

    Buffer->CurrentPosition.LowPart = FileTableEntry->Position.LowPart;
    Buffer->CurrentPosition.HighPart = 0;

    if (FlagOn(Attributes, FAT_DIRENT_ATTR_READ_ONLY)) { SetFlag(Buffer->Attributes, ArcReadOnlyFile) };
    if (FlagOn(Attributes, FAT_DIRENT_ATTR_HIDDEN))    { SetFlag(Buffer->Attributes, ArcHiddenFile) };
    if (FlagOn(Attributes, FAT_DIRENT_ATTR_SYSTEM))    { SetFlag(Buffer->Attributes, ArcSystemFile) };
    if (FlagOn(Attributes, FAT_DIRENT_ATTR_ARCHIVE))   { SetFlag(Buffer->Attributes, ArcArchiveFile) };
    if (FlagOn(Attributes, FAT_DIRENT_ATTR_DIRECTORY)) { SetFlag(Buffer->Attributes, ArcDirectoryFile) };

    Buffer->FileNameLength = FileTableEntry->FileNameLength;

    for (i = 0; i < FileTableEntry->FileNameLength; i += 1) {

        Buffer->FileName[i] = FileTableEntry->FileName[i];
    }

    return ESUCCESS;
}


ARC_STATUS
FatOpen (
    IN PCHAR FileName,
    IN OPEN_MODE OpenMode,
    IN PULONG FileId
    )

/*++

Routine Description:

    This routine searches the device for a file matching FileName.
    If a match is found the dirent for the file is saved and the file is
    opened.

Arguments:

    FileName - Supplies a pointer to a zero terminated file name.

    OpenMode - Supplies the mode of the open.

    FileId - Supplies a pointer to a variable that specifies the file
        table entry that is to be filled in if the open is successful.

Return Value:

    ESUCCESS is returned if the open operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    PFAT_STRUCTURE_CONTEXT FatStructureContext;
    ULONG DeviceId;

    FAT_ENTRY CurrentDirectoryIndex;
    BOOLEAN SearchSucceeded;
    BOOLEAN IsDirectory;
    BOOLEAN IsReadOnly;

    STRING PathName;
    FAT8DOT3 Name;

    FatDebugOutput("FatOpen\r\n", 0, 0);

    //
    //  Load our local variables
    //

    FileTableEntry = &BlFileTable[*FileId];
    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
    DeviceId = FileTableEntry->DeviceId;

    //
    //  Construct a file name descriptor from the input file name
    //

    RtlInitString( &PathName, FileName );

    //
    //  While the path name has some characters in it we'll go through our loop
    //  which extracts the first part of the path name and searches the current
    //  directory for an entry.  If what we find is a directory then we have to
    //  continue looping until we're done with the path name.
    //

    FileTableEntry->u.FatFileContext.DirentLbo = 0;
    FileTableEntry->Position.LowPart = 0;
    FileTableEntry->Position.HighPart = 0;

    CurrentDirectoryIndex = 0;
    SearchSucceeded = TRUE;
    IsDirectory = TRUE;
    IsReadOnly = TRUE;

    if ((PathName.Buffer[0] == '\\') && (PathName.Length == 1)) {

        //
        // We are opening the root directory.
        //
        // N.B.: IsDirectory and SearchSucceeded are already TRUE.
        //

        PathName.Length = 0;

        FileTableEntry->FileNameLength = 1;
        FileTableEntry->FileName[0] = PathName.Buffer[0];

        //
        // Root dirent is all zeroes with a directory attribute.
        //

        RtlZeroMemory(&FileTableEntry->u.FatFileContext.Dirent, sizeof(DIRENT));

        FileTableEntry->u.FatFileContext.Dirent.Attributes = FAT_DIRENT_ATTR_DIRECTORY;

        FileTableEntry->u.FatFileContext.DirentLbo = 0;

        IsReadOnly = FALSE;

        CurrentDirectoryIndex = FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile;

    } else {

        //
        // We are not opening the root directory.
        //

        while ((PathName.Length > 0) && IsDirectory) {

            ARC_STATUS Status;

            //
            //  Extract the first component and search the directory for a match, but
            //  first copy the first part to the file name buffer in the file table entry
            //

            if (PathName.Buffer[0] == '\\') {
                PathName.Buffer +=1;
                PathName.Length -=1;
            }

            for (FileTableEntry->FileNameLength = 0;
                 (((USHORT)FileTableEntry->FileNameLength < PathName.Length) &&
                  (PathName.Buffer[FileTableEntry->FileNameLength] != '\\'));
                 FileTableEntry->FileNameLength += 1) {

                FileTableEntry->FileName[FileTableEntry->FileNameLength] =
                                             PathName.Buffer[FileTableEntry->FileNameLength];
            }

            FatFirstComponent( &PathName, &Name );

            Status = FatSearchForDirent( FatStructureContext,
                                         DeviceId,
                                         CurrentDirectoryIndex,
                                         &Name,
                                         &FileTableEntry->u.FatFileContext.Dirent,
                                         &FileTableEntry->u.FatFileContext.DirentLbo,
                                         FALSE );

            if (Status == ENOENT) {

                SearchSucceeded = FALSE;
                break;
            }

            if (Status != ESUCCESS) {

                return Status;
            }

            //
            //  We have a match now check to see if it is a directory, and also
            //  if it is readonly
            //

            IsDirectory = BooleanFlagOn( FileTableEntry->u.FatFileContext.Dirent.Attributes,
                                         FAT_DIRENT_ATTR_DIRECTORY );

            IsReadOnly = BooleanFlagOn( FileTableEntry->u.FatFileContext.Dirent.Attributes,
                                        FAT_DIRENT_ATTR_READ_ONLY );

            if (IsDirectory) {

                CurrentDirectoryIndex = FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile;
            }
        }
    }

    //
    //  If the path name length is not zero then we were trying to crack a path
    //  with an nonexistent (or non directory) name in it.  For example, we tried
    //  to crack a\b\c\d and b is not a directory or does not exist (then the path
    //  name will still contain c\d).
    //

    if (PathName.Length != 0) {

        return ENOTDIR;
    }

    //
    //  At this point we've cracked the name up to (an maybe including the last
    //  component).  We located the last component if the SearchSucceeded flag is
    //  true, otherwise the last component does not exist.  If we located the last
    //  component then this is like an open or a supersede, but not a create.
    //

    if (SearchSucceeded) {

        //
        //  Check if the last component is a directory
        //

        if (IsDirectory) {

            //
            //  For an existing directory the only valid open mode is OpenDirectory
            //  all other modes return an error
            //

            switch (OpenMode) {

            case ArcOpenReadOnly:
            case ArcOpenWriteOnly:
            case ArcOpenReadWrite:
            case ArcCreateWriteOnly:
            case ArcCreateReadWrite:
            case ArcSupersedeWriteOnly:
            case ArcSupersedeReadWrite:

                //
                //  If we reach here then the caller got a directory but didn't
                //  want to open a directory
                //

                return EISDIR;

            case ArcOpenDirectory:

                //
                //  If we reach here then the caller got a directory and wanted
                //  to open a directory.
                //

                FileTableEntry->Flags.Open = 1;
                FileTableEntry->Flags.Read = 1;

                return ESUCCESS;

            case ArcCreateDirectory:

                //
                //  If we reach here then the caller got a directory and wanted
                //  to create a new directory
                //

                return EACCES;
            }
        }

        //
        //  If we get there then we have an existing file that is being opened.
        //  We can open existing files through a lot of different open modes in
        //  some cases we need to check the read only part of file and/or truncate
        //  the file.
        //

        switch (OpenMode) {

        case ArcOpenReadOnly:

            //
            //  If we reach here then the user got a file and wanted to open the
            //  file read only
            //

            FileTableEntry->Flags.Open = 1;
            FileTableEntry->Flags.Read = 1;

            return ESUCCESS;

        case ArcOpenWriteOnly:

            //
            //  If we reach here then the user got a file and wanted to open the
            //  file write only
            //

            if (IsReadOnly) { return EROFS; }
            FileTableEntry->Flags.Open = 1;
            FileTableEntry->Flags.Write = 1;

            return ESUCCESS;

        case ArcOpenReadWrite:

            //
            //  If we reach here then the user got a file and wanted to open the
            //  file read/write
            //

            if (IsReadOnly) { return EROFS; }
            FileTableEntry->Flags.Open = 1;
            FileTableEntry->Flags.Read = 1;
            FileTableEntry->Flags.Write = 1;

            return ESUCCESS;

        case ArcCreateWriteOnly:
        case ArcCreateReadWrite:

            //
            //  If we reach here then the user got a file and wanted to create a new
            //  file
            //

            return EACCES;

        case ArcSupersedeWriteOnly:

            //
            //  If we reach here then the user got a file and wanted to supersede a
            //  file
            //

            if (IsReadOnly) { return EROFS; }
            TruncateFileAllocation( *FileId, 0 );
            FileTableEntry->Flags.Open = 1;
            FileTableEntry->Flags.Read = 1;
            FileTableEntry->Flags.Write = 1;

            return ESUCCESS;

        case ArcSupersedeReadWrite:

            //
            //  If we reach here then the user got a file and wanted to supersede a
            //  file
            //

            if (IsReadOnly) { return EROFS; }
            TruncateFileAllocation( *FileId, 0 );
            FileTableEntry->Flags.Open = 1;
            FileTableEntry->Flags.Read = 1;
            FileTableEntry->Flags.Write = 1;

            return ESUCCESS;

        case ArcOpenDirectory:
        case ArcCreateDirectory:

            //
            //  If we reach here then the user got a file and wanted a directory
            //

            return ENOTDIR;
        }
    }

    //
    //  If we get here the last component does not exist so we are trying to create
    //  either a new file or a directory.
    //

    switch (OpenMode) {

    case ArcOpenReadOnly:
    case ArcOpenWriteOnly:
    case ArcOpenReadWrite:

        //
        //  If we reach here then the user did not get a file but wanted a file
        //

        return ENOENT;

    case ArcCreateWriteOnly:
    case ArcSupersedeWriteOnly:

        //
        //  If we reach here then the user did not get a file and wanted to create
        //  or supersede a file write only
        //

        RtlZeroMemory( &FileTableEntry->u.FatFileContext.Dirent, sizeof(DIRENT));

        FatSetDirent( &Name, &FileTableEntry->u.FatFileContext.Dirent, 0 );

        CreateDirent( FatStructureContext,
                      DeviceId,
                      CurrentDirectoryIndex,
                      &FileTableEntry->u.FatFileContext.Dirent,
                      &FileTableEntry->u.FatFileContext.DirentLbo );

        FileTableEntry->Flags.Open = 1;
        FileTableEntry->Flags.Write = 1;

        return ESUCCESS;

    case ArcCreateReadWrite:
    case ArcSupersedeReadWrite:

        //
        //  If we reach here then the user did not get a file and wanted to create
        //  or supersede a file read/write
        //

        RtlZeroMemory( &FileTableEntry->u.FatFileContext.Dirent, sizeof(DIRENT));

        FatSetDirent( &Name, &FileTableEntry->u.FatFileContext.Dirent, 0 );

        CreateDirent( FatStructureContext,
                      DeviceId,
                      CurrentDirectoryIndex,
                      &FileTableEntry->u.FatFileContext.Dirent,
                      &FileTableEntry->u.FatFileContext.DirentLbo );

        FileTableEntry->Flags.Open = 1;
        FileTableEntry->Flags.Read = 1;
        FileTableEntry->Flags.Write = 1;

        return ESUCCESS;

    case ArcOpenDirectory:

        //
        //  If we reach here then the user did not get a file and wanted to open
        //  an existing directory
        //

        return ENOENT;

    case ArcCreateDirectory:

        //
        //  If we reach here then the user did not get a file and wanted to create
        //  a new directory.
        //

        RtlZeroMemory( &FileTableEntry->u.FatFileContext.Dirent, sizeof(DIRENT));

        FatSetDirent( &Name,
                      &FileTableEntry->u.FatFileContext.Dirent,
                      FAT_DIRENT_ATTR_DIRECTORY );

        CreateDirent( FatStructureContext,
                      DeviceId,
                      CurrentDirectoryIndex,
                      &FileTableEntry->u.FatFileContext.Dirent,
                      &FileTableEntry->u.FatFileContext.DirentLbo );

        IncreaseFileAllocation( *FileId, sizeof(DIRENT) * 2 );

        {
            DIRENT Buffer;
            LBO Lbo;
            ULONG Count;
            ULONG i;

            RtlZeroMemory((PVOID)&Buffer.FileName[0], sizeof(DIRENT) );

            for (i = 0; i < 11; i += 1) {
                Buffer.FileName[i] = ' ';
            }
            Buffer.Attributes = FAT_DIRENT_ATTR_DIRECTORY;

            VboToLbo( *FileId, 0, &Lbo, &Count );
            Buffer.FileName[0] = FAT_DIRENT_DIRECTORY_ALIAS;
            Buffer.FirstClusterOfFile = FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile;
            DiskWrite( DeviceId, Lbo, sizeof(DIRENT), (PVOID)&Buffer.FileName[0] );

            VboToLbo( *FileId, sizeof(DIRENT), &Lbo, &Count );
            Buffer.FileName[1] = FAT_DIRENT_DIRECTORY_ALIAS;
            Buffer.FirstClusterOfFile = CurrentDirectoryIndex;
            DiskWrite( DeviceId, Lbo, sizeof(DIRENT), (PVOID)&Buffer.FileName[0] );
        }

        FileTableEntry->Flags.Open = 1;
        FileTableEntry->Flags.Read = 1;

        return ESUCCESS;
    }
}


ARC_STATUS
FatRead (
    IN ULONG FileId,
    OUT PVOID Buffer,
    IN ULONG Length,
    OUT PULONG Transfer
    )

/*++

Routine Description:

    This routine reads data from the specified file.

Arguments:

    FileId - Supplies the file table index.

    Buffer - Supplies a pointer to the buffer that receives the data
        read.

    Length - Supplies the number of bytes that are to be read.

    Transfer - Supplies a pointer to a variable that receives the number
        of bytes actually transfered.

Return Value:

    ESUCCESS is returned if the read operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    PFAT_STRUCTURE_CONTEXT FatStructureContext;
    ULONG DeviceId;

    FatDebugOutput("FatRead\r\n", 0, 0);

    //
    //  Load out local variables
    //

    FileTableEntry = &BlFileTable[FileId];
    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
    DeviceId = FileTableEntry->DeviceId;

    //
    //  Clear the transfer count
    //

    *Transfer = 0;

    //
    //  Read in runs (i.e., bytes) until the byte count goes to zero
    //

    while (Length > 0) {

        LBO Lbo;

        ULONG CurrentRunByteCount;

        //
        //  Lookup the corresponding Lbo and run length for the current position
        //  (i.e., Vbo).
        //

        if (FatVboToLbo( FileId, FileTableEntry->Position.LowPart, &Lbo, &CurrentRunByteCount, FALSE ) != ESUCCESS) {

            return ESUCCESS;
        }

        //
        //  while there are bytes to be read in from the current run
        //  length and we haven't exhausted the request we loop reading
        //  in bytes.  The biggest request we'll handle is only 32KB
        //  contiguous bytes per physical read.  So we might need to loop
        //  through the run.
        //

        while ((Length > 0) && (CurrentRunByteCount > 0)) {

            LONG SingleReadSize;

            //
            //  Compute the size of the next physical read
            //

            SingleReadSize = Minimum(Length, 32 * 1024);
            SingleReadSize = Minimum((ULONG)SingleReadSize, CurrentRunByteCount);

            //
            //  Don't read beyond the eof
            //

            if (((ULONG)SingleReadSize + FileTableEntry->Position.LowPart) >
                FileTableEntry->u.FatFileContext.Dirent.FileSize) {

                SingleReadSize = FileTableEntry->u.FatFileContext.Dirent.FileSize -
                                 FileTableEntry->Position.LowPart;

                //
                //  If the readjusted read length is now zero then we're done.
                //

                if (SingleReadSize <= 0) {

                    return ESUCCESS;
                }

                //
                //  By also setting length here we'll make sure that this is our last
                //  read
                //

                Length = SingleReadSize;
            }

            //
            //  Issue the read
            //

            DiskRead( DeviceId, Lbo, SingleReadSize, Buffer, FALSE );

            //
            //  Update the remaining length, Current run byte count
            //  and new Lbo offset
            //

            Length -= SingleReadSize;
            CurrentRunByteCount -= SingleReadSize;
            Lbo += SingleReadSize;

            //
            //  Update the current position and the number of bytes transfered
            //

            FileTableEntry->Position.LowPart += SingleReadSize;
            *Transfer += SingleReadSize;

            //
            //  Update buffer to point to the next byte location to fill in
            //

            Buffer = (PCHAR)Buffer + SingleReadSize;
        }
    }

    //
    //  If we get here then remaining sector count is zero so we can
    //  return success to our caller
    //

    return ESUCCESS;
}


ARC_STATUS
FatRename(
    IN ULONG FileId,
    IN PCHAR NewFileName
    )

/*++

Routine Description:

    This routine renames an open file.  It does no checking to
    see if the target filename already exists.  It is intended for use
    only when dual-booting DOS on x86 machines, where it is used to
    replace the NT MVDM CONFIG.SYS and AUTOEXEC.BAT with the native DOS
    CONFIG.SYS and AUTOEXEC.BAT files.

Arguments:

    FileId - Supplies the file id of the file to be renamed

    NewFileName - Supplies the new name for the file.

Return Value:

    ARC_STATUS

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    PFAT_STRUCTURE_CONTEXT FatStructureContext;
    ULONG DeviceId;
    FAT8DOT3 FatName;
    STRING String;

    //
    //  Initialize our local variables
    //

    RtlInitString( &String, NewFileName );
    FileTableEntry = &BlFileTable[FileId];
    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
    DeviceId = FileTableEntry->DeviceId;

    //
    //  Modify a in-memory copy of the dirent with the new name
    //

    FatFirstComponent( &String, &FatName );

    FatSetDirent( &FatName,
                  &FileTableEntry->u.FatFileContext.Dirent,
                  FileTableEntry->u.FatFileContext.Dirent.Attributes );

    //
    //  Write the modified dirent to disk
    //

    DiskWrite( DeviceId,
               FileTableEntry->u.FatFileContext.DirentLbo,
               sizeof(DIRENT),
               &FileTableEntry->u.FatFileContext.Dirent );

    //
    //  And return to our caller
    //

    return ESUCCESS;
}


ARC_STATUS
FatSeek (
    IN ULONG FileId,
    IN PLARGE_INTEGER Offset,
    IN SEEK_MODE SeekMode
    )

/*++

Routine Description:

    This routine seeks to the specified position for the file specified
    by the file id.

Arguments:

    FileId - Supplies the file table index.

    Offset - Supplies the offset in the file to position to.

    SeekMode - Supplies the mode of the seek operation.

Return Value:

    ESUCCESS is returned if the seek operation is successful.  Otherwise,
    EINVAL is returned.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    ULONG NewPosition;

    FatDebugOutput("FatSeek\r\n", 0, 0);

    //
    //  Load our local variables
    //

    FileTableEntry = &BlFileTable[FileId];

    //
    //  Compute the new position
    //

    if (SeekMode == SeekAbsolute) {

        NewPosition = Offset->LowPart;

    } else {

        NewPosition = FileTableEntry->Position.LowPart + Offset->LowPart;
    }

    //
    //  If the new position is greater than the file size then return
    //  an error
    //

    if (NewPosition > FileTableEntry->u.FatFileContext.Dirent.FileSize) {

        return EINVAL;
    }

    //
    //  Otherwise set the new position and return to our caller
    //

    FileTableEntry->Position.LowPart = NewPosition;

    return ESUCCESS;
}


ARC_STATUS
FatSetFileInformation (
    IN ULONG FileId,
    IN ULONG AttributeFlags,
    IN ULONG AttributeMask
    )

/*++

Routine Description:

    This routine sets the file attributes of the indicated file

Arguments:

    FileId - Supplies the File Id for the operation

    AttributeFlags - Supplies the value (on or off) for each attribute being modified

    AttributeMask - Supplies a mask of the attributes being altered.  All other
        file attributes are left alone.

Return Value:

    ESUCCESS is returned if the read operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    PFAT_STRUCTURE_CONTEXT FatStructureContext;
    ULONG DeviceId;
    UCHAR DirentAttributes;
    UCHAR DirentMask;
    UCHAR DirentFlags;

    FatDebugOutput("FatSetFileInformation\r\n", 0, 0);

    //
    //  Load our local variables
    //

    FileTableEntry = &BlFileTable[FileId];
    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
    DeviceId = FileTableEntry->DeviceId;

    DirentAttributes = FileTableEntry->u.FatFileContext.Dirent.Attributes;

    //
    //  Check if this is the root directory
    //

    if (FileTableEntry->u.FatFileContext.DirentLbo == 0) {

        return EACCES;
    }

    //
    //  Check if the users wishes to delete the file/directory
    //

    if (FlagOn(AttributeMask, ArcDeleteFile) && FlagOn(AttributeFlags, ArcDeleteFile)) {

        //
        //  Check if the file/directory is marked read only
        //

        if (FlagOn(DirentAttributes, FAT_DIRENT_ATTR_READ_ONLY)) {

            return EACCES;
        }

        //
        //  Check if this is a directory because we need to then check if the
        //  directory is empty
        //

        if (FlagOn(DirentAttributes, FAT_DIRENT_ATTR_DIRECTORY)) {

            ULONG BytesPerCluster;
            FAT_ENTRY FatEntry;
            CLUSTER_TYPE ClusterType;

            BytesPerCluster = FatBytesPerCluster( &FatStructureContext->Bpb );
            FatEntry = FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile;
            ClusterType = FatInterpretClusterType( FatStructureContext, FatEntry );

            //
            //  Now loop through each cluster, and compute the starting Lbo for each
            //  cluster that we encounter
            //

            while (ClusterType == FatClusterNext) {

                LBO ClusterLbo;
                ULONG Offset;

                ClusterLbo = FatIndexToLbo( FatStructureContext, FatEntry );

                //
                //  Now for each dirent in the cluster compute the lbo, read in the dirent
                //  and check if it is in use
                //

                for (Offset = 0; Offset < BytesPerCluster; Offset += sizeof(DIRENT)) {

                    DIRENT Dirent;

                    DiskRead( DeviceId, Offset + ClusterLbo, sizeof(DIRENT), &Dirent, FALSE );

                    if (Dirent.FileName[0] == FAT_DIRENT_NEVER_USED) {

                        break;
                    }

                    if ((Dirent.FileName[0] != FAT_DIRENT_DIRECTORY_ALIAS) ||
                        (Dirent.FileName[0] != FAT_DIRENT_DELETED)) {

                        return EACCES;
                    }
                }

                //
                //  Now that we've exhausted the current cluster we need to read
                //  in the next cluster.  So locate the next fat entry in the chain
                //  and go back to the top of the while loop.
                //

                LookupFatEntry( FatStructureContext, DeviceId, FatEntry, &FatEntry, FALSE );

                ClusterType = FatInterpretClusterType(FatStructureContext, FatEntry);
            }
        }

        //
        //  At this point the file/directory can be deleted so mark the name
        //  as deleted and write it back out
        //

        FileTableEntry->u.FatFileContext.Dirent.FileName[0] = FAT_DIRENT_DELETED;

        DiskWrite( DeviceId,
                   FileTableEntry->u.FatFileContext.DirentLbo,
                   sizeof(DIRENT),
                   &FileTableEntry->u.FatFileContext.Dirent );

        //
        //  And then truncate any file allocation assigned to the file
        //

        TruncateFileAllocation( FileId, 0);

        return ESUCCESS;
    }

    //
    //  At this point the user does not want to delete the file so we only
    //  need to modify the attributes
    //

    DirentMask = 0;
    DirentFlags = 0;

    //
    //  Build up a mask and flag byte that correspond to the bits in the dirent
    //

    if (FlagOn(AttributeMask, ArcReadOnlyFile)) { SetFlag(DirentMask, FAT_DIRENT_ATTR_READ_ONLY); }
    if (FlagOn(AttributeMask, ArcHiddenFile))   { SetFlag(DirentMask, FAT_DIRENT_ATTR_HIDDEN); }
    if (FlagOn(AttributeMask, ArcSystemFile))   { SetFlag(DirentMask, FAT_DIRENT_ATTR_SYSTEM); }
    if (FlagOn(AttributeMask, ArcArchiveFile))  { SetFlag(DirentMask, FAT_DIRENT_ATTR_ARCHIVE); }

    if (FlagOn(AttributeFlags, ArcReadOnlyFile)) { SetFlag(DirentFlags, FAT_DIRENT_ATTR_READ_ONLY); }
    if (FlagOn(AttributeFlags, ArcHiddenFile))   { SetFlag(DirentFlags, FAT_DIRENT_ATTR_HIDDEN); }
    if (FlagOn(AttributeFlags, ArcSystemFile))   { SetFlag(DirentFlags, FAT_DIRENT_ATTR_SYSTEM); }
    if (FlagOn(AttributeFlags, ArcArchiveFile))  { SetFlag(DirentFlags, FAT_DIRENT_ATTR_ARCHIVE); }

    //
    //  The new attributes is calculated via the following formula
    //
    //      Attributes = (~Mask & OldAttributes) | (Mask & NewAttributes);
    //
    //  After we calculate the new attribute byte we write it out.
    //

    FileTableEntry->u.FatFileContext.Dirent.Attributes = (UCHAR)((~DirentMask & DirentAttributes) |
                                                                 (DirentMask & DirentFlags));

    DiskWrite( DeviceId,
               FileTableEntry->u.FatFileContext.DirentLbo,
               sizeof(DIRENT),
               &FileTableEntry->u.FatFileContext.Dirent );

    return ESUCCESS;
}


ARC_STATUS
FatWrite (
    IN ULONG FileId,
    IN PVOID Buffer,
    IN ULONG Length,
    OUT PULONG Transfer
    )

/*++

Routine Description:

    This routine writes data to the specified file.

Arguments:

    FileId - Supplies the file table index.

    Buffer - Supplies a pointer to the buffer that contains the data
        written.

    Length - Supplies the number of bytes that are to be written.

    Transfer - Supplies a pointer to a variable that receives the number
        of bytes actually transfered.

Return Value:

    ESUCCESS is returned if the write operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    PFAT_STRUCTURE_CONTEXT FatStructureContext;
    ULONG DeviceId;
    ULONG OffsetBeyondWrite;

    FatDebugOutput("FatWrite\r\n", 0, 0);

    //
    //  Load our local variables
    //

    FileTableEntry = &BlFileTable[FileId];
    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
    DeviceId = FileTableEntry->DeviceId;

    //
    //  Reset the file size to be the maximum of what is it now and the end of
    //  our write.  We will assume that there is always enough allocation to support
    //  the file size, so we only need to increase allocation if we are increasing
    //  the file size.
    //

    OffsetBeyondWrite = FileTableEntry->Position.LowPart + Length;

    if (OffsetBeyondWrite > FileTableEntry->u.FatFileContext.Dirent.FileSize) {

        IncreaseFileAllocation( FileId, OffsetBeyondWrite );

        FileTableEntry->u.FatFileContext.Dirent.FileSize = OffsetBeyondWrite;

        DiskWrite( DeviceId,
                   FileTableEntry->u.FatFileContext.DirentLbo,
                   sizeof(DIRENT),
                   &FileTableEntry->u.FatFileContext.Dirent );
    }

    //
    //  Clear the transfer count
    //

    *Transfer = 0;

    //
    //  Write out runs (i.e., bytes) until the byte count goes to zero
    //

    while (Length > 0) {

        LBO Lbo;

        ULONG CurrentRunByteCount;

        //
        //  Lookup the corresponding Lbo and run length for the current position
        //  (i.e., Vbo).
        //

        VboToLbo( FileId, FileTableEntry->Position.LowPart, &Lbo, &CurrentRunByteCount );

        //
        //  While there are bytes to be written out to the current run
        //  length and we haven't exhausted the request we loop reading
        //  in bytes.  The biggest request we'll handle is only 32KB
        //  contiguous bytes per physical read.  So we might need to loop
        //  through the run.
        //

        while ((Length > 0) && (CurrentRunByteCount > 0)) {

            LONG SingleWriteSize;

            //
            //  Compute the size of the next physical read
            //

            SingleWriteSize = Minimum(Length, 32 * 1024);
            SingleWriteSize = Minimum((ULONG)SingleWriteSize, CurrentRunByteCount);

            //
            //  Issue the Write
            //

            DiskWrite( DeviceId, Lbo, SingleWriteSize, Buffer);

            //
            //  Update the remaining length, Current run byte count
            //  and new Lbo offset
            //

            Length -= SingleWriteSize;
            CurrentRunByteCount -= SingleWriteSize;
            Lbo += SingleWriteSize;

            //
            //  Update the current position and the number of bytes transfered
            //

            FileTableEntry->Position.LowPart += SingleWriteSize;
            *Transfer += SingleWriteSize;

            //
            //  Update buffer to point to the next byte location to fill in
            //

            Buffer = (PCHAR)Buffer + SingleWriteSize;
        }
    }

    //
    //  Check if the fat is dirty and flush it out if it is.
    //

    if (FatStructureContext->CachedFatDirty) {

        FlushFatEntries( FatStructureContext, DeviceId );
    }

    //
    //  If we get here then remaining sector count is zero so we can
    //  return success to our caller
    //

    return ESUCCESS;
}


ARC_STATUS
FatInitialize (
    VOID
    )

/*++

Routine Description:

    This routine initializes the fat boot filesystem.
    Currently this is a no-op.

Arguments:

    None.

Return Value:

    ESUCCESS.

--*/

{
    return ESUCCESS;
}


//
//  Internal support routine
//

ARC_STATUS
FatDiskRead (
    IN ULONG DeviceId,
    IN LBO Lbo,
    IN ULONG ByteCount,
    IN PVOID Buffer
    )

/*++

Routine Description:

    This routine reads in zero or more bytes from the specified device.

Arguments:

    DeviceId - Supplies the device id to use in the arc calls.

    Lbo - Supplies the LBO to start reading from.

    ByteCount - Supplies the number of bytes to read.

    Buffer - Supplies a pointer to the buffer to read the bytes into.

Return Value:

    ESUCCESS is returned if the read operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    LARGE_INTEGER LargeLbo;
    ARC_STATUS Status;
    ULONG i;

    //
    //  Special case the zero byte read request
    //

    if (ByteCount == 0) {

        return ESUCCESS;
    }

    //
    //  Seek to the appropriate offset on the volume
    //

    LargeLbo.LowPart = (ULONG)Lbo;
    LargeLbo.HighPart = 0;

    if ((Status = ArcSeek( DeviceId, &LargeLbo, SeekAbsolute )) != ESUCCESS) {

        return Status;
    }

    //
    //  Issue the arc read request
    //

    if ((Status = ArcRead( DeviceId, Buffer, ByteCount, &i)) != ESUCCESS) {

        return Status;
    }

    //
    //  Make sure we got back the amount requested
    //

    if (ByteCount != i) {

        return EIO;
    }

    //
    //  Everything is fine so return success to our caller
    //

    return ESUCCESS;
}


//
//  Internal support routine
//

ARC_STATUS
FatDiskWrite (
    IN ULONG DeviceId,
    IN LBO Lbo,
    IN ULONG ByteCount,
    IN PVOID Buffer
    )

/*++

Routine Description:

    This routine writes in zero or more bytes to the specified device.

Arguments:

    DeviceId - Supplies the device id to use in the arc calls.

    Lbo - Supplies the LBO to start writing from.

    ByteCount - Supplies the number of bytes to write.

    Buffer - Supplies a pointer to the buffer of bytes to write out.

Return Value:

    ESUCCESS is returned if the write operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    LARGE_INTEGER LargeLbo;
    ARC_STATUS Status;
    ULONG i;

    //
    //  Special case the zero byte write request
    //

    if (ByteCount == 0) {

        return ESUCCESS;
    }

    //
    //  Seek to the appropriate offset on the volume
    //

    LargeLbo.LowPart = (ULONG) Lbo;
    LargeLbo.HighPart = 0;

    if ((Status = ArcSeek( DeviceId, &LargeLbo, SeekAbsolute )) != ESUCCESS) {

        return Status;
    }

    //
    //  Issue the arc write request
    //

    if ((Status = ArcWrite( DeviceId, Buffer, ByteCount, &i)) != ESUCCESS) {

        return Status;
    }

    //
    //  Make sure we wrote out the amount requested
    //

    if (ByteCount != i) {

        return EIO;
    }

    //
    //  Everything is fine so return success to our caller
    //

    return ESUCCESS;
}


//
//  Internal support routine
//

CLUSTER_TYPE
FatInterpretClusterType (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN FAT_ENTRY Entry
    )

/*++

Routine Description:

    This procedure tells the caller how to interpret a fat table entry.  It will
    indicate if the fat cluster is available, reserved, bad, the last one, or another
    fat index.

Arguments:

    FatStructureContext - Supplies the volume structure for the operation

    DeviceId - Supplies the DeviceId for the volume being used.

    Entry - Supplies the fat entry to examine.

Return Value:

    The type of the input fat entry is returned

--*/

{
    //
    //  Check for 12 or 16 bit fat.
    //

    if (FatIndexBitSize(&FatStructureContext->Bpb) == 12) {

        //
        //  For 12 bit fat check for one of the cluster types, but first
        //  make sure we only looking at 12 bits of the entry
        //

        Entry &= 0x00000fff;

        if       (Entry == 0x000)                      { return FatClusterAvailable; }
        else if ((Entry >= 0xff0) && (Entry <= 0xff6)) { return FatClusterReserved; }
        else if  (Entry == 0xff7)                      { return FatClusterBad; }
        else if ((Entry >= 0xff8) && (Entry <= 0xfff)) { return FatClusterLast; }
        else                                           { return FatClusterNext; }

   } else {

        //
        //  For 16 bit fat check for one of the cluster types, but first
        //  make sure we are only looking at 16 bits of the entry
        //

        Entry &= 0x0000ffff;

        if       (Entry == 0x0000)                       { return FatClusterAvailable; }
        else if ((Entry >= 0xfff0) && (Entry <= 0xfff6)) { return FatClusterReserved; }
        else if  (Entry == 0xfff7)                       { return FatClusterBad; }
        else if ((Entry >= 0xfff8) && (Entry <= 0xffff)) { return FatClusterLast; }
        else                                             { return FatClusterNext; }
    }
}


//
//  Internal support routine
//

ARC_STATUS
FatLookupFatEntry (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId,
    IN FAT_ENTRY FatIndex,
    OUT PFAT_ENTRY FatEntry,
    IN BOOLEAN IsDoubleSpace
    )

/*++

Routine Description:

    This routine returns the value stored within the fat table and the specified
    fat index.  It is semantically equivalent to doing

        x = Fat[FatIndex]

Arguments:

    FatStrutureContext - Supplies the volume struture being used

    DeviceId - Supplies the device being used

    FatIndex - Supplies the index being looked up.

    FatEntry - Receives the value stored at the specified fat index

    IsDoubleSpace - Indicates if the search is being done on a double space volume

Return Value:

    ESUCCESS is returned if the operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    BOOLEAN TwelveBitFat;
    VBO Vbo;

    //****if (IsDoubleSpace) { DbgPrint("FatLookupFatEntry(%0x,%0x,%0x,%0x,%0x)\n",FatStructureContext, DeviceId, FatIndex, FatEntry, IsDoubleSpace); }

    //
    //  Calculate the Vbo of the word in the fat we need and
    //  also figure out if this is a 12 or 16 bit fat
    //

    if (FatIndexBitSize( &FatStructureContext->Bpb ) == 12) {

        TwelveBitFat = TRUE;
        Vbo = (FatIndex * 3) / 2;

    } else {

        TwelveBitFat = FALSE;
        Vbo = FatIndex * 2;
    }

    //
    //  Check if the Vbo we need is already in the cached fat
    //

    if ((FatStructureContext->CachedFat == NULL) ||
        (Vbo < FatStructureContext->CachedFatVbo) ||
        ((Vbo+1) > (FatStructureContext->CachedFatVbo + FAT_CACHE_SIZE))) {

        //
        //  Set the aligned cached fat buffer in the structure context
        //

        FatStructureContext->CachedFat = ALIGN_BUFFER( &FatStructureContext->CachedFatBuffer[0] );

        //
        //  As a safety net we'll flush any dirty fats that we might have cached before
        //  we turn the window
        //

        if (!IsDoubleSpace && FatStructureContext->CachedFatDirty) {

            FlushFatEntries( FatStructureContext, DeviceId );
        }

        //
        //  Now set the new cached Vbo to be the Vbo of the cache sized section that
        //  we're trying to map.  Each time we read in the cache we only read in
        //  cache sized and cached aligned pieces of the fat.  So first compute an
        //  aligned cached fat vbo and then do the read.
        //

        FatStructureContext->CachedFatVbo = (Vbo / FAT_CACHE_SIZE) * FAT_CACHE_SIZE;

        DiskRead( DeviceId,
                  FatStructureContext->CachedFatVbo + FatFirstFatAreaLbo(&FatStructureContext->Bpb),
                  FAT_CACHE_SIZE,
                  FatStructureContext->CachedFat,
                  IsDoubleSpace );
    }

    //
    //  At this point the cached fat contains the vbo we're after so simply
    //  extract the word
    //

    CopyUchar2( FatEntry,
                &FatStructureContext->CachedFat[Vbo - FatStructureContext->CachedFatVbo] );

    //
    //  Now if this is a 12 bit fat then check if the index is odd or even
    //  If it is odd then we need to shift it over 4 bits, and in all
    //  cases we need to mask out the high 4 bits.
    //

    if (TwelveBitFat) {

        if ((FatIndex % 2) == 1) { *FatEntry >>= 4; }

        *FatEntry &= 0x0fff;
    }

    return ESUCCESS;
}


//
//  Internal support routine
//

ARC_STATUS
FatSetFatEntry(
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId,
    IN FAT_ENTRY FatIndex,
    IN FAT_ENTRY FatEntry
    )

/*++

Routine Description:

    This procedure sets the data within the fat table at the specified index to
    to the specified value.  It is semantically equivalent to doing

        Fat[FatIndex] = FatEntry;

Arguments:

    FatStructureContext - Supplies the structure context for the operation

    DeviceId - Supplies the device for the operation

    FatIndex - Supplies the index within the fat table to set

    FatEntry - Supplies the value to store within the fat table

Return Value:

    ESUCCESS is returned if the operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    BOOLEAN TwelveBitFat;
    VBO Vbo;

    //
    //  Calculate the Vbo of the word in the fat we are modifying and
    //  also figure out if this is a 12 or 16 bit fat
    //

    if (FatIndexBitSize( &FatStructureContext->Bpb ) == 12) {

        TwelveBitFat = TRUE;
        Vbo = (FatIndex * 3) / 2;

    } else {

        TwelveBitFat = FALSE;
        Vbo = FatIndex * 2;
    }

    //
    //  Check if the Vbo we need is already in the cached fat
    //

    if ((FatStructureContext->CachedFat == NULL) ||
        (Vbo < FatStructureContext->CachedFatVbo) ||
        ((Vbo+1) > (FatStructureContext->CachedFatVbo + FAT_CACHE_SIZE))) {

        //
        //  Set the aligned cached fat buffer in the structure context
        //

        FatStructureContext->CachedFat = ALIGN_BUFFER( &FatStructureContext->CachedFatBuffer[0] );

        //
        //  As a safety net we'll flush any dirty fats that we might have cached before
        //  we turn the window
        //

        if (FatStructureContext->CachedFatDirty) {

            FlushFatEntries( FatStructureContext, DeviceId );
        }

        //
        //  Now set the new cached Vbo to be the Vbo of the cache sized section that
        //  we're trying to map.  Each time we read in the cache we only read in
        //  cache sized and cached aligned pieces of the fat.  So first compute an
        //  aligned cached fat vbo and then do the read.
        //

        FatStructureContext->CachedFatVbo = (Vbo / FAT_CACHE_SIZE) * FAT_CACHE_SIZE;

        DiskRead( DeviceId,
                  FatStructureContext->CachedFatVbo + FatFirstFatAreaLbo(&FatStructureContext->Bpb),
                  FAT_CACHE_SIZE,
                  FatStructureContext->CachedFat,
                  FALSE );
    }

    //
    //  At this point the cached fat contains the vbo we're after.  For a 16 bit
    //  fat we simply put in the fat entry.  For the 12 bit fat we first need to extract
    //  the word containing the entry, modify the word, and then put it back.
    //

    if (TwelveBitFat) {

        FAT_ENTRY Temp;

        CopyUchar2( &Temp,
                    &FatStructureContext->CachedFat[Vbo - FatStructureContext->CachedFatVbo] );

        if ((FatIndex % 2) == 0) {

            FatEntry = (FAT_ENTRY)((Temp & 0xf000) | (FatEntry & 0x0fff));

        } else {

            FatEntry = (FAT_ENTRY)((Temp & 0x000f) | ((FatEntry << 4) & 0xfff0));
        }
    }

    CopyUchar2( &FatStructureContext->CachedFat[Vbo - FatStructureContext->CachedFatVbo],
                &FatEntry );

    //
    //  Now that we're done we can set the fat dirty
    //

    FatStructureContext->CachedFatDirty = TRUE;

    return ESUCCESS;
}


//
//  Internal support routine
//

ARC_STATUS
FatFlushFatEntries (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId
    )

/*++

Routine Description:

    This routine flushes out any dirty cached fat entries to the volume.

Arguments:

    FatStructureContext - Supplies the structure context for the operation

    DeviceId - Supplies the Device for the operation

Return Value:

    ESUCCESS is returned if the operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    ULONG BytesPerFat;
    ULONG AmountToWrite;
    ULONG i;

    //
    //  Compute the actual number of bytes that we need to write.  We do this
    //  because we don't want to overwrite beyond the fat.
    //

    BytesPerFat = FatBytesPerFat(&FatStructureContext->Bpb);

    if (FatStructureContext->CachedFatVbo + FAT_CACHE_SIZE <= BytesPerFat) {

        AmountToWrite = FAT_CACHE_SIZE;

    } else {

        AmountToWrite = BytesPerFat - FatStructureContext->CachedFatVbo;
    }

    //
    //  For each fat table on the volume we will calculate the lbo for the operation
    //  and then write out the cached fat
    //

    for (i = 0; i < FatStructureContext->Bpb.Fats; i += 1) {

        ULONG Lbo;

        Lbo = FatStructureContext->CachedFatVbo +
              FatFirstFatAreaLbo(&FatStructureContext->Bpb) +
              (i * BytesPerFat);

        DiskWrite( DeviceId,
                   Lbo,
                   AmountToWrite,
                   FatStructureContext->CachedFat );
    }

    //
    //  we are all done so now mark the fat clean
    //

    FatStructureContext->CachedFatDirty = FALSE;

    return ESUCCESS;
}


//
//  Internal support routine
//

LBO
FatIndexToLbo (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN FAT_ENTRY FatIndex
    )

/*++

Routine Description:

    This procedure translates a fat index into its corresponding lbo.

Arguments:

    FatStructureContext - Supplies the volume structure for the operation

    Entry - Supplies the fat entry to examine.

Return Value:

    The LBO for the input fat index is returned

--*/

{
    //
    //  The formula for translating an index into an lbo is to take the index subtract
    //  2 (because index values 0 and 1 are reserved) multiply that by the bytes per
    //  cluster and add the results to the first file area lbo.
    //

    return ((FatIndex-2) * FatBytesPerCluster(&FatStructureContext->Bpb))
           + FatFileAreaLbo(&FatStructureContext->Bpb);
}


//
//  Internal support routine
//

ARC_STATUS
FatSearchForDirent (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId,
    IN FAT_ENTRY DirectoriesStartingIndex,
    IN PFAT8DOT3 FileName,
    OUT PDIRENT Dirent,
    OUT PLBO Lbo,
    IN BOOLEAN IsDoubleSpace
    )

/*++

Routine Description:

    The procedure searches the indicated directory for a dirent that matches
    the input file name.

Arguments:

    FatStructureContext - Supplies the structure context for the operation

    DeviceId - Supplies the Device id for the operation

    DirectoriesStartingIndex - Supplies the fat index of the directory we are
        to search.  A value of zero indicates that we are searching the root directory

    FileName - Supplies the file name to look for.  The name must have already been
        biased by the 0xe5 transmogrification

    Dirent - The caller supplies the memory for a dirent and this procedure will
        fill in the dirent if one is located

    Lbo - Receives the Lbo of the dirent if one is located

    IsDoubleSpace - Indicates if the search is being done on a double space volume

Return Value:

    ESUCCESS is returned if the operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PDIRENT DirentBuffer;
    UCHAR Buffer[ 16 * sizeof(DIRENT) + 256 ];

    ULONG i;
    ULONG j;

    ULONG BytesPerCluster;
    FAT_ENTRY FatEntry;
    CLUSTER_TYPE ClusterType;

    DirentBuffer = (PDIRENT)ALIGN_BUFFER( &Buffer[0] );

    //****if (IsDoubleSpace) { (*FileName)[11] = 0; DbgPrint("FatSearchForDirent(%0x,%0x,%0x,\"%11s\",%0x,%0x,%0x)\n", FatStructureContext, DeviceId, DirectoriesStartingIndex, FileName, Dirent, Lbo, IsDoubleSpace); }

    //
    //  Check if this is the root directory that is being searched
    //

    if (DirectoriesStartingIndex == FAT_CLUSTER_AVAILABLE) {

        VBO Vbo;

        ULONG RootLbo = FatRootDirectoryLbo(&FatStructureContext->Bpb);
        ULONG RootSize = FatRootDirectorySize(&FatStructureContext->Bpb);

        //
        //  For the root directory we'll zoom down the dirents until we find
        //  a match, or run out of dirents or hit the never used dirent.
        //  The outer loop reads in 512 bytes of the directory at a time into
        //  dirent buffer.
        //

        for (Vbo = 0; Vbo < RootSize; Vbo += 16 * sizeof(DIRENT)) {

            *Lbo = Vbo + RootLbo;

            DiskRead( DeviceId, *Lbo, 16 * sizeof(DIRENT), DirentBuffer, IsDoubleSpace );

            //
            //  The inner loop cycles through the 16 dirents that we've just read in
            //

            for (i = 0; i < 16; i += 1) {

                //
                //  Check if we've found a non label match for file name, and if so
                //  then copy the buffer into the dirent and set the real lbo
                //  of the dirent and return
                //

                if (!FlagOn(DirentBuffer[i].Attributes, FAT_DIRENT_ATTR_VOLUME_ID ) &&
                    AreNamesEqual(&DirentBuffer[i].FileName, FileName)) {

                    for (j = 0; j < sizeof(DIRENT); j += 1) {

                        ((PCHAR)Dirent)[j] = ((PCHAR)DirentBuffer)[(i * sizeof(DIRENT)) + j];
                    }

                    *Lbo = Vbo + RootLbo + (i * sizeof(DIRENT));

                    return ESUCCESS;
                }

                if (DirentBuffer[i].FileName[0] == FAT_DIRENT_NEVER_USED) {

                    return ENOENT;
                }
            }
        }

        return ENOENT;
    }

    //
    //  If we get here we need to search a non-root directory.  The alrogithm
    //  for doing the search is that for each cluster we read in each dirent
    //  until we find a match, or run out of clusters, or hit the never used
    //  dirent.  First set some local variables and then get the cluster type
    //  of the first cluster
    //

    BytesPerCluster = FatBytesPerCluster( &FatStructureContext->Bpb );
    FatEntry = DirectoriesStartingIndex;
    ClusterType = FatInterpretClusterType( FatStructureContext, FatEntry );

    //
    //  Now loop through each cluster, and compute the starting Lbo for each cluster
    //  that we encounter
    //

    while (ClusterType == FatClusterNext) {

        LBO ClusterLbo;
        ULONG Offset;

        ClusterLbo = FatIndexToLbo( FatStructureContext, FatEntry );

        //
        //  Now for each dirent in the cluster compute the lbo, read in the dirent
        //  and check for a match, the outer loop reads in 512 bytes of dirents at
        //  a time.
        //

        for (Offset = 0; Offset < BytesPerCluster; Offset += 16 * sizeof(DIRENT)) {

            *Lbo = Offset + ClusterLbo;

            DiskRead( DeviceId, *Lbo, 16 * sizeof(DIRENT), DirentBuffer, IsDoubleSpace );

            //
            //  The inner loop cycles through the 16 dirents that we've just read in
            //

            for (i = 0; i < 16; i += 1) {

                //
                //  Check if we've found a for file name, and if so
                //  then copy the buffer into the dirent and set the real lbo
                //  of the dirent and return
                //

                if (AreNamesEqual(&DirentBuffer[i].FileName, FileName)) {

                    for (j = 0; j < sizeof(DIRENT); j += 1) {

                        ((PCHAR)Dirent)[j] = ((PCHAR)DirentBuffer)[(i * sizeof(DIRENT)) + j];
                    }

                    *Lbo = Offset + ClusterLbo + (i * sizeof(DIRENT));

                    return ESUCCESS;
                }

                if (DirentBuffer[i].FileName[0] == FAT_DIRENT_NEVER_USED) {

                    return ENOENT;
                }
            }
        }

        //
        //  Now that we've exhausted the current cluster we need to read
        //  in the next cluster.  So locate the next fat entry in the chain
        //  and go back to the top of the while loop.
        //

        LookupFatEntry( FatStructureContext, DeviceId, FatEntry, &FatEntry, IsDoubleSpace );

        ClusterType = FatInterpretClusterType(FatStructureContext, FatEntry);
    }

    return ENOENT;
}


//
//  Internal support routine
//

ARC_STATUS
FatCreateDirent (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId,
    IN FAT_ENTRY DirectoriesStartingIndex,
    IN PDIRENT Dirent,
    OUT PLBO Lbo
    )

/*++

Routine Description:

    This procedure allocates and write out a new dirent for a data file in the
    specified directory.  It assumes that the file name does not already exist.

Arguments:

    FatStructureContext - Supplies the structure context for the operation

    DeviceId - Supplies the device id for the operation

    DirectoriesStartingIndex - Supplies the fat index of the directory we are
        to use.  A value of zero indicates that we are using the root directory

    Dirent - Supplies a copy of the dirent to put out on the disk

    Lbo - Recieves the Lbo of where the dirent is placed

Return Value:

    ESUCCESS is returned if the operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    DIRENT TemporaryDirent;

    ULONG BytesPerCluster;
    FAT_ENTRY FatEntry;
    FAT_ENTRY PreviousEntry;

    //
    //  Check if this is the root directory that is being used
    //

    if (DirectoriesStartingIndex == FAT_CLUSTER_AVAILABLE) {

        VBO Vbo;

        ULONG RootLbo = FatRootDirectoryLbo(&FatStructureContext->Bpb);
        ULONG RootSize = FatRootDirectorySize(&FatStructureContext->Bpb);

        //
        //  For the root directory we'll zoom down the dirents until we find
        //  a the never used (or deleted) dirent, if we never find one then the
        //  directory is full.
        //

        for (Vbo = 0; Vbo < RootSize; Vbo += sizeof(DIRENT)) {

            *Lbo = Vbo + RootLbo;

            DiskRead( DeviceId, *Lbo, sizeof(DIRENT), &TemporaryDirent, FALSE );

            if ((TemporaryDirent.FileName[0] == FAT_DIRENT_DELETED) ||
                (TemporaryDirent.FileName[0] == FAT_DIRENT_NEVER_USED)) {

                //
                //  This dirent is free so write out the dirent, and we're done.
                //

                DiskWrite( DeviceId, *Lbo, sizeof(DIRENT), Dirent );

                return ESUCCESS;
            }
        }

        return ENOSPC;
    }

    //
    //  If we get here we need to use a non-root directory.  The alrogithm
    //  for doing the work is that for each cluster we read in each dirent
    //  until we hit a never used dirent or run out of clusters.  First set
    //  some local variables and then get the cluster type of the first
    //  cluster
    //

    BytesPerCluster = FatBytesPerCluster( &FatStructureContext->Bpb );
    FatEntry = DirectoriesStartingIndex;

    //
    //  Now loop through each cluster, and compute the starting Lbo for each cluster
    //  that we encounter
    //

    while (TRUE) {

        LBO ClusterLbo;
        ULONG Offset;

        ClusterLbo = FatIndexToLbo( FatStructureContext, FatEntry );

        //
        //  Now for each dirent in the cluster compute the lbo, read in the dirent
        //  and check if it is available.
        //

        for (Offset = 0; Offset < BytesPerCluster; Offset += sizeof(DIRENT)) {

            *Lbo = Offset + ClusterLbo;

            DiskRead( DeviceId, *Lbo, sizeof(DIRENT), &TemporaryDirent, FALSE );

            if ((TemporaryDirent.FileName[0] == FAT_DIRENT_DELETED) ||
                (TemporaryDirent.FileName[0] == FAT_DIRENT_NEVER_USED)) {

                //
                //  This dirent is free so write out the dirent, and we're done.
                //

                DiskWrite( DeviceId, *Lbo, sizeof(DIRENT), Dirent );

                return ESUCCESS;
            }
        }

        //
        //  Now that we've exhausted the current cluster we need to read
        //  in the next cluster.  So locate the next fat entry in the chain.
        //  Set previous entry to be the saved entry just in case we run off
        //  the chain and need to allocate another cluster.
        //

        PreviousEntry = FatEntry;

        LookupFatEntry( FatStructureContext, DeviceId, FatEntry, &FatEntry, FALSE );

        //
        //  If there isn't another cluster in the chain then we need to allocate a
        //  new cluster, and set previous entry to point to it.
        //

        if (FatInterpretClusterType(FatStructureContext, FatEntry) != FatClusterNext) {

            AllocateClusters( FatStructureContext, DeviceId, 1, PreviousEntry, &FatEntry );

            SetFatEntry( FatStructureContext, DeviceId, PreviousEntry, FatEntry );
        }
    }

    return ENOSPC;
}


//
//  Internal support routine
//

VOID
FatSetDirent (
    IN PFAT8DOT3 FileName,
    IN OUT PDIRENT Dirent,
    IN UCHAR Attributes
    )

/*++

Routine Description:

    This routine sets up the dirent

Arguments:

    FileName - Supplies the name to store in the dirent

    Dirent - Receives the current date and time

    Attributes - Supplies the attributes to initialize the dirent with

Return Value:

    None.

--*/

{
    PTIME_FIELDS Time;
    ULONG i;

    for (i = 0; i < sizeof(FAT8DOT3); i+= 1) {

        Dirent->FileName[i] = (*FileName)[i];
    }

    Dirent->Attributes = (UCHAR)(Attributes | FAT_DIRENT_ATTR_ARCHIVE);

    Time = ArcGetTime();

    Dirent->LastWriteTime.Time.DoubleSeconds = (USHORT)(Time->Second/2);
    Dirent->LastWriteTime.Time.Minute = Time->Minute;
    Dirent->LastWriteTime.Time.Hour = Time->Hour;

    Dirent->LastWriteTime.Date.Day = Time->Day;
    Dirent->LastWriteTime.Date.Month = Time->Month;
    Dirent->LastWriteTime.Date.Year = (USHORT)(Time->Year - 1980);

    return;
}


//
//  Internal support routine
//

ARC_STATUS
FatLoadMcb (
    IN ULONG FileId,
    IN VBO StartingVbo,
    IN BOOLEAN IsDoubleSpace
    )

/*++

Routine Description:

    This routine loads into the cached mcb table the the retrival information for
    the starting vbo.

Arguments:

    FileId - Supplies the FileId for the operation

    StartingVbo - Supplies the starting vbo to use when loading the mcb

    IsDoubleSpace - Indicates if the operation is being done on a double space volume

Return Value:

    ESUCCESS is returned if the operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    PFAT_STRUCTURE_CONTEXT FatStructureContext;
    PFAT_MCB Mcb;
    ULONG DeviceId;
    ULONG BytesPerCluster;

    FAT_ENTRY FatEntry;
    CLUSTER_TYPE ClusterType;
    VBO Vbo;

    //****if (IsDoubleSpace) { DbgPrint("FatLoadMcb(%0x,%0x,%0x)\n", FileId, StartingVbo, IsDoubleSpace); }

    //
    //  Preload some of the local variables
    //

    FileTableEntry = &BlFileTable[FileId];
    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
    Mcb = &FatStructureContext->Mcb;
    DeviceId = FileTableEntry->DeviceId;
    BytesPerCluster = FatBytesPerCluster(&FatStructureContext->Bpb);

    if (IsDoubleSpace) { DeviceId = FileId; }

    //
    //  Set the file id in the structure context, and also set the mcb to be initially
    //  empty
    //

    FatStructureContext->FileId = FileId;
    Mcb->InUse = 0;
    Mcb->Vbo[0] = 0;

    //
    //  Check if this is the root directory.  If it is then we build the single
    //  run mcb entry for the root directory.
    //

    if (FileTableEntry->u.FatFileContext.DirentLbo == 0) {

        Mcb->InUse = 1;
        Mcb->Lbo[0] = FatRootDirectoryLbo(&FatStructureContext->Bpb);
        Mcb->Vbo[1] = FatRootDirectorySize(&FatStructureContext->Bpb);

        return ESUCCESS;
    }

    //
    //  For all other files/directories we need to do some work. First get the fat
    //  entry and cluster type of the fat entry stored in the dirent
    //

    FatEntry = FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile;
    ClusterType = FatInterpretClusterType(FatStructureContext, FatEntry);

    //
    //  Scan through the fat until we reach the vbo we're after and then build the
    //  mcb for the file
    //

    for (Vbo = BytesPerCluster; Vbo < StartingVbo; Vbo += BytesPerCluster) {

        //
        //  Check if the file does not have any allocation beyond this point in which
        //  case the mcb we return is empty
        //

        if (ClusterType != FatClusterNext) {

            return ESUCCESS;
        }

        LookupFatEntry( FatStructureContext, DeviceId, FatEntry, &FatEntry, IsDoubleSpace );

        ClusterType = FatInterpretClusterType(FatStructureContext, FatEntry);
    }

    //
    //  We need to check again if the file does not have any allocation beyond this
    //  point in which case the mcb we return is empty
    //

    if (ClusterType != FatClusterNext) {

        return ESUCCESS;
    }

    //
    //  At this point FatEntry denotes another cluster, and it happens to be the
    //  cluster we want to start loading into the mcb.  So set up the first run in
    //  the mcb to be this cluster, with a size of a single cluster.
    //

    Mcb->InUse = 1;
    Mcb->Vbo[0] = Vbo - BytesPerCluster;
    Mcb->Lbo[0] = FatIndexToLbo( FatStructureContext, FatEntry );
    Mcb->Vbo[1] = Vbo;

    //
    //  Now we'll scan through the fat chain until we either exhaust the fat chain
    //  or we fill up the mcb
    //

    while (TRUE) {

        LBO Lbo;

        //
        //  Get the next fat entry and interpret its cluster type
        //

        LookupFatEntry( FatStructureContext, DeviceId, FatEntry, &FatEntry, IsDoubleSpace );

        ClusterType = FatInterpretClusterType(FatStructureContext, FatEntry);

        if (ClusterType != FatClusterNext) {

            return ESUCCESS;
        }

        //
        //  Now calculate the lbo for this cluster and determine if it
        //  is a continuation of the previous run or a start of a new run
        //

        Lbo = FatIndexToLbo(FatStructureContext, FatEntry);

        //
        //  It is a continuation if the lbo of the last run plus the current
        //  size of the run is equal to the lbo for the next cluster.  If it
        //  is a contination then we only need to add a cluster amount to the
        //  last vbo to increase the run size.  If it is a new run then
        //  we need to check if the run will fit, and if so then add in the
        //  new run.
        //

        if ((Mcb->Lbo[Mcb->InUse-1] + (Mcb->Vbo[Mcb->InUse] - Mcb->Vbo[Mcb->InUse-1])) == Lbo) {

            Mcb->Vbo[Mcb->InUse] += BytesPerCluster;

        } else {

            if ((Mcb->InUse + 1) >= FAT_MAXIMUM_MCB) {

                return ESUCCESS;
            }

            Mcb->InUse += 1;
            Mcb->Lbo[Mcb->InUse-1] = Lbo;
            Mcb->Vbo[Mcb->InUse] = Mcb->Vbo[Mcb->InUse-1] + BytesPerCluster;
        }
    }

    return ESUCCESS;
}


//
//  Internal support routine
//

ARC_STATUS
FatVboToLbo (
    IN ULONG FileId,
    IN VBO Vbo,
    OUT PLBO Lbo,
    OUT PULONG ByteCount,
    IN BOOLEAN IsDoubleSpace
    )

/*++

Routine Description:

    This routine computes the run denoted by the input vbo to into its
    corresponding lbo and also returns the number of bytes remaining in
    the run.

Arguments:

    Vbo - Supplies the Vbo to match

    Lbo - Recieves the corresponding Lbo

    ByteCount - Receives the number of bytes remaining in the run

    IsDoubleSpace - Indicates if the operation is being done on a double space volume

Return Value:

    ESUCCESS is returned if the operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PFAT_STRUCTURE_CONTEXT FatStructureContext;
    PFAT_MCB Mcb;
    ULONG i;

    //****if (IsDoubleSpace) { DbgPrint("FatVboToLbo(%0x,%0x,%0x,%0x,%0x)\n", FileId, Vbo, Lbo, ByteCount, IsDoubleSpace); }

    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)BlFileTable[FileId].StructureContext;
    Mcb = &FatStructureContext->Mcb;

    //
    //  Check if the mcb is for the correct file id and has the range we're asking for.
    //  If it doesn't then call load mcb to load in the right range.
    //

    if ((FileId != FatStructureContext->FileId) ||
        (Vbo < Mcb->Vbo[0]) || (Vbo >= Mcb->Vbo[Mcb->InUse])) {

        LoadMcb(FileId, Vbo, IsDoubleSpace);
    }

    //
    //  Now search for the slot where the Vbo fits in the mcb.  Note that
    //  we could also do a binary search here but because the run count
    //  is probably small the extra overhead of a binary search doesn't
    //  buy us anything
    //

    for (i = 0; i < Mcb->InUse; i += 1) {

        //
        //  We found our slot if the vbo we're after is less then the
        //  next mcb's vbo
        //

        if (Vbo < Mcb->Vbo[i+1]) {

            //
            //  Compute the corresponding lbo which is the stored lbo plus
            //  the difference between the stored vbo and the vbo we're
            //  looking up.  Also compute the byte count which is the
            //  difference between the current vbo we're looking up and
            //  the vbo for the next run.
            //

            *Lbo = Mcb->Lbo[i] + (Vbo - Mcb->Vbo[i]);

            *ByteCount = Mcb->Vbo[i+1] - Vbo;

            //
            //  and return success to our caller
            //

            return ESUCCESS;
        }
    }

    //
    //  If we really reach here we have an error, most likely because the file is
    //  not large enough for the requested Vbo.
    //

    return EINVAL;
}


//
//  Internal support routine
//

ARC_STATUS
FatIncreaseFileAllocation (
    IN ULONG FileId,
    IN ULONG ByteSize
    )

/*++

Routine Description:

    This procedure increases the file allocation to be at minimum the indicated
    size.

Arguments:

    FileId - Supplies the file id being processed

    ByteSize - Supplies the minimum byte size for file allocation

Return Value:

    ESUCCESS is returned if the operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    PFAT_STRUCTURE_CONTEXT FatStructureContext;
    ULONG DeviceId;
    ULONG BytesPerCluster;

    ULONG NumberOfClustersNeeded;
    FAT_ENTRY FatEntry;
    CLUSTER_TYPE ClusterType;
    FAT_ENTRY PreviousEntry;
    ULONG i;

    //
    //  Preload some of the local variables
    //

    FileTableEntry = &BlFileTable[FileId];
    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
    DeviceId = FileTableEntry->DeviceId;
    BytesPerCluster = FatBytesPerCluster(&FatStructureContext->Bpb);

    //
    //  Check if this is the root directory.  If it is then check if the allocation
    //  increase is already accommodated in the volume
    //

    if (FileTableEntry->u.FatFileContext.DirentLbo == 0) {

        if (FatRootDirectorySize(&FatStructureContext->Bpb) >= ByteSize) {

            return ESUCCESS;

        } else {

            return ENOSPC;
        }
    }

    //
    //  Compute the actual number of clusters needed to satisfy the request
    //  Also get the first fat entry and its cluster type from the dirent.
    //

    NumberOfClustersNeeded = (ByteSize + BytesPerCluster - 1) / BytesPerCluster;

    FatEntry = FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile;
    ClusterType = FatInterpretClusterType(FatStructureContext, FatEntry);

    //
    //  Previous Entry is as a hint to allocate new space and to show us where
    //  the end of the current fat chain is located
    //

    PreviousEntry = 2;

    //
    //  We loop for the number of clusters we need trying to go down the fat chain.
    //  When we exit i is either number of clusters in the file (if less then
    //  the number of clusters we need) or it is set equal to the number of clusters
    //  we need
    //

    for (i = 0; i < NumberOfClustersNeeded; i += 1) {

        if (ClusterType != FatClusterNext) { break; }

        PreviousEntry = FatEntry;

        LookupFatEntry( FatStructureContext, DeviceId, PreviousEntry, &FatEntry, FALSE );

        ClusterType = FatInterpretClusterType(FatStructureContext, FatEntry);
    }

    if (i >= NumberOfClustersNeeded) {

        return ESUCCESS;
    }

    //
    //  At this point previous entry points to the last entry and i contains the
    //  number of clusters in the file.  We now need to build up the allocation
    //

    AllocateClusters( FatStructureContext,
                      DeviceId,
                      NumberOfClustersNeeded - i,
                      PreviousEntry,
                      &FatEntry );

    //
    //  We have our additional allocation, so now figure out if we need to chain off of
    //  the dirent or it we already have a few clusters in the chain and we
    //  need to munge the fat.
    //

    if (FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile == FAT_CLUSTER_AVAILABLE) {

        FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile = FatEntry;

        DiskWrite( DeviceId,
                   FileTableEntry->u.FatFileContext.DirentLbo,
                   sizeof(DIRENT),
                   &FileTableEntry->u.FatFileContext.Dirent );

    } else {

        SetFatEntry( FatStructureContext, DeviceId, PreviousEntry, FatEntry );
    }

    return ESUCCESS;
}


//
//  Internal support routine
//

ARC_STATUS
FatTruncateFileAllocation (
    IN ULONG FileId,
    IN ULONG ByteSize
    )

/*++

Routine Description:

    This procedure decreases the file allocation to be at maximum the indicated
    size.

Arguments:

    FileId - Supplies the file id being processed

    ByteSize - Supplies the maximum byte size for file allocation

Return Value:

    ESUCCESS is returned if the operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    PFAT_STRUCTURE_CONTEXT FatStructureContext;
    ULONG DeviceId;
    ULONG BytesPerCluster;

    ULONG NumberOfClustersNeeded;
    FAT_ENTRY FatEntry;
    CLUSTER_TYPE ClusterType;
    FAT_ENTRY CurrentIndex;
    ULONG i;

    //
    //  Preload some of the local variables
    //

    FileTableEntry = &BlFileTable[FileId];
    FatStructureContext = (PFAT_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;
    DeviceId = FileTableEntry->DeviceId;
    BytesPerCluster = FatBytesPerCluster(&FatStructureContext->Bpb);

    //
    //  Check if this is the root directory.  If it is then noop this request
    //

    if (FileTableEntry->u.FatFileContext.DirentLbo == 0) {

        return ESUCCESS;
    }

    //
    //  Compute the actual number of clusters needed to satisfy the request
    //  Also get the first fat entry and its cluster type from the dirent
    //

    NumberOfClustersNeeded = (ByteSize + BytesPerCluster - 1) / BytesPerCluster;

    FatEntry = FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile;
    ClusterType = FatInterpretClusterType(FatStructureContext, FatEntry);

    //
    //  The current index variable is used to indicate where we extracted the current
    //  fat entry value from.  It has a value of 0 we got the fat entry from the
    //  dirent.
    //

    CurrentIndex = FAT_CLUSTER_AVAILABLE;

    //
    //  Now loop through the fat chain for the number of clusters needed.
    //  If we run out of the chain before we run out of clusters needed then the
    //  current allocation is already smaller than necessary.
    //

    for (i = 0; i < NumberOfClustersNeeded; i += 1) {

        //
        //  If we run out of the chain before we run out of clusters needed then the
        //  current allocation is already smaller than necessary.
        //

        if (ClusterType != FatClusterNext) { return ESUCCESS; }

        //
        //  Update the current index, and read in a new fat entry and interpret its
        //  type
        //

        CurrentIndex = FatEntry;

        LookupFatEntry( FatStructureContext, DeviceId, CurrentIndex, &FatEntry, FALSE );

        ClusterType = FatInterpretClusterType(FatStructureContext, FatEntry);
    }

    //
    //  If we get here then we've found that the current allocation is equal to or
    //  larger than what we want.  It is equal if the current cluster type does not
    //  point to another cluster.  The first thing we have to do is terminate the
    //  fat chain correctly.  If the current index is zero then we zero out the
    //  dirent, otherwise we need to set the value to be last cluster.
    //

    if (CurrentIndex == FAT_CLUSTER_AVAILABLE) {

        if (FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile != FAT_CLUSTER_AVAILABLE) {

            //
            //  By setting the dirent we set in a new date.
            //

            FatSetDirent( &FileTableEntry->u.FatFileContext.Dirent.FileName,
                          &FileTableEntry->u.FatFileContext.Dirent,
                          0 );

            FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile = FAT_CLUSTER_AVAILABLE;
            FileTableEntry->u.FatFileContext.Dirent.FileSize = 0;

            DiskWrite( DeviceId,
                       FileTableEntry->u.FatFileContext.DirentLbo,
                       sizeof(DIRENT),
                       &FileTableEntry->u.FatFileContext.Dirent );
        }

    } else {

        if (ClusterType != FatClusterLast) {

            SetFatEntry( FatStructureContext, DeviceId, CurrentIndex, FAT_CLUSTER_LAST );
        }
    }

    //
    //  Now while there are clusters left to deallocate then we need to go down the
    //  chain freeing up the clusters
    //

    while (ClusterType == FatClusterNext) {

        //
        //  Read in the value at the next fat entry and interpret its cluster type
        //

        CurrentIndex = FatEntry;

        LookupFatEntry( FatStructureContext, DeviceId, CurrentIndex, &FatEntry, FALSE );

        ClusterType = FatInterpretClusterType(FatStructureContext, FatEntry);

        //
        //  Now deallocate the cluster at the current index
        //

        SetFatEntry( FatStructureContext, DeviceId, CurrentIndex, FAT_CLUSTER_AVAILABLE );
    }

    return ESUCCESS;
}


//
//  Internal support routine
//

ARC_STATUS
FatAllocateClusters (
    IN PFAT_STRUCTURE_CONTEXT FatStructureContext,
    IN ULONG DeviceId,
    IN ULONG ClusterCount,
    IN FAT_ENTRY Hint,
    OUT PFAT_ENTRY AllocatedEntry
    )

/*++

Routine Description:

    This procedure allocates a new cluster, set its entry to be the last one,
    and zeros out the cluster.

Arguments:

    FatStructureContext - Supplies the structure context for the operation

    DeviceId - Supplies the device id for the operation

    ClusterCount - Supplies the number of clusters we need to allocate

    Hint - Supplies a hint to start from when looking for a free cluster

    AllocatedEntry - Receives the first fat index for the new allocated cluster chain

Return Value:

    ESUCCESS is returned if the operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    ULONG TotalClustersInVolume;
    ULONG BytesPerCluster;
    UCHAR BlankBuffer[512];

    FAT_ENTRY PreviousEntry;
    ULONG CurrentClusterCount;
    ULONG j;
    LBO ClusterLbo;
    ULONG i;

    //
    //  Load some local variables
    //

    TotalClustersInVolume = FatNumberOfClusters(&FatStructureContext->Bpb);
    BytesPerCluster = FatBytesPerCluster(&FatStructureContext->Bpb);
    RtlZeroMemory((PVOID)&BlankBuffer[0], 512);

    PreviousEntry = 0;
    CurrentClusterCount = 0;

    //
    //  For each cluster on the disk we'll do the following loop
    //

    for (j = 0; j < TotalClustersInVolume; j += 1) {

        FAT_ENTRY EntryToExamine;
        FAT_ENTRY FatEntry;

        //
        //  Check if the current allocation is enough.
        //

        if (CurrentClusterCount >= ClusterCount) {

            return ESUCCESS;
        }

        //
        //  Compute an entry to examine based on the loop iteration and our hint
        //

        EntryToExamine = (FAT_ENTRY)(((j + Hint - 2) % TotalClustersInVolume) + 2);

        //
        //  Read in the prospective fat entry and check if it is available.  If it
        //  is not available then continue looping.
        //

        LookupFatEntry( FatStructureContext, DeviceId, EntryToExamine, &FatEntry, FALSE );

        if (FatInterpretClusterType(FatStructureContext, FatEntry) != FatClusterAvailable) {

            continue;
        }

        //
        //  We have a free cluster, so put it at the end of the chain.
        //

        if (PreviousEntry == 0) {

            *AllocatedEntry = EntryToExamine;

        } else {

            SetFatEntry( FatStructureContext, DeviceId, PreviousEntry, EntryToExamine );
        }

        SetFatEntry( FatStructureContext, DeviceId, EntryToExamine, FAT_CLUSTER_LAST );

        //
        //  Now we need to go through and zero out the data in the cluster that we've
        //  just allocated.  Because all clusters must be a multiple of dirents we'll
        //  do it a dirent at a time.
        //

        ClusterLbo = FatIndexToLbo( FatStructureContext, EntryToExamine );

        for (i = 0; i < BytesPerCluster; i += 512) {

            DiskWrite( DeviceId, ClusterLbo + i, 512, BlankBuffer );
        }

        //
        //  Before we go back to the top of the loop we need to update the
        //  previous entry so that it points to the end of the current chain and
        //  also i because we've just added another cluster.
        //

        PreviousEntry = EntryToExamine;
        CurrentClusterCount += 1;
    }

    return ENOSPC;
}


//
//  Internal support routine
//

VOID
FatFirstComponent (
    IN OUT PSTRING String,
    OUT PFAT8DOT3 FirstComponent
    )

/*++

Routine Description:

    Convert a string into fat 8.3 format and advance the input string
    descriptor to point to the next file name component.

Arguments:

    InputString - Supplies a pointer to the input string descriptor.

    Output8dot3 - Supplies a pointer to the converted string.

Return Value:

    None.

--*/

{
    ULONG Extension;
    ULONG Index;

    //
    //  Fill the output name with blanks.
    //

    for (Index = 0; Index < 11; Index += 1) { (*FirstComponent)[Index] = ' '; }

    //
    //  Copy the first part of the file name up to eight characters and
    //  skip to the end of the name or the input string as appropriate.
    //

    for (Index = 0; Index < String->Length; Index += 1) {

        if ((String->Buffer[Index] == '\\') || (String->Buffer[Index] == '.')) {

            break;
        }

        if (Index < 8) {

            (*FirstComponent)[Index] = (CHAR)ToUpper(String->Buffer[Index]);
        }
    }

    //
    //  Check if the end of the string was reached, an extension was specified,
    //  or a subdirectory was specified..
    //

    if (Index < String->Length) {

        if (String->Buffer[Index] == '.') {

            //
            //  Skip over the extension separator and add the extension to
            //  the file name.
            //

            Index += 1;
            Extension = 8;

            while (Index < String->Length) {

                if (String->Buffer[Index] == '\\') {

                    break;
                }

                if (Extension < 11) {

                    (*FirstComponent)[Extension] = (CHAR)ToUpper(String->Buffer[Index]);
                    Extension += 1;
                }

                Index += 1;
            }
        }
    }

    //
    //  Now we'll bias the first component by the 0xe5 factor so that all our tests
    //  to names on the disk will be ready for a straight 11 byte comparison
    //

    if ((*FirstComponent)[0] == 0xe5) {

        (*FirstComponent)[0] = FAT_DIRENT_REALLY_0E5;
    }

    //
    //  Update string descriptor.
    //

    String->Buffer += Index;
    String->Length -= (USHORT)Index;

    return;
}


//
//  Internal support routine
//

VOID
FatDirToArcDir (
    IN PDIRENT FatDirent,
    OUT PDIRECTORY_ENTRY ArcDirent
    )

/*++

Routine Description:

    This routine converts a FAT directory entry into an ARC
    directory entry.

Arguments:

    FatDirent - supplies a pointer to a FAT directory entry.

    ArcDirent - supplies a pointer to an ARC directory entry.

Return Value:

    None.

--*/

{
    ULONG i, e;

    //
    //  clear info area
    //

    RtlZeroMemory( ArcDirent, sizeof(DIRECTORY_ENTRY) );

    //
    //  check the directory flag
    //

    if (FlagOn( FatDirent->Attributes, FAT_DIRENT_ATTR_DIRECTORY )) {

        SetFlag( ArcDirent->FileAttribute, ArcDirectoryFile );
    }

    //
    //  check the read-only flag
    //

    if (FlagOn( FatDirent->Attributes, FAT_DIRENT_ATTR_READ_ONLY )) {

        SetFlag( ArcDirent->FileAttribute, ArcReadOnlyFile );
    }

    //
    //  clear name string
    //

    RtlZeroMemory( ArcDirent->FileName, 32 );

    //
    //  copy first portion of file name
    //

    for (i = 0;  (i < 8) && (FatDirent->FileName[i] != ' ');  i += 1) {

        ArcDirent->FileName[i] = FatDirent->FileName[i];
    }

    //
    //  check for an extension
    //

    if ( FatDirent->FileName[8] != ' ' ) {

        //
        //  store the dot char
        //

        ArcDirent->FileName[i++] = '.';

        //
        //  add the extension
        //

        for (e = 8;  (e < 11) && (FatDirent->FileName[e] != ' ');  e += 1) {

            ArcDirent->FileName[i++] = FatDirent->FileName[e];
        }
    }

    //
    //  set file name length before returning
    //

    ArcDirent->FileNameLength = i;

    return;
}


#ifdef DBLSPACE_LEGAL


/*++

    The remainder of this file contains the double space additions.
    Logically the caller see a virtual fat partition (vfp), which
    double space implements on top of a compressed volume file (cvf).

    All user reads to the Vfp are translated to a read (with possibly
    some decompression) of the Cvf.  In our implementation the Cvf
    is just an opened file handled by FatBoot.  So double space
    can read the cvf by simply calling BlSeek/BlRead using the FileId
    for the Cvf which is fed straight back into FatBoot.

    Pictorially here is how things look:

                                     CVF
                                    +------------+
                                    |            |
                                    | Cvf Header |
                                    |            |
                                    +------------+
                                    |            |
                                    | Cvf Bitmap |
                                    |            |
                                    +------------+
                                    |            |
                                    | Cvf Fat    |
                                    | Extensions |
         VFP                        |            |
        +-------------+             +------------+
        |             |             |            |
        | Boot Sector |             | Dos Boot   |
        | and         |             | Sector and |
        | Reserved    |             | Reserved   |
        | Area        |             | Area3      |
        |             |             |            |
        +-------------+             +------------+
        |             | ---read---> |            |
        | First       |             | Dos Fat    |
        | Fat         |             |            |
        |             |             +------------+
        |.............|         +-> |            |
        |             |         |   | Dos Root   |
        | Secondary   |         |   | Directory  |
        | Fats        |         |   |            |
        |             |         |   +------------+
        +-------------+         |   |            |
        |             | ---read-+   | Cvf Heap   |
        | Root        |             |            |
        | Directory   |         +-> |            |
        |             |         |   +------------+
        +-------------+         |
        |             | ---read-+
        | File Area   |
        |             |
        |             |
        +-------------+

    The boot file system never tries to read from the Vfp Boot and
    Reserved Sector area or the secondary fats so none of the logic
    needs to worry about reading those areas.

--*/

//
//  Low level I/O procedure prototypes for reading the Vfp.  This
//  makes the Vfp look like one big partition without compression.
//  The other prototype is for reading the Cvf.
//

ARC_STATUS
DblsReadVfp (
    IN ULONG FileId,
    IN LBO Lbo,
    IN ULONG ByteCount,
    IN PUCHAR Buffer
    );

ARC_STATUS
DblsReadCvf (
    IN ULONG CvfId,
    IN LBO Lbo,
    IN ULONG ByteCount,
    IN PVOID Buffer
    );

#define ReadVfp(A,B,C,D) { ARC_STATUS _s;                       \
    if ((_s = DblsReadVfp(A,B,C,D)) != ESUCCESS) { return _s; } \
}

#define ReadCvf(A,B,C,D) { ARC_STATUS _s;                       \
    if ((_s = DblsReadCvf(A,B,C,D)) != ESUCCESS) { return _s; } \
}

//
//  The following macro is used to translate an Lbo relative to the start
//  of the file area into a cluster index.
//
//      ULONG
//      DblsLboToIndex (
//          IN PDBLS_STRUCTURE_CONTEXT Dscb,
//          IN LBO Lbo
//          );
//

#define DblsLboToIndex(D,L) (                  \
    ((L) / (D)->VfpLayout.BytesPerCluster) + 2 \
)

//
//      LBO
//      DblsGetHeapLbo (
//          IN CVF_FAT_EXTENSIONS FatExtensions
//          );
//
//      ULONG
//      DblsGetCompressedDataLength (
//          IN CVF_FAT_EXTENSIONS FatExtensions
//          );
//
//      ULONG
//      DblsGetUncompressedDatalength (
//          IN CVF_FAT_EXTENSIONS FatExtensions
//          );
//

#define DblsGetHeapLbo(F) (            \
    ((F).CvfHeapLbnMinus1 + 1) * 0x200 \
)

#define DblsGetCompressedDataLength(F) (           \
    ((F).CompressedSectorLengthMinus1 + 1) * 0x200 \
)

#define DblsGetUncompressedDataLength(F) (           \
    ((F).UncompressedSectorLengthMinus1 + 1) * 0x200 \
)

ULONG
DblsMrcfDecompress (
    PUCHAR UncompressedBuffer,
    ULONG UncompressedLength,
    PUCHAR CompressedBuffer,
    ULONG CompressedLength,
    PMRCF_DECOMPRESS WorkSpace
    );

//
// Define global data.
//

BOOLEAN DblsBootInitialized = FALSE;

//
// File entry table - This is a structure that provides entry to the DBLS
//      file system procedures. It is exported when a FAT file structure
//      is recognized.
//

BL_DEVICE_ENTRY_TABLE DblsDeviceEntryTable;


PBL_DEVICE_ENTRY_TABLE
IsDblsFileStructure (
    IN ULONG CvfId,
    IN PVOID StructureContext
    )

/*++

Routine Description:

    This routine determines if the partition on the specified opened file
    contains a Double Space file system volume.

    This routine does minimal sanity checking because the logic being that
    if we're being called to boot a double space system then we know what
    we doing and any little small error (like bad signatures) shouldn't
    necessarily keep us from booting.  If the system (i.e., the disk is
    really bad then we're not going to boot anyway.

Arguments:

    CvfId - Supplies the file table index for the CVF on which
        read operations are to be performed.

    StructureContext - Supplies a pointer to a DBLS file structure context.

Return Value:

    A pointer to the DBLS entry table is returned if the file is
    recognized as containing a DBLS volume.  Otherwise, NULL is returned.

--*/

{
    PDBLS_STRUCTURE_CONTEXT Dscb;

    //****DbgPrint("IsDblsFileStructure(%0x,%0x)\n", CvfId, StructureContext);
    //****DbgWaitForEnter("Start of IsDblsFileStructure");

    //
    //  Clear the file system context block.
    //

    Dscb = (PDBLS_STRUCTURE_CONTEXT)StructureContext;
    RtlZeroMemory(&Dscb->FatStructureContext, sizeof(FAT_STRUCTURE_CONTEXT));

    //
    //  Figure out the size and shape of the Cvf
    //

    {
        ULONG CvfSize;
        PACKED_CVF_HEADER PackedCvfHeader;

        //
        //  Get the size of the Cvf.  Rather than call back to get the file
        //  information we'll just incestuously know that it is stored in the
        //  Dirent field of the Fat File Context of the Cvf's file table entry.
        //

        CvfSize = BlFileTable[ CvfId ].u.FatFileContext.Dirent.FileSize;

        //
        //  Read in the Cvf Header, unpack the Cvf and compute
        //  the Cvf layout
        //

        if (DblsReadCvf( CvfId, 0, sizeof(PACKED_CVF_HEADER), &PackedCvfHeader) != ESUCCESS) {

            return NULL;
        }

        CvfUnpackCvfHeader( &Dscb->CvfHeader, &PackedCvfHeader );

        CvfLayout( &Dscb->CvfLayout, &Dscb->CvfHeader, CvfSize );
    }

    //
    //  Figure out the size and shape of the Vfp
    //

    {
        PACKED_BIOS_PARAMETER_BLOCK PackedBpb;

        //
        //  Read in the Bpb from the Cvf and compute the
        //  Vfp layout
        //

        if (DblsReadCvf( CvfId,
                         Dscb->CvfLayout.DosBootSector.Lbo + FIELD_OFFSET(PACKED_BOOT_SECTOR, PackedBpb),
                         sizeof(PACKED_BIOS_PARAMETER_BLOCK),
                         &PackedBpb ) != ESUCCESS) {

            return NULL;
        }

        FatUnpackBios( &Dscb->FatStructureContext.Bpb, &PackedBpb );

        //
        //  Now setup the vfp layout fields
        //

        Dscb->VfpLayout.Fat.Lbo        = Dscb->FatStructureContext.Bpb.ReservedSectors *
                                         Dscb->FatStructureContext.Bpb.BytesPerSector;
        Dscb->VfpLayout.Fat.Allocation =
        Dscb->VfpLayout.Fat.Size       = Dscb->FatStructureContext.Bpb.Fats *
                                         FatBytesPerFat( &Dscb->FatStructureContext.Bpb );

        Dscb->VfpLayout.RootDirectory.Lbo        = FatRootDirectoryLbo( &Dscb->FatStructureContext.Bpb );
        Dscb->VfpLayout.RootDirectory.Allocation =
        Dscb->VfpLayout.RootDirectory.Size       = FatRootDirectorySize( &Dscb->FatStructureContext.Bpb );

        Dscb->VfpLayout.FileArea.Lbo        = FatFileAreaLbo( &Dscb->FatStructureContext.Bpb );
        Dscb->VfpLayout.FileArea.Allocation =
        Dscb->VfpLayout.FileArea.Size       = FatNumberOfClusters( &Dscb->FatStructureContext.Bpb ) *
                                              FatBytesPerCluster( &Dscb->FatStructureContext.Bpb );

        Dscb->VfpLayout.BytesPerCluster = FatBytesPerCluster( &Dscb->FatStructureContext.Bpb );
    }

    if (!DblsBootInitialized) {

        DblsBootInitialized = TRUE;

        //
        //  Now that we know it is a real double space partition we'll
        //  allocate some heap storage for our decompression algorithm,
        //  and also some scratch space for buffering the file data area.
        //

        Dscb->DecompressWorkSpace = BlAllocateHeap( sizeof(MRCF_DECOMPRESS) );
        Dscb->CompressedBuffer    = BlAllocateHeap( 8192 );
        Dscb->UncompressedBuffer  = BlAllocateHeap( 8192 );

        //
        //  Now set up the cached fat extensions area
        //

        Dscb->CachedFatExtensionsLbo = 0;
        Dscb->CachedFatExtensions = BlAllocateHeap( sizeof(CVF_FAT_EXTENSIONS) * 512 );

        //
        //  Initialize the file entry table and return the address of the table.
        //

        DblsDeviceEntryTable.Open               = DblsOpen;
        DblsDeviceEntryTable.Close              = FatClose;
        DblsDeviceEntryTable.Read               = DblsRead;
        DblsDeviceEntryTable.Seek               = FatSeek;
        DblsDeviceEntryTable.GetFileInformation = FatGetFileInformation;
    }

    return &DblsDeviceEntryTable;
}


ARC_STATUS
DblsOpen (
    IN PCHAR FileName,
    IN OPEN_MODE OpenMode,
    IN PULONG FileId
    )

/*++

Routine Description:

    This routine searches the device for a file matching FileName.  If a
    match is found the dirent for the file is saved and the file is opened.

Arguments:

    FileName - Supplies a pointer to a zero terminated file name.

    OpenMode - Supplies the mode of the open.

    FileId - Supplies a pointer to a variable that specifies the file
        table entry that is to be filled in if the open is successful.

Return Value:

    ESUCCESS is returned if the open operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PBL_FILE_TABLE FileTableEntry;
    PDBLS_STRUCTURE_CONTEXT DblsStructureContext;

    FAT_ENTRY CurrentDirectoryIndex;
    BOOLEAN IsDirectory;

    STRING PathName;
    FAT8DOT3 Name;

    //****DbgPrint("DblsOpen(\"%s\", %0x, %0x)\n", FileName, OpenMode, *FileId);
    //****DbgWaitForEnter("Start of DblsOpen");

    //
    //  Load our local variables
    //

    FileTableEntry = &BlFileTable[*FileId];
    DblsStructureContext = (PDBLS_STRUCTURE_CONTEXT)FileTableEntry->StructureContext;

    //
    // Indicate that no fat extensions are cached. We need to do this here
    // because we might be openening a file on removable media and we
    // have no volume tracking mechanism here -- don't want to use cached
    // data from a volume that isn't currently in the drive.
    //

    DblsStructureContext->CachedFatExtensionsLbo = 0;

    //
    //  Construct a file name descriptor from the input file name
    //

    RtlInitString( &PathName, FileName );

    //
    //  Initialize the file table entry by zeroing out the file context, and
    //  setting the position to zero.
    //

    RtlZeroMemory(&FileTableEntry->u.FatFileContext, sizeof(FAT_FILE_CONTEXT));

    FileTableEntry->Position.LowPart = 0;
    FileTableEntry->Position.HighPart = 0;

    //
    //  All of our work starts from the root directory which is denoted
    //  by an index value of zero.
    //

    CurrentDirectoryIndex = 0;
    IsDirectory = TRUE;

    //
    //  Check if the user simply wants to open the root directory
    //

    if ((PathName.Buffer[0] == '\\') && (PathName.Length == 1)) {

        //
        //  We are opening the root directory.  So construct
        //  a file name for it in the file table entry.
        //

        FileTableEntry->FileNameLength = 1;
        FileTableEntry->FileName[0] = PathName.Buffer[0];

        //
        //  Root dirent is all zeroes with a directory attribute.
        //  Including an LBO of zero, which we already zeroed.  So
        //  all that is left to do is set the directory attribute.
        //

        FileTableEntry->u.FatFileContext.Dirent.Attributes = FAT_DIRENT_ATTR_DIRECTORY;

    } else {

        //
        //  We are not opening the root directory.
        //
        //  While the path name has some characters in it we'll go through
        //  our loop which extracts the first part of the path name and
        //  searches the current directory for an entry.  If what we find
        //  is a directory and we're not done cracking the path name we'll
        //  continue looping.
        //

        while ((PathName.Length > 0) && IsDirectory) {

            //
            //  Extract the first component and search the directory for a
            //  match, but first skip over the backslash and copy the first
            //  part to the file name buffer in the file table entry.
            //

            if (PathName.Buffer[0] == '\\') {

                PathName.Buffer +=1;
                PathName.Length -=1;
            }

            for (FileTableEntry->FileNameLength = 0;
                 (((USHORT)FileTableEntry->FileNameLength < PathName.Length) &&
                  (PathName.Buffer[FileTableEntry->FileNameLength] != '\\'));
                 FileTableEntry->FileNameLength += 1) {

                FileTableEntry->FileName[FileTableEntry->FileNameLength] =
                                             PathName.Buffer[FileTableEntry->FileNameLength];
            }

            FatFirstComponent( &PathName, &Name );

            SearchForDirent( &DblsStructureContext->FatStructureContext,
                             *FileId,
                             CurrentDirectoryIndex,
                             &Name,
                             &FileTableEntry->u.FatFileContext.Dirent,
                             &FileTableEntry->u.FatFileContext.DirentLbo,
                             TRUE );

            //
            //  We have a match now check to see if it is a directory
            //

            if (IsDirectory = BooleanFlagOn( FileTableEntry->u.FatFileContext.Dirent.Attributes,
                                             FAT_DIRENT_ATTR_DIRECTORY )) {

                CurrentDirectoryIndex = FileTableEntry->u.FatFileContext.Dirent.FirstClusterOfFile;
            }
        }

        //
        //  If the path name length is not zero then we were trying to crack a path
        //  with an nonexistent (or non directory) name in it.  For example, we tried
        //  to crack a\b\c\d and b is not a directory or does not exist (then the path
        //  name will still contain c\d).
        //

        if (PathName.Length != 0) {

            return ENOTDIR;
        }
    }

    //
    //  At this point we're done cracking the path name, and the Fat file
    //  context is fully setup.  Now all that is left to decide is what
    //  type open the user wants to do and if it is compatible with what
    //  we just found.
    //

    //
    //  Check if the last component is a directory
    //

    if (IsDirectory) {

        //
        //  For an existing directory the only valid open mode is OpenDirectory
        //  all other modes return an error
        //

        switch (OpenMode) {

        case ArcOpenDirectory:

            FileTableEntry->Flags.Open = 1;
            FileTableEntry->Flags.Read = 1;

            return ESUCCESS;

        case ArcOpenReadOnly:
        case ArcOpenWriteOnly:
        case ArcOpenReadWrite:
        case ArcCreateWriteOnly:
        case ArcCreateReadWrite:
        case ArcSupersedeWriteOnly:
        case ArcSupersedeReadWrite:

            return EISDIR;

        case ArcCreateDirectory:

            return EACCES;
        }
    }

    //
    //  If we get here then we have an existing file that is being opened.
    //  We can only open existing files for read access.
    //

    switch (OpenMode) {

    case ArcOpenReadOnly:

        FileTableEntry->Flags.Open = 1;
        FileTableEntry->Flags.Read = 1;

        {
            //**** this is just a hack to make everything work.  Don't know why but this works...
            ULONG FileId2;
            BlOpen(BlFileTable[BlFileTable[*FileId].DeviceId].DeviceId, "\\", ArcOpenReadOnly, &FileId2);
            BlClose(FileId2);
        }

        return ESUCCESS;

    case ArcOpenWriteOnly:
    case ArcOpenReadWrite:
    case ArcSupersedeWriteOnly:
    case ArcSupersedeReadWrite:

        return EROFS;

    case ArcCreateWriteOnly:
    case ArcCreateReadWrite:

        return EACCES;

    case ArcOpenDirectory:
    case ArcCreateDirectory:

        return ENOTDIR;
    }
}


ARC_STATUS
DblsRead (
    IN ULONG FileId,
    OUT PVOID Buffer,
    IN ULONG Length,
    OUT PULONG Transfer
    )

/*++

Routine Description:

    This routine reads data from the specified file.

Arguments:

    FileId - Supplies the file table index.

    Buffer - Supplies a pointer to the buffer that receives the data
        read.

    Length - Supplies the number of bytes that are to be read.

    Transfer - Supplies a pointer to a variable that receives the number
        of bytes actually transfered.

Return Value:

    ESUCCESS is returned if the read operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PBL_FILE_TABLE FileTableEntry;

    //****DbgPrint("DblsRead(%0x,%0x,%0x,%0x)\n", FileId, Buffer, Length, Transfer);
    //****DbgWaitForEnter("Start of DblsRead");

    //
    //  Load the local variables
    //

    FileTableEntry = &BlFileTable[FileId];

    //
    //  Clear the transfer count
    //

    *Transfer = 0;

    //
    //  Read in runs (i.e., bytes) until the byte count goes to zero
    //

    while (Length > 0) {

        LBO Lbo;
        ULONG CurrentRunByteCount;
        LONG ReadSize;

        //
        //  Lookup the corresponding Lbo and run length for the current position
        //  (i.e., Vbo).
        //

        if (FatVboToLbo( FileId, FileTableEntry->Position.LowPart, &Lbo, &CurrentRunByteCount, TRUE ) != ESUCCESS) {

            //****DbgPrint("DblsRead #1 %0x", *Transfer); DbgWaitForEnter("");
            return ESUCCESS;
        }

        //
        //  Now compute the size of the next read.  It will be the minimum
        //  of what the caller wants, the current run length, and the
        //  end of the file.
        //

        ReadSize = Minimum( Length, CurrentRunByteCount );

        if (((ULONG)ReadSize + FileTableEntry->Position.LowPart) >
            FileTableEntry->u.FatFileContext.Dirent.FileSize) {

            ReadSize = FileTableEntry->u.FatFileContext.Dirent.FileSize -
                       FileTableEntry->Position.LowPart;

            //
            //  If the readjusted read length is now zero then we're done.
            //

            if (ReadSize <= 0) {

                //****DbgPrint("DblsRead #2 %0x", *Transfer); DbgWaitForEnter("");
                return ESUCCESS;
            }

            //
            //  By also setting length here we'll make sure that this is our last
            //  read
            //

            Length = ReadSize;
        }

        //
        //  Issue the read
        //

        ReadVfp(FileId, Lbo, ReadSize, Buffer);

        //
        //  Update the remaining length
        //

        Length -= ReadSize;

        //
        //  Update the current position and the number of bytes transfered
        //

        FileTableEntry->Position.LowPart += ReadSize;
        *Transfer += ReadSize;

        //
        //  Update buffer to point to the next byte location to fill in
        //

        Buffer = (PCHAR)Buffer + ReadSize;
    }

    //
    //  If we get here then remaining sector count is zero so we can
    //  return success to our caller
    //

    //****DbgPrint("DblsRead #3 %0x", *Transfer); DbgWaitForEnter("");
    return ESUCCESS;
}


ARC_STATUS
DblsInitialize (
    VOID
    )

/*++

Routine Description:

    This routine initializes the DoubleSpace boot filesystem.

    This involves setting a global flag to indicate that DoubleSpace
    is not initialized.  This causes IsDblsFileStructure to allocate
    buffers the first time someone tries to open a DoubleSpaced file.

Arguments:

    None.

Return Value:

    ESUCCESS.

--*/

{
    DblsBootInitialized = FALSE;

    return ESUCCESS;
}


//
//  Internal support routine
//

ARC_STATUS
DblsReadVfp (
    IN ULONG FileId,
    IN LBO Lbo,
    IN ULONG ByteCount,
    IN PUCHAR Buffer
    )

/*++

Routine Description:

    This routine reads in zero or more bytes from the specified Vfp.
    It makes the Vfp look like one big partition without compression.

    Because of the way booting work we know we will only be asked to
    read from the Primary Fat, Root Directory, and File Data area.
    And only one of the areas at a time (e.g., a request will never
    overlap between the Root Directory and the File Data area).

Arguments:

    FileId - Supplies the Id for the file.

    Lbo - Supplies the LBO to start reading from.

    ByteCount - Supplies the number of bytes to read.

    Buffer - Supplies a pointer to the buffer to read the bytes into.

Return Value:

    ESUCCESS is returned if the read operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    PDBLS_STRUCTURE_CONTEXT Dscb;
    ULONG CvfId;

    //****DbgPrint("DblsReadVfp(%0x,%0x,%0x,%0x)\n", FileId, Lbo, ByteCount, Buffer);

    //
    //  Special case where we really don't have to read anything
    //

    if (ByteCount == 0) { return ESUCCESS; }

    //
    //  Get the context structure for the double space partition, and the cvf id
    //

    Dscb = (PDBLS_STRUCTURE_CONTEXT)BlFileTable[ FileId ].StructureContext;
    CvfId = BlFileTable[ FileId ].DeviceId;

    //
    //  Check if we are starting out read in the FAT.  We'll assume that
    //  if we starting reading from the FAT then we're going to complete
    //  our read from the fat.
    //

    if ((Dscb->VfpLayout.Fat.Lbo <= Lbo) && (Lbo < Dscb->VfpLayout.RootDirectory.Lbo)) {

        return DblsReadCvf( CvfId,
                            Dscb->CvfLayout.DosFat.Lbo + (Lbo - Dscb->VfpLayout.Fat.Lbo),
                            ByteCount,
                            Buffer );
    }

    //
    //  Now check if we are reading from the root directory.  Again if we
    //  are starting the read within this area then we'll assume we're
    //  going to only read from this area.
    //

    if ((Dscb->VfpLayout.RootDirectory.Lbo <= Lbo) && (Lbo < Dscb->VfpLayout.FileArea.Lbo)) {

        return DblsReadCvf( CvfId,
                            Dscb->CvfLayout.DosRootDirectory.Lbo + (Lbo - Dscb->VfpLayout.RootDirectory.Lbo),
                            ByteCount,
                            Buffer );
    }

    //
    //  If we get here we're reading from the data area of the Vfp
    //  so we have some real work to do (i.e., decompressing data and
    //  so forth).
    //

    {
        ULONG AmountRead;
        ULONG RelativeOffset;

        ULONG StartingClusterRelativeOffset;
        ULONG EndingClusterRelativeOffset;

        ULONG i;

        //
        //  Check if the amount to read puts us beyond the File Area.  We
        //  handle this by simply setting amount read to the mininum
        //  of the input byte count or the size that we are able to read.
        //

        RelativeOffset = Lbo - Dscb->VfpLayout.FileArea.Lbo;

        AmountRead = Minimum( ByteCount, Dscb->VfpLayout.FileArea.Allocation - RelativeOffset );

        //
        //  Compute the relative offset of our read with respect to
        //  to the start of the file area, and calculate the relative
        //  offset (from the start of the file area) of the starting
        //  cluster and ending cluster.  For the starting value we take
        //  the index for the first byte and truncate it to a cluster
        //  boundary.  For the ending value we take index of the last
        //  byte we write, truncate it to a cluster.
        //
        //
        //                  Vfp
        //                 +------+
        //                 |      | <- StartClusterRelativeOff - i(0)
        //     Buffer      |      |
        //    +------+     |      |
        //    |      | --> | .... | <- RelativeOffset ---------- Start(0)
        //    |      |     |      |
        //    |      |     |      |
        //    |      |     |------|
        //    |      |     |      | <--------------------------- Stop(0) i(1) Start(1)
        //    |      |     |      |
        //    |      |     |      |
        //    |      |     |      |
        //    |      |     |      |
        //    |      |     |      |
        //    |      |     |------|
        //    |      |     |      | <- EndClusterRelativeOff --- Stop(1) i(2) Start(2)
        //    |      |     |      |
        //    |      | --> | .... |
        //    +------+     |      | <--------------------------- Stop(2)
        //                 |      |
        //                 |      |
        //                 +------+
        //

        StartingClusterRelativeOffset = RelativeOffset & ~(Dscb->VfpLayout.BytesPerCluster - 1);
        EndingClusterRelativeOffset = (RelativeOffset + AmountRead - 1) & ~(Dscb->VfpLayout.BytesPerCluster - 1);

        //
        //  The following loop considers each cluster that overlap with
        //  the user buffer.  The loop index "i" is the offset within the
        //  file area of the current cluster under consideration
        //

        for (i = StartingClusterRelativeOffset;
             i <= EndingClusterRelativeOffset;
             i += Dscb->VfpLayout.BytesPerCluster) {

            ULONG Start;
            ULONG Stop;

            CVF_FAT_EXTENSIONS FatExtension;

            //
            //  Calculate the relative offsets of the overlap between the
            //  user buffer and this cluster.
            //

            Start = Maximum( RelativeOffset, i );
            Stop = Minimum( RelativeOffset + AmountRead, i + Dscb->VfpLayout.BytesPerCluster );

            //
            //  So now Start and Stop are within the same cluster and provide
            //  a boundary for our transfer.  So now compute the cluster index
            //  of this cluster and read in its fat extension.
            //

            {
                ULONG ClusterIndex;
                LBO FatExtensionLbo;

                ClusterIndex = DblsLboToIndex( Dscb, Start );

                FatExtensionLbo = Dscb->CvfLayout.CvfFatExtensions.Lbo +
                                  ((Dscb->CvfHeader.CvfFatFirstDataEntry + ClusterIndex) * sizeof(CVF_FAT_EXTENSIONS));

                //
                //  Check if it is not already in our cache
                //

                if ((FatExtensionLbo < Dscb->CachedFatExtensionsLbo) ||
                    (FatExtensionLbo >= Dscb->CachedFatExtensionsLbo + (sizeof(CVF_FAT_EXTENSIONS) * 512))) {

                    ReadCvf( CvfId, FatExtensionLbo, sizeof(CVF_FAT_EXTENSIONS) * 512, Dscb->CachedFatExtensions )
                    Dscb->CachedFatExtensionsLbo = FatExtensionLbo;
                }

                FatExtension = Dscb->CachedFatExtensions[ (FatExtensionLbo - Dscb->CachedFatExtensionsLbo) / sizeof(CVF_FAT_EXTENSIONS) ];
            }

            //
            //  Now if the cluster is not is use we do not have to read in any
            //  data but can simply zero out the range in the user buffer
            //

            if (!FatExtension.IsEntryInUse) {

                RtlZeroMemory( &Buffer[ Start - RelativeOffset ], Stop - Start );

            } else {

                ULONG CompressedDataLength;
                ULONG UncompressedDataLength;
                LBO ClusterLbo;

                //
                //  Otherwise the cluster is in use so to make life easier we
                //  pull out the compressed and uncompressed data length and
                //  the lbo of the heap for the cluster
                //

                CompressedDataLength = DblsGetCompressedDataLength( FatExtension );

                UncompressedDataLength = DblsGetUncompressedDataLength( FatExtension );

                ClusterLbo = DblsGetHeapLbo( FatExtension );

                //
                //  If we are reading beyond the compressed length, we already
                //  know the answer.
                //

                if (Start - i >= UncompressedDataLength) {

                    RtlZeroMemory( &Buffer[ Start - RelativeOffset ], Stop - Start );
                    continue;
                }

                //
                //  Now check if the data is uncompressed and our life is really
                //  easy because we only need to read in the data.
                //

                if (FatExtension.IsDataUncompressed) {

                    //
                    //  The data is not compressed so read it straight into the
                    //  caller's buffer, taking into account that we only want
                    //  to read in the as much as will fit in our buffer or as much
                    //  as is available.
                    //

                    ReadCvf( CvfId,
                             ClusterLbo + (Start - i),
                             Minimum( UncompressedDataLength + i, Stop) - Start,
                             &Buffer[ Start - RelativeOffset ] );

                } else {

                    PUCHAR TargetBuffer;

                    //
                    //  Read in the compressed buffer and decompressed it
                    //

                    ReadCvf( CvfId,
                             ClusterLbo,
                             CompressedDataLength,
                             Dscb->CompressedBuffer );

                    //
                    //  If we can, decompress directly into the user's buffer
                    //

                    if ((Start == i) && ((Stop - i) >= UncompressedDataLength)) {

                        TargetBuffer = &Buffer[ Start - RelativeOffset ];

                    } else {

                        TargetBuffer = Dscb->UncompressedBuffer;
                    }

                    UncompressedDataLength = DblsMrcfDecompress( TargetBuffer,
                                                                 UncompressedDataLength,
                                                                 Dscb->CompressedBuffer,
                                                                 CompressedDataLength,
                                                                 Dscb->DecompressWorkSpace );

                    //
                    //  At this point the uncompressed buffer is full and we
                    //  need to copy the appropriate amount to data to the
                    //  caller's buffer
                    //

                    if (TargetBuffer == Dscb->UncompressedBuffer) {

                        RtlCopyMemory( &Buffer[ Start - RelativeOffset ],
                                       &TargetBuffer[ Start - i ],
                                       Minimum(UncompressedDataLength + i, Stop) - Start );
                    }
                }

                //
                //  At this point we've copied some data into the user buffer
                //  however if the uncompressed data length is less than what we
                //  wanted to copy from this cluster then we need to zero out
                //  the end of the user buffer
                //

                if (UncompressedDataLength + i < Stop) {

                    RtlZeroMemory( &Buffer[ (Start - RelativeOffset) + UncompressedDataLength ],
                                   Stop - (UncompressedDataLength + i) );
                }
            }
        }
    }

    //
    //  And return to our caller
    //

    return ESUCCESS;
}


//
//  Internal support routine
//

ARC_STATUS
DblsReadCvf (
    IN ULONG CvfId,
    IN LBO Lbo,
    IN ULONG ByteCount,
    IN PVOID Buffer
    )

/*++

Routine Description:

    This routine reads in zero or more bytes from the specified CVF.

Arguments:

    CvfId - Supplies the Id for the Cvf.

    Lbo - Supplies the LBO to start reading from.

    ByteCount - Supplies the number of bytes to read.

    Buffer - Supplies a pointer to the buffer to read the bytes into.

Return Value:

    ESUCCESS is returned if the read operation is successful.  Otherwise,
    an unsuccessful status is returned that describes the reason for failure.

--*/

{
    LARGE_INTEGER LargeLbo;
    ARC_STATUS Status;
    ULONG i;

    //****DbgPrint("DblsReadCvf(%0x,%0x,%0x,%0x)\n", CvfId, Lbo, ByteCount, Buffer);

    //
    //  Special case the zero byte read request
    //
    if (ByteCount == 0) {

        return ESUCCESS;
    }

    //
    //  Seek to the appropriate offset in the Cvf
    //

    LargeLbo.LowPart = (ULONG)Lbo;
    LargeLbo.HighPart = 0;

    if ((Status = BlSeek( CvfId, &LargeLbo, SeekAbsolute )) != ESUCCESS) {

        return Status;
    }

    //
    //  Issue the read request to FatBoot
    //

    if ((Status = BlRead( CvfId, Buffer, ByteCount, &i)) != ESUCCESS) {

        return Status;
    }

    //
    //  Make sure we got back the amount requested
    //

    if (ByteCount != i) {

        return EIO;
    }

    //
    //  Everything is fine so return success to our caller
    //

    return ESUCCESS;
}


#ifndef _X86_
//
//  The following definitions and routines are used for decompressing
//  a buffer.  The code is copied from Mrcf.c
//

#define wBACKPOINTERMAX                  (4415)

typedef struct _MDSIGNATURE {
    USHORT sigStamp;
    USHORT sigType;
} MDSIGNATURE, *PMDSIGNATURE;

VOID
DblsMrcfSetBitBuffer (
    PUCHAR pb,
    ULONG cb,
    PMRCF_BIT_IO BitIo
    );

VOID
DblsMrcfFillBitBuffer (
    PMRCF_BIT_IO BitIo
    );

USHORT
DblsMrcfReadBit (
    PMRCF_BIT_IO BitIo
    );

USHORT
DblsMrcfReadNBits (
    LONG cbits,
    PMRCF_BIT_IO BitIo
    );


//
//  Internal support routine
//

ULONG
DblsMrcfDecompress (
    PUCHAR UncompressedBuffer,
    ULONG UncompressedLength,
    PUCHAR CompressedBuffer,
    ULONG CompressedLength,
    PMRCF_DECOMPRESS WorkSpace
    )

/*++

Routine Description:

    This routine decompresses a buffer of StandardCompressed or MaxCompressed
    data.

Arguments:

    UncompressedBuffer - buffer to receive uncompressed data

    UncompressedLength - length of UncompressedBuffer

          NOTE: UncompressedLength must be the EXACT length of the uncompressed
                data, as Decompress uses this information to detect
                when decompression is complete.  If this value is
                incorrect, Decompress may crash!

    CompressedBuffer - buffer containing compressed data

    CompressedLength - length of CompressedBuffer

    WorkSpace - pointer to a private work area for use by this operation

Return Value:

    ULONG - Returns the size of the decompressed data in bytes. Returns 0 if
        there was an error in the decompress.

--*/

{
    ULONG  cbMatch; //  Length of match string
    ULONG  i;       //  Index in UncompressedBuffer to receive decoded data
    ULONG  iMatch;  //  Index in UncompressedBuffer of matched string
    ULONG  k;       //  Number of bits in length string
    ULONG  off;     //  Offset from i in UncompressedBuffer of match string
    USHORT x;       //  Current bit being examined
    ULONG  y;

    //****DbgPrint("DblsMrcfDecompress(%0x,%0x,%0x,%0x,%0x)\n", UncompressedBuffer,UncompressedLength,CompressedBuffer,CompressedLength,WorkSpace);

    //
    //  Skip over the signature
    //

    CompressedLength -= sizeof(MDSIGNATURE);
    CompressedBuffer += sizeof(MDSIGNATURE);

    //
    //  Set up for decompress, start filling UncompressedBuffer at front
    //

    i = 0;

    //
    //  Set statics to save parm passing
    //

    DblsMrcfSetBitBuffer(CompressedBuffer,CompressedLength,&WorkSpace->BitIo);

    while (TRUE) {

        y = DblsMrcfReadNBits(2,&WorkSpace->BitIo);

        //
        //  Check if next 7 bits are a byte
        //  1 if 128..255 (0x80..0xff), 2 if 0..127 (0x00..0x7f)
        //

        if (y == 1 || y == 2) {

            UncompressedBuffer[i] = (UCHAR)((y == 1 ? 0x80 : 0) | DblsMrcfReadNBits(7,&WorkSpace->BitIo));

            i++;

        } else {

            //
            //  Have match sequence
            //  Get the offset
            //

            if (y == 0) {

                //
                //  next 6 bits are offset
                //

                off = DblsMrcfReadNBits(6,&WorkSpace->BitIo);

            } else {

                x = DblsMrcfReadBit(&WorkSpace->BitIo);

                if (x == 0) {

                    //
                    //  next 8 bits are offset-64 (0x40)
                    //

                    off = DblsMrcfReadNBits(8, &WorkSpace->BitIo) + 64;

                } else {

                    //
                    //  next 12 bits are offset-320 (0x140)
                    //

                    off = DblsMrcfReadNBits(12, &WorkSpace->BitIo) + 320;

                    if (off == wBACKPOINTERMAX) {

                        //
                        //  EOS marker
                        //

                        if (i >= UncompressedLength) {

                            //
                            // Done with entire buffer
                            //

                            return i;

                        } else {

                            //
                            //  More to do
                            //  Done with a 512-byte chunk
                            //

                            continue;
                        }
                    }
                }
            }

            //
            //  Get the length  - logarithmically encoded
            //

            for (k=0; (x=DblsMrcfReadBit(&WorkSpace->BitIo)) == 0; k++) { NOTHING; }

            if (k == 0) {

                //
                //  All matches at least 2 chars long
                //

                cbMatch = 2;

            } else {

                cbMatch = (1 << k) + 1 + DblsMrcfReadNBits(k, &WorkSpace->BitIo);
            }

            //
            //  Copy the matched string
            //

            iMatch = i - off;

            while ( (cbMatch > 0) && (i<UncompressedLength) ) {

                UncompressedBuffer[i++] = UncompressedBuffer[iMatch++];
                cbMatch--;
            }
        }
    }
}


//
//  Internal Support Routine
//

VOID
DblsMrcfSetBitBuffer (
    PUCHAR pb,
    ULONG cb,
    PMRCF_BIT_IO BitIo
    )

/*++

Routine Description:

    Set statics with coded buffer pointer and length

Arguments:

    pb - pointer to compressed data buffer

    cb - length of compressed data buffer

    BitIo - Supplies a pointer to the bit buffer statics

Return Value:

    None.

--*/

{
    //****DbgPrint("DblsMrcfSetBitBuffer(%0x,%0x,%0x)\n", pb,cb,BitIo);

    BitIo->pbBB        = pb;
    BitIo->cbBB        = cb;
    BitIo->cbBBInitial = cb;
    BitIo->cbitsBB     = 0;
    BitIo->abitsBB     = 0;
}


//
//  Internal Support Routine
//

VOID
DblsMrcfFillBitBuffer (
    PMRCF_BIT_IO BitIo
    )

/*++

Routine Description:

    Fill abitsBB from static bit buffer

Arguments:

    BitIo - Supplies a pointer to the bit buffer statics

Return Value:

    None.

--*/

{
    //****DbgPrint("DblsMrcfFillBitBuffer(%0x)\n", BitIo);

    switch (BitIo->cbBB) {

    case 0:

        break;

    case 1:

        //
        //  Get last byte and adjust count
        //

        BitIo->cbitsBB = 8;
        BitIo->abitsBB = *(BitIo->pbBB)++;
        BitIo->cbBB--;

        break;

    default:

        //
        //  Get word and adjust count
        //

        BitIo->cbitsBB = 16;
        BitIo->abitsBB = *((USHORT *)(BitIo->pbBB))++;
        BitIo->cbBB -= 2;

        break;
    }
}


//
//  Internal Support Routine
//

USHORT
DblsMrcfReadBit (
    PMRCF_BIT_IO BitIo
    )

/*++

Routine Description:

    Get next bit from bit buffer

Arguments:

    BitIo - Supplies a pointer to the bit buffer statics

Return Value:

    USHORT - Returns next bit (0 or 1)

--*/

{
    USHORT bit;

    //****DbgPrint("DblsMrcfReadBit(%0x)\n", BitIo);

    //
    //  Check if no bits available
    //

    if ((BitIo->cbitsBB) == 0) {

        DblsMrcfFillBitBuffer(BitIo);
    }

    //
    //  Decrement the bit count
    //  get the bit, remove it, and return the bit
    //

    (BitIo->cbitsBB)--;
    bit = (BitIo->abitsBB) & 1;
    (BitIo->abitsBB) >>= 1;

    return bit;
}


//
//  Internal Support Routine
//

USHORT
DblsMrcfReadNBits (
    LONG cbits,
    PMRCF_BIT_IO BitIo
    )

/*++

Routine Description:

    Get next N bits from bit buffer

Arguments:

    cbits - count of bits to get

    BitIo - Supplies a pointer to the bit buffer statics

Return Value:

    USHORT - Returns next cbits bits.

--*/

{
    ULONG abits;        // Bits to return
    LONG cbitsPart;     // Partial count of bits
    ULONG cshift;       // Shift count
    ULONG mask;         // Mask

    //****DbgPrint("DblsMrcfReadNBits(%0x,%0x)\n", cbits,BitIo);

    //
    //  Largest number of bits we should read at one time is 12 bits for
    //  a 12-bit offset.  The largest length field component that we
    //  read is 8 bits.  If this routine were used for some other purpose,
    //  it can support up to 15 (NOT 16) bit reads, due to how the masking
    //  code works.
    //

    //
    //  No shift and no bits yet
    //

    cshift = 0;
    abits = 0;

    while (cbits > 0) {

        //
        //  If not bits available get some bits
        //

        if ((BitIo->cbitsBB) == 0) {

            DblsMrcfFillBitBuffer(BitIo);
        }

        //
        //  Number of bits we can read
        //

        cbitsPart = Minimum((BitIo->cbitsBB), cbits);

        //
        //  Mask for bits we want, extract and store them
        //

        mask = (1 << cbitsPart) - 1;
        abits |= ((BitIo->abitsBB) & mask) << cshift;

        //
        //  Remember the next chunk of bits
        //

        cshift = cbitsPart;

        //
        //  Update bit buffer, move remaining bits down and
        //  update count of bits left
        //

        (BitIo->abitsBB) >>= cbitsPart;
        (BitIo->cbitsBB) -= cbitsPart;

        //
        //  Update count of bits left to read
        //

        cbits -= cbitsPart;
    }

    //
    //  Return requested bits
    //

    return (USHORT)abits;
}
#endif // ndef _X86_

#endif // def DBLSPACE_LEGAL
