src/storage/fatfs/fatfs.c 	Microsoft Invisible Computing

/* Copyright (c) Microsoft Corporation. All rights reserved. */
/* TODO:

   OPTIM:
   write the FATs less often
   avoid read-before-write
   done: delay writes
   done: sync direntry on file close only

   BUGS:
   fixed: extending a directory
 */

#define DEBUG_WRITES 0

#define DEVICE_ORDER LITTLE_ENDIAN
#include <mmlite.h>
#include <mmhal.h>
#include <stdio.h>
#include <ctype.h> /* for tolower() */
//#define _DEBUG 1

#include "fatfs.h"
#undef DBGLVL
#define DBGLVL 1

/* Preferred settings are for a full-featured system
 */
#if !defined(__WANTS_BUFFER_CACHE)
#define __WANTS_BUFFER_CACHE 1
#endif
#if !defined(__WANTS_FATFS_WRITES)
#define __WANTS_FATFS_WRITES 1
#endif
#if !defined(__WANTS_LONG_FILENAMES)
#define __WANTS_LONG_FILENAMES 1
#endif
#if !defined(__WANTS_GROW_DIRECTORIES)
#define __WANTS_GROW_DIRECTORIES 1
#endif

/* Buffer cache definitions
 */
#if __WANTS_BUFFER_CACHE
#include <bufcache.h>
#define FatGetCachedBuffer(_File_)       BcacheGetBufferData((_File_)->HeldBuffer)
#define FatGetCachedCluster(_Fs_,_File_) BcacheGetBufferBlockNo((_File_)->HeldBuffer);
#else /* __WANTS_BUFFER_CACHE */
#define BcacheInit()
#define BcacheSpillTag(_Tag_)
#define BcacheGetBufferData(_b_)         (_b_)
#define FatGetCachedBuffer(_File_)       (_File_)->HeldBuffer
#define FatGetCachedCluster(_Fs_,_File_) ((_File_)->CachedClusterNumber + (_Fs_)->FirstSector)
#endif /* __WANTS_BUFFER_CACHE */

#define FatCachedBufferIsValid(_File_)   ((_File_)->HeldBuffer != NULL)

/* Working buffer for handling long filenames
 */
#if __WANTS_LONG_FILENAMES
#define MAXLNAMELEN 0xff  /* times UNICODE char */
#define MAXLNINDEX  20
typedef struct {
    UINT8 Length;
    UINT8 Sum;
    UINT16 Name[MAXLNAMELEN+1];
} FAT_LONGNAME, *PFAT_LONGNAME;

#endif

/* FAT Volume descriptor
 */
typedef struct {
    const struct IUnknownVtbl *v;
    INT     RefCnt;
    PIFILE  Device;
    UINT16* Fat;
#if __WANTS_LONG_FILENAMES
    PFAT_LONGNAME Lfn;
#endif
#if __WANTS_FATFS_WRITES
    UINT    FirstFree;
    INT     NumFree;
    INT16   Dirty;
    UINT16  NumFatCopies;
    MUTEX   WriteLock;  /*BUGBUG its a RWLock */
#endif
                            /* put after this only permanent parms
                             * because FatError() knows too much
                             */
    UINT    FatBits;
    UINT    FatLen;
    UINT    FatStart;
    DISK_ADDRESS FirstSector;
    UINT    RootStart;
    UINT    RootSize;
    UINT    ClusterStart;
    UINT    ClusterSize;
    UINT    LogBlockSize;
} FATFS;

PRIVATE const struct IUnknownVtbl FatVolumeVtbl;

/* File object
 */
typedef struct {
    const struct IFileVtbl *v;
    INT     RefCnt;
    FATFS*  Fs;
#if __WANTS_LONG_FILENAMES
    PFAT_LONGNAME Lfn;
#endif
    DISK_ADDRESS StartCluster;
    UINT    Size;
    UINT    Attributes;
#define ATTR_HAD_ERROR    0x10000 /* volatile attributes */
#define ATTR_MODIFIED     0x20000
#define ATTR_DELETED      0x40000

#if __WANTS_FATFS_WRITES
    MUTEX   WriteLock;  /*BUGBUG its a RWLock */
    UINT    MyDirentryBlockNum; /* absolute!! yuch */
    UINT    MyDirentry;
#endif

    UINT    HeldBufferOffset;
    PTR     HeldBuffer;
#if __WANTS_BUFFER_CACHE
#else
    DISK_ADDRESS CachedClusterNumber;
#endif
} FATFILE;

PRIVATE const struct IFileVtbl FatFileVtbl;

/* Namespace Object
 */
typedef struct {
    const struct INameSpaceVtbl *v;
    INT     RefCnt;
    PIUNKNOWN Volume;       /* If Dir below is NULL, this is a *FATFS */
                            /* otherwise this is a *FATNAME           */
    FATFILE* Dir;
} FATNAME;

PRIVATE const struct INameSpaceVtbl FatNameVtbl;

/* FAT Filesystem permanent data structures
 */
#define FAT_MIN_SECTOR_SIZE 512 /* MSDOS sector size in bytes */
#define FAT_ROOT            1
#define FAT_LABEL_SECTOR    0
#define FAT_INVALID_ENTRY   ((UINT16)0xffff)
#define FAT_MAX_ENTRY       ((UINT16)0xfff0)

/* Endian-ess is LE (386)  */
#define FatInt16(_x_)       ((_x_)[0]|((_x_)[1]<<8))
#define FatInt32(_x_)       ((_x_)[0]|((_x_)[1]<<8)|((_x_)[2] <<16)|((_x_)[3]<<24))

#define MkFatInt16(_x_,_v_) {(_x_)[0] = (UINT8)(_v_);(_x_)[1] = (UINT8)((_v_)>>8);}
#define MkFatInt32(_x_,_v_) {(_x_)[0] = (UINT8)(_v_);(_x_)[1] = (UINT8)((_v_)>>8);(_x_)[2] = (UINT8)((_v_)>>16);(_x_)[3] = (UINT8)((_v_)>>24);}

/* Optimize for LE machines, when a-ligned */
#if (BYTE_ORDER == LITTLE_ENDIAN)
#define FatInt16a(_x_)      (*(UINT16 *)(_x_))
#define FatInt32a(_x_)      (*(UINT32 *)(_x_))
#else
#define FatInt16a(_x_)      FatInt16(_x_)
#define FatInt32a(_x_)      FatInt32(_x_)
#endif

/*
 * Information contained in sector zero of a FAT filesystem
 */
typedef struct {
    UINT8   bootid;         /* bootable or not */
#define BIOS_BOOTABLE   128

    UINT8   beghead;        /* beginning head, sector, cylinder */
    UINT8   begsect;        /* begcyl is a 10-bit number. High 2 bits */
    UINT8   begcyl;         /*     are in begsect. */

    UINT8   systid;         /* filesystem type */
#define SYS_FAT 6
#define SYS_EXT 5
#define SYS_FAT16 4
#define SYS_COMPAQ 18

    UINT8   endhead;        /* ending head, sector, cylinder */
    UINT8   endsect;        /* endcyl is a 10-bit number.  High 2 bits */
    UINT8   endcyl;         /*     are in endsect. */

    UINT8   offset[4];
    UINT8   n_sectors[4];
}  BIOS_PARTITION_INFO;

typedef struct {
    INT8    partitions[4*sizeof(BIOS_PARTITION_INFO)];
    UINT8   magic[2];
#define BIOS_LABEL_MAGIC    0xaa55
} BIOS_LABEL;

typedef struct {
    UINT8   jump[3];        /* Jump to boot code */
    UINT8   banner[8];      /* OEM name & version */
    UINT8   secsiz[2];      /* Bytes per sector hopefully 512 */
    UINT8   clsiz;          /* Cluster size in sectors */
    UINT8   nrsvsect[2];    /* Number of reserved (boot) sectors */
    UINT8   nfat;           /* Number of FAT tables hopefully 2 */
    UINT8   dirents[2];     /* Number of root directory entries */
    UINT8   psect[2];       /* Total sectors on disk */
    UINT8   descr;          /* Media descriptor=first byte of FAT */
    UINT8   fatlen[2];      /* Sectors in FAT */
    UINT8   nsect[2];       /* Sectors/track */
    UINT8   nheads[2];      /* Heads */
    UINT8   nhs[4];         /* number of hidden sectors */
    UINT8   bigsect[4];     /* big total sectors */
    UINT8   driveno;        /* drive number */
    UINT8   reserved;
    UINT8   bsigrec;        /* ext boot signature record (x29) */
    UINT8   volid[4];       /* 32bit volume id */
    UINT8   volume_label[11];   /* */
    UINT8   res1[8];        /* actually, "FATnn   " nn=12/16 */
    UINT16  bootcode[384/sizeof(UINT16)]; /* boot code */
    BIOS_LABEL label;       /* right at the end */
} FAT_VOLUME_INFO;

/*
 * Information in a directory entry
 */
#define MAX83LEN            11
#define MAX83NAMLEN         8
#define MAX83EXTLEN         3

typedef struct {
    UINT8 name[8];          /* file name */
#define FAT_NAME_DELETED    0xe5
#define FAT_NAME_ESCAPE     0x5
#define FAT_NAME_EMPTY      0
#define FAT_NAME_LFNEND     0x40
#define FAT_NAME_LFNIX      0x1f
    UINT8 ext[3];           /* file extension */
    UINT8 attr;             /* attribute byte */
#define FAT_ATTR_RONLY      0x01
#define FAT_ATTR_HIDDEN     0x02
#define FAT_ATTR_SYS        0x04
#define FAT_ATTR_LABEL      0x08    /* optional entry, only in rootdir */
#define FAT_ATTR_DIR        0x10
#define FAT_ATTR_ARCHIVE    0x20    /* e.g. dirty bit */
#define FAT_ATTR_xxx        0xc0
#define FAT_ATTR_LONGFN     0x0f
    UINT8 casebyte;
#define FAT_CASE_NAMELOW    0x08
#define FAT_CASE_EXTLOW     0x10
    UINT8 lfnsum;
    UINT8 reserved[8];     /* mbz */
    UINT8 time[2];          /* time stamp */
    UINT8 date[2];          /* date stamp */
    UINT8 start[2];         /* starting cluster number */
    UINT8 size[4];          /* size of the file */
} FAT_DIRECTORY_RECORD;

/*
 * Macros
 */
/* NOTE: "Logical Block" as defined in the FAT entries, e.g. cluster no. */
#define BlockSize(_f_)               (1 << (_f_)->LogBlockSize)
#define OffsetToBlocks(_f_, _o_)     ((_o_) >> (_f_)->LogBlockSize)
#define BlocksToOffset(_f_,_n_)      ((_n_) << (_f_)->LogBlockSize)
#define OffsetInBlock(_f_, _o_)      ((_o_) & (BlockSize(_f_)-1))

/*
 * Forward declarations
 */
PRIVATE SCODE MCT
FatFileReadAt(PIFILE pThis, UINT64 Position, 
              BYTE *Buffer, UINT ByteCount, PUINT pSizeRead);

PRIVATE SCODE MCT
FatFileWriteAt(PIFILE pThis, UINT64 Position, 
               const BYTE *pBuffer, UINT ByteCount, PUINT pSizeWritten);

PRIVATE BOOL
FatFileDestroy(FATFILE *File);

PRIVATE SCODE
FatFileGrow( FATFILE *File, UINT32 NewSize);

PRIVATE SCODE
FatFileShrink( FATFILE *File, UINT32 NewSize, BOOL RemoveIt);

PRIVATE SCODE
FatFlushMyBuffer( FATFILE *File );

PRIVATE BOOL FatError(FATFILE *File, SCODE Error);

PRIVATE void
FatFsDestroy(FATFS *Fs);

PRIVATE BOOL
FatFsCacheFat(FATFS *Fs);

PRIVATE BOOL
FatFsSpillFat(FATFS *Fs);

PRIVATE SCODE
FatStubbornlyReadWrite(
    PIFILE Device,
    UINT64 Offset,
    BYTE  *Buffer,
    UINT32 Length,
    UINT32 *BytesTransferred,
    BOOL   fRead
    );

PRIVATE UINT8
FatChecksum(UINT8 *Name);

#if _DEBUG && (DBGLVL==0)
/* =============================================
 * Debugging tools
 * =============================================
 */

/* Print the content of one of our internal LongFileName objects,
 * which is where we reconstruct the LFN from the fragments on disk.
 */
PRIVATE void DumpLfn(PFAT_LONGNAME Lfn)
{
    UINT8 i;

    /* Q: Does it have to be zero-terminated (on disk) ??
     * A: No, but we do it internally.
     */
    printf("Lfn[%d] %x \"", Lfn->Length, Lfn->Sum);
    for (i = 0; i < Lfn->Length; i++) {
        if (Lfn->Name[i] == 0)
            break;
        printf("%c", (UINT8)Lfn->Name[i]);
    }
    printf("\"\n");
}

/* Print the relevant fields of a directory entry
 */
PRIVATE void DumpDirentry(FAT_DIRECTORY_RECORD *dp, UINT32 Block, UINT Offset)
{
    int i;
    UINT8 *p = (UINT8 *)dp;
    UINT8 *ps;
    UINT16 c;

    /* NOTE on long filenames.
     *
     * - long names are found in reverse order, last chunk first.
     * - the short name immediately follows the last long name entry
     * - first byte of a long name is the chunk-index
     * - the last chunk has 0x40 or-ed into the chunk-index.
     * - yes, chunks can cross block boundaries.
     * - dp->attr=0x0f for longname chunks, and dp->start=0
     * - sometimes only the short name is used, using the "casebyte" field.
     *   Example: "a" file only has "A" entry, with dp->casebyte=8.
     *   NB: Otherwise it is dp->casebyte=0
     * - Counter: file "B" has only "B" entry, with dp->casebyte=0
     * - WindowsXP does not seem too eager to recycle DELETED entries.
     * - checksum is for the shortname, to check it corresponds to the long one.
     * - checksum byte repeats identical in all chunks.
     * - see FatChecksum() for the algorithm used.
     * - name clashes are handled in a very complicated way
     */

    printf("Dir[%u,+%x]:",Block,Offset);
#if 0
    for (i = 0; i < 32; i++)
        printf(" %2x", p[i]);
    printf("\n");
#endif
    if (dp->attr == FAT_ATTR_LONGFN) {
        printf(" %x \"",p[0]);
        ps = (p+1);
        for (i = 0; i < 5; i++) {
            c = FatInt16(ps);
            if (c == 0) goto DoneName;
            printf("%c",(UINT8)c);
            ps += 2;
        }
        ps = (p+14);
        for (i = 0; i < 6; i++) {
            c = FatInt16(ps);
            if (c == 0) goto DoneName;
            printf("%c",(UINT8)c);
            ps += 2;
        }
        ps = (p+28);
        for (i = 0; i < 2; i++) {
            c = FatInt16(ps);
            if (c == 0) goto DoneName;
            printf("%c",(UINT8)c);
            ps += 2;
        }
    DoneName:
        printf("\"  sum=%x\n",p[13]);
    } else {
        printf(" r%x %x \"", dp->attr, dp->casebyte);
        for (i = 0; i < 11; i++)
            printf("%c",p[i]);
        printf("\" start=%x len=%d sum=%x\n",
               FatInt32(dp->start), FatInt16(dp->size),
               FatChecksum(p));
    }
}
#else
#define DumpLfn(l)
#define DumpDirentry(d,b,o) 0
#endif


/* =============================================
 * Auxiliary functions
 * =============================================
 */

/* From a path component to name entry
 * NOTENOTE - FAT names are always ASCII
 * Returns FALSE if we consumed the input string, either because we
 * reached the EOS or a path separator.
 * Returns TRUE if the input cannot be mapped to the 8.3 format.
 */
PRIVATE BOOL StrToFName(BYTE *ncp, const _TUCHAR * *pcp)
{
    INT len = 0;
    _TUCHAR c;
    const _TUCHAR * cp = *pcp;

    DBGOUT(DBGFS, 0, printf("StrToFName %x %x (%s)\n", (ADDRESS)ncp, (ADDRESS)pcp, cp));

    /* Watch for escape */
    c = (*cp == FAT_NAME_DELETED) ? FAT_NAME_ESCAPE : *cp;

    while (c != _EOS && !IS_PATH_SEPARATOR(c)) {

        /* Pad out with blanks if we reach the . too soon
         */
        if ((c == _T('.')) && (len <= MAX83NAMLEN)) {
            while (len < MAX83NAMLEN) {
                *ncp++ = ' ';
                len++;
            }
            goto Next;
        }

        /* Get out if it does not fit
         */
        if (++len > MAX83LEN)
            goto NameTooLong;

        /* Upcase it.  Shouldn't, really.
         */
        if (c >= _T('a') && c <= _T('z'))
            c -= _T('a') - _T('A');

        *ncp++ = (BYTE) c;
    Next:
        cp++;
        c = *cp;
    }
    
    /* Pad it if too short
     */
    while (len++ < MAX83LEN)
        *ncp++ = ' ';

    *ncp = '\0';
    *pcp = cp;
    return FALSE;
NameTooLong:
    return TRUE;
}

/* From a name entry to a C string
 */
SCODE FNameToStr(BYTE *Name, _TCHAR * Buf, UINT BufSize,
                 UINT Flags, PUINT pStrLen)
{
    INT Len = (INT) BufSize / sizeof(_TCHAR) - 1;
    INT i, j;
    _TUCHAR c;

    DBGOUT(DBGFS, 0, printf("FNameToStr(%x %x %x %x %x)\n",
                            (ADDRESS)Name, (ADDRESS)Buf, BufSize, Flags, (ADDRESS)pStrLen));

    /* Copy name (w/o extension).
     * Watch for escape
     */
    c = (_TUCHAR) Name[0];
    if (c == FAT_NAME_ESCAPE) c = FAT_NAME_DELETED;

    for (i = 0, j = 1; i < Len && j <= MAX83NAMLEN; i++, j++) {
        if (c == _T(' '))
            break;
        if (Flags & (FAT_CASE_NAMELOW<<8))
            c = _totlower(c);
        Buf[i] = c;
        c = (_TUCHAR) Name[j];
    }

    /* Skip spaces (between name and extension).
     */
    for (; j < MAX83NAMLEN; j++) {
        if (Name[j] != _T(' '))
            break;
    }

    /* If there is an extension, insert dot, then copy the extension.
     */
    if (j < MAX83LEN) {
        if (i >= Len)
            goto SmallBuf;

        j = MAX83NAMLEN;
        if (Name[j] != _T(' '))
            Buf[i++] = _T('.');

        for (; i < Len && j < MAX83LEN; i++, j++) {
            c = (_TUCHAR) Name[j];
            if (c == _T(' ')) {
                j = MAX83LEN; /* Quickly skip trailing blanks */
                break;
            }
            if (Flags & (FAT_CASE_EXTLOW<<8))
                c = _totlower(c);
            Buf[i] = c;
        }
    }

    /* Check if buffer too small (if not all chars were copied)
     */
    if (j < MAX83LEN)
        goto SmallBuf;

    /* Add the trailing slash if required
     */
    if (Flags & FAT_ATTR_DIR) {
        if (i >= Len)
            goto SmallBuf;

        Buf[i++] = PATH_SEPARATOR1;
    }

    /* Terminate string [As per SPECS, dont count _EOS]
     */
    Buf[i] = _EOS;
    if (pStrLen)
        *pStrLen = i;

    return NO_ERROR;

SmallBuf:
    Buf[0] = _EOS;
    if (pStrLen)
        *pStrLen = 0;

    return S_BUFFER_TOO_SMALL;
}

#if __WANTS_LONG_FILENAMES
/* Compute a hash of an 8.3 filename.
 * This is used to verify its pairing with a long filename.
 */
PRIVATE UINT8 FatChecksum(UINT8 *Name)
{
    UINT8 Sum = Name[0];
    int i;
    for (i = 1; i < 11; i++)
        Sum = Name[i] + (Sum >> 1) + ((Sum & 1) ? 0x80 : 0);
    return Sum;
}

/* From a long-filename-path component to name entry
 * NB: Called after StrToFName has failed.
 */
PRIVATE BOOL LongStrToFName(BYTE *ncp, const _TCHAR * *pcp)
{
    _TCHAR c;
    const _TCHAR * cp = *pcp;

    /* If we were to do this for real it would be a lot of work.
     * But since this is only used for lookups... anything goes.
     * We can just blank out the 8.3 name and the match will
     * happen, if it does, on the longname only.
     */
    memset(ncp,' ',MAX83LEN);
    ncp[MAX83LEN] = 0;

    /* As for the path, we still have to get to the next component
     */
    while ((c = *cp) != _EOS && !IS_PATH_SEPARATOR(c))
        cp++;

    *pcp = cp;

    return FALSE;
}

/* From a long filename to a C string
 * NB: Lfn->Length is rounded upwards of 13x, Lfn->Name is zero-terminated.
 */
SCODE LFNameToStr(PFAT_LONGNAME Lfn, _TCHAR * Buf, UINT BufSize,
                  UINT Flags, PUINT pStrLen)
{
    INT Len = (INT) BufSize / sizeof(_TCHAR) - 1;
    INT i;
    _TUCHAR c = 1; /* !EOS */

    /* Copy name, while it fits
     */
    for (i = 0; i < Len && i < Lfn->Length; i++) {
        c = (_TUCHAR) Lfn->Name[i];
        Buf[i] = c;
        if (c == _EOS)
            break;
    }

    /* Check if buffer too small (if not all chars were copied)
     */
    if ((c != _EOS) && (i < Lfn->Length))
        goto SmallBuf;

    /* Add the trailing slash if required
     */
    if (Flags & FAT_ATTR_DIR) {
        if (i >= Len)
            goto SmallBuf;

        Buf[i++] = PATH_SEPARATOR1;
    }

    /* Terminate string [As per SPECS, dont count _EOS]
     */
    Buf[i] = _EOS;
    if (pStrLen)
        *pStrLen = i;

    return NO_ERROR;

SmallBuf:
    Buf[0] = _EOS;
    if (pStrLen)
        *pStrLen = 0;

    return S_BUFFER_TOO_SMALL;
}
#else
#define LongStrToFName(a,b) TRUE
#endif

/* Shorthand for memory allocation, wired.
 */
PRIVATE void
FatUnwireMemory( ADDRESS Where, UINT Size)
{
#ifdef mmheavy
    PIADDRESSSPACE pIAddress = GetPcb()->pIAddress;

    pIAddress->v->Deallocate(pIAddress, Where, Size);
#else
    PIHEAP pIHeap = CurrentHeap();
    pIHeap->v->Free(pIHeap, 0, (PTR)Where);
    DBGOUT(DBGFS, 0, printf("Unwire %x\n",Where));
#endif
}

PRIVATE SCODE
FatWireMemory( ADDRESS *pWhere, UINT Size )
{
#ifdef mmheavy
    PIADDRESSSPACE pIAddress = GetPcb()->pIAddress;
    SCODE  Sc;

    Sc = pIAddress->v->Reserve(pIAddress, pWhere, Size, 0);
    if (FAILED(Sc))
        return Sc;

    /* Better not be paged out, or the whole thing
     * turns into a bad joke.
     */
    Sc = pIAddress->v->Commit(pIAddress, *pWhere, 
                              Size, 
                              ADDR_COMMIT_LOCK);

    if (FAILED(Sc)) {
        FatUnwireMemory( *pWhere, Size );
        *pWhere = (ADDRESS) NULL;
    }
    return Sc;
#else
    PIHEAP pIHeap = CurrentHeap();
    PTR Memory = pIHeap->v->Alloc(pIHeap,0,Size,0);
    DBGOUT(DBGFS, 0, printf("Wire %x(%d)\n",(ADDRESS)Memory,Size));
    if (Memory == NULL)
        return E_NOT_ENOUGH_MEMORY;
    *pWhere = (ADDRESS)Memory;
    return NO_ERROR;
#endif
}


/*
 * File allocation table
 */
PRIVATE UINT16
FatGetEntry(FATFS * Fs, UINT Entry)
{
    UINT FBits = Fs->FatBits;
    UINT16 * Fat = Fs->Fat;

    if (Fat == NULL) {
        if (!FatFsCacheFat(Fs))
            return FAT_INVALID_ENTRY;
        Fat = Fs->Fat;
    }

    if (FBits == 16)
        return _bs16(Fat[Entry]);

    FBits = (Entry * 3) / 4;/* 12/16 == 3/4 */

    switch (Entry & 0x3) {
    case 0:
        FBits = _bs16(Fat[FBits]);
        break;
    case 1:
        FBits = (_bs16(Fat[FBits]) >> 12) | (_bs16(Fat[FBits + 1]) << 4);
        break;
    case 2:
        FBits = (_bs16(Fat[FBits]) >>  8) | (_bs16(Fat[FBits + 1]) << 8);
        break;
    case 3:
        FBits = _bs16(Fat[FBits]) >> 4;
        break;
    }

    FBits &= 0xfff;
    if ((FBits & 0xff0) == 0xff0)
        FBits |= 0xf000;/* make specials always 16 bits */

    return (UINT16) FBits;
}

#if __WANTS_FATFS_WRITES
PRIVATE void
FatSetEntry(FATFS * Fs, UINT Entry, UINT16 Value)
{
    UINT FBits = Fs->FatBits;
    UINT16 * Fat = Fs->Fat;

#if DEBUG_WRITES
 printf("fse %x %x\n", Entry, Value);
#endif
    /* Fat is non-null else we wont be in here
     */
    assert(Fat != NULL);

    /* FAT now needs to be written back
     */
    Fs->Dirty = 2;

    if (FBits == 16) {
        Fat[Entry] = _bs16(Value);
        return;
    }

    Value &= 0xfff;
    FBits = (Entry * 3) / 4;/* 12/16 == 3/4 */

    switch (Entry & 0x3) {
    case 0:
        Fat[FBits] = _bs16( (UINT16)((_bs16(Fat[FBits]) & 0xf000) | Value) );
        break;
    case 1:
        Fat[FBits] = _bs16( (UINT16)((_bs16(Fat[FBits]) & 0x0fff) | (Value << 12)) );
        FBits++;
        Fat[FBits] = _bs16( (UINT16)((_bs16(Fat[FBits]) & 0xff00) | (Value >>  4)) );
        break;
    case 2:
        Fat[FBits] = _bs16( (UINT16)((_bs16(Fat[FBits]) & 0x00ff) | (Value <<  8)) );
        FBits++;
        Fat[FBits] = _bs16( (UINT16)((_bs16(Fat[FBits]) & 0xfff0) | (Value >>  8)) );
        break;
    case 3:
        Fat[FBits] = _bs16( (UINT16)((_bs16(Fat[FBits]) & 0x000f) | (Value <<  4)) );
        break;
    }
}

#ifndef NBBY
#define NBBY 8
#endif

PRIVATE SCODE
FatAllocEntry( FATFS * Fs, UINT Start, PUINT pEntry )
{
    UINT        i, j, s, e;

#if DEBUG_WRITES
 printf("fae %x ", Start);
#endif
    Mutex_Lock(&Fs->WriteLock);

    /* From anywhere ?
     */
    if ((s = Start) == 0)
        s = Fs->FirstFree;

    /* First phase, forward
     */
    e = (Fs->FatLen * NBBY) / Fs->FatBits;
    for (i = s; i < e; i++) {
        j = FatGetEntry(Fs, i);
        if (j == 0) goto found;
    }

    /* Second phase, backward
     */
    for (i = s - 1; i >= 2; i--) {
        j = FatGetEntry(Fs, i);
        if (j == 0) goto found;
    }

    /* Disk full
     */
    assert( Fs->NumFree <= 0 );
    DBGOUT(DBGFS, 3, printf("DISK FULL (%d) !!!\n", Fs->NumFree));
    Mutex_Unlock(&Fs->WriteLock);

    return E_OUT_OF_STRUCTURES;

found:
    /* extend/truncate chain
     */
    FatSetEntry(Fs, i, FAT_INVALID_ENTRY);
    Fs->FirstFree = i+1;
    Fs->NumFree--;
    if (Start != 0)
        FatSetEntry(Fs, Start, (UINT16)i);

    Mutex_Unlock(&Fs->WriteLock);

    *pEntry = i;
    return NO_ERROR;
}

/* This is approximate, but goodnuf.
 * Fat only wants the number of double-seconds since 1980.
 */

const TIME  DoubleSecondsToStartOf1980=Int64Initializer(0x00000001,0x646f9b80);
const UINT8 DaysPerMonth[12] = {31,28,31,30,31,30,31,31,30,31,30,31};

PRIVATE void
TimeToFatTime( UINT8 *pFatTime, TIME OsTime)
{
    UINT32 DoubleSeconds;
    UINT years, months, days, hours, minutes, Dseconds;

    /* Make it into double-seconds
     */
    OsTime = Int64DividedByInt32(OsTime,20000000);

    /* Convert time reference base (assuming we dont just have boot-time)
     */
    if (TimeLess(DoubleSecondsToStartOf1980,OsTime)) {
        OsTime = Int64Subtract(OsTime,DoubleSecondsToStartOf1980);
    }

    /* See if we overflow the FAT time rep
     */
    if (TimeNeeds32Bits(OsTime)) {
        memset(pFatTime, ~0, 4);
        return;
    }

    DoubleSeconds = Uint64ToUint32(OsTime);

    /* No overflow, use 32 bit math then
     */
#define DSECMIN  ((UINT)30)            /* Dseconds per minute */
#define DSECHOUR ((UINT)(60*DSECMIN))  /* Dseconds per hour */
#define DSECDAY  ((UINT)(24*DSECHOUR)) /* Dseconds per day */
#define DSECYR   ((UINT)(365*DSECDAY)) /* Dseconds per regular year */

#define LEAPYEAR(x)     (((x) & 3) == 0)

    years = 0;
    for (;;) {
        Dseconds = DSECYR;
        if (LEAPYEAR(years))
            Dseconds += DSECDAY;
        if (DoubleSeconds < Dseconds)
            break;
        DoubleSeconds -= Dseconds;
        years++;
    }

    months = 0;
    for (;;) {
        Dseconds = DaysPerMonth[months++] * DSECDAY;
        if ((months == 2/* February */) && LEAPYEAR(years))
            Dseconds += DSECDAY;
        if (DoubleSeconds < Dseconds)
            break;
        DoubleSeconds -= Dseconds;
    }

    days = DoubleSeconds / DSECDAY;
    DoubleSeconds -= DSECDAY * days;
    days++;                     /* 1-based */

    hours = DoubleSeconds / DSECHOUR;
    DoubleSeconds -= DSECHOUR * hours;

    minutes = DoubleSeconds / DSECMIN;
    DoubleSeconds -= DSECMIN * minutes;

    /* Encode it
     */
    pFatTime[0] = (UINT8)
        (((DoubleSeconds >> 1) & 0x1f) | ((minutes & 0x7) << 5));
    pFatTime[1] =  (UINT8)
        (((minutes >> 3) & 0x7) | ((hours << 3)));
    pFatTime[2] =  (UINT8)
        ((days & 0x1f) | (months << 5));
    pFatTime[3] =  (UINT8)
        (((months >> 3) & 1) | (years << 1));
}


/*
 * Find the cluster at a given file offset
 * NOTE: Not applicable to the root directory.
 */
PRIVATE SCODE
MapCluster(
     FATFILE * File, 
     UINT FileOffset, 
     DISK_ADDRESS * pCluster) /* out */
{
    FATFS * Fs = File->Fs;
    DISK_ADDRESS RetCluster;

    if ((RetCluster = File->StartCluster) == 0)
        /* zero-sized */
        return E_INVALID_PARAMETER;

    /* The first cluster is good for some
     */
    if (FileOffset > 0) {

        /* rename: Offset-in-bytes --> Offset-in-clusters
         */
        FileOffset = OffsetToBlocks(Fs,FileOffset - 1);

        while (FileOffset > 0) {
            /*
             * Which one is the next block in the file ?
             */
            RetCluster = (DISK_ADDRESS) FatGetEntry(Fs, RetCluster);

            if ((RetCluster == 0) || ((RetCluster & 0xfff0) == 0xfff0)) {
                return E_INVALID_PARAMETER;
            }
            FileOffset--;
        }
    }

    *pCluster = RetCluster;

    return NO_ERROR;
}

#endif /* __WANTS_FATFS_WRITES */

/*
 * Given an offset in a file, find the sector that
 * starts that cluster.
 */
PRIVATE SCODE
MapBlock(
     FATFILE * File, 
     DISK_ADDRESS FileBlock, 
     DISK_ADDRESS * pDiskBlock) /* out */
{
    FATFS * Fs = File->Fs;
    DISK_ADDRESS RetBlock;

    if (File->StartCluster <= FAT_ROOT) {
        *pDiskBlock = Fs->RootStart + (FileBlock * Fs->ClusterSize);
    } else {

        if ((RetBlock = File->StartCluster) == 0)
             /* zero-sized */
            return E_INVALID_PARAMETER;

        while (FileBlock > 0) {
            /*
             * Which one is the next block in the file ?
             */
            RetBlock = (DISK_ADDRESS) FatGetEntry(Fs, RetBlock);

            if ((RetBlock == 0) || ((RetBlock & 0xfff0) == 0xfff0)) {
                return E_INVALID_PARAMETER;
            }
            FileBlock--;
        }
        /* first cluster is no. 2 
         */
        RetBlock = ((RetBlock - 2) * Fs->ClusterSize) + Fs->ClusterStart;
        *pDiskBlock = RetBlock;
    }
    return NO_ERROR;
}

/*
 * Length of a directory
 */
PRIVATE DISK_ADDRESS
FatGetDirLen(
     FATFS * Fs, 
     DISK_ADDRESS ClusterNumber)
{
    if (ClusterNumber > FAT_ROOT) {
        DISK_ADDRESS len = 0;
        do {
            ClusterNumber = FatGetEntry(Fs, ClusterNumber);
            len++;
        } while (ClusterNumber && (ClusterNumber < 0xfff8));
        return BlocksToOffset(Fs,len);
    }
    /* len of root 
     */
    return (Fs->ClusterStart - Fs->RootStart) * FAT_MIN_SECTOR_SIZE;
}

/*
 * Search a directory for a filename
 */
typedef BOOL (DP_MATCH)(FATFILE *File,
                        FAT_DIRECTORY_RECORD *dp,
                        PTR Arg1,
                        PTR Arg2);

PRIVATE SCODE
FatSearchDirectory(FATFILE * File, FAT_DIRECTORY_RECORD **pDr, DP_MATCH * Match, PTR Arg1, PTR Arg2)
{
    UINT FileOffset;
    UINT64 Offset64;
    FATFS * Fs = File->Fs;
    FAT_DIRECTORY_RECORD * dp = NULL;
    UINT DirClusterNum = 0;
    int LfnIndex = -1;
#if __WANTS_LONG_FILENAMES
    PFAT_LONGNAME Lfn = File->Lfn;

    *pDr = NULL;

    if (Arg2) {
        DBGME(0,printf("  Search(%s) %d\n", (BYTE*) Arg2, File->Size));
    }

    /* Borrow/allocate one?
     */
    if (Lfn == NULL) {
        /* Try for the volume
         */
        Lfn = File->Fs->Lfn;
        if (Lfn != NULL) {
            File->Fs->Lfn = NULL;
        } else {
            /* Allocate one
             */
            PIHEAP pIHeap = CurrentHeap();
            Lfn = pIHeap->v->Alloc(pIHeap,0,sizeof(FAT_LONGNAME),0);
            /*printf("fsd %x(%d)",Lfn,sizeof(FAT_LONGNAME));*/
            if (Lfn == NULL) {
                /* Cant do squat without one
                 */
                return (E_NOT_ENOUGH_MEMORY);
            }
        }
        File->Lfn = Lfn;
    }

    Lfn->Length = 0;
#endif

    FileOffset = 0;
    while (FileOffset < File->Size) {
        INT c;
        SCODE Sc;

        /*
         * Read a new block if necessary
         */
        if (OffsetInBlock(Fs, FileOffset) == 0) {
        RetryRead:
            Uint32ToUint64(Offset64,FileOffset);
            Sc = FatFileReadAt((PIFILE)File, Offset64, 
                               NULL, BlockSize(Fs), NULL);
            if (FAILED(Sc)) {
                if (FatError(File, Sc))
                    goto ErrorExit;
                else
                    goto RetryRead;
            }
            File->HeldBufferOffset = FileOffset;
            DirClusterNum = FatGetCachedCluster(Fs,File);
            dp = (FAT_DIRECTORY_RECORD *)FatGetCachedBuffer(File);
        }

        /* Is this entry valid ?
         * DOS rules sez the first EMPTY entry marks
         * the end of the directory, and we should look
         * no further.
         */
        c = dp->name[0];
        if (c == FAT_NAME_EMPTY)
            break;

        /* If it has been deleted skip it.
         * Longnames do not cross invalid records. (right?)
         */
        if (c == FAT_NAME_DELETED) {
            goto InvalidRecord;
        }

#if __WANTS_LONG_FILENAMES
        /* Is it an lfn entry
         */
        if (dp->attr == FAT_ATTR_LONGFN) {
            UINT Length;
            int i;
            UINT8 *pChars; /* shorts are NOT aligned!! */

            /* We can be in one of two states here:
             * A- In the middle of building an LFN
             * B- First LFN entry we see (LfnIndex==-1)
             */
            if (LfnIndex < 0) {

                /* LFN entries are in reverse, this one tells us 3 things:
                 * - The maximum size of the name
                 * - How many more entries to expect
                 * - The checksum of the corresponding shortname
                 * First check it is indeed the last LFN entry
                 */
                if ((dp->name[0] & FAT_NAME_LFNEND) == 0)
                    goto InvalidRecord;

                /* Pick up and check the index
                 */
                LfnIndex = dp->name[0] & FAT_NAME_LFNIX;
                if (LfnIndex > MAXLNINDEX)
                    goto InvalidRecord;

                /* All seems ok, initialize the working buffer
                 */
                Length = LfnIndex * 13;
                if (Length > MAXLNAMELEN)
                    Length = MAXLNAMELEN;
                Lfn->Length = (UINT8) Length;
                Lfn->Name[Lfn->Length] = 0;
                Lfn->Sum = dp->lfnsum;

                /* Jump to the common code for copying UNICODE chars
                 */
                goto CopyChars;
            }

            /* This must be the correct continuation record
             */
            if ((dp->name[0] != LfnIndex) ||
                (dp->lfnsum != Lfn->Sum))
                goto InvalidRecord;

            /* Copy the chars into the buffer
             */
        CopyChars:
            pChars = &dp->name[1];
            Length = (LfnIndex - 1) * 13;

            for (i = 0; i < 5; i++) {
                Lfn->Name[Length++] = FatInt16(pChars);
                pChars += 2;
            }

            pChars += 3;/* skip to after the checksum */

            for (i = 0; i < 6; i++) {
                Lfn->Name[Length++] = FatInt16(pChars);
                pChars += 2;
            }

            pChars += 2;/* skip the "start" field */

            for (i = 0; i < 2; i++) {
                Lfn->Name[Length++] = FatInt16(pChars);
                pChars += 2;
            }

            /* Get ready for the next record
             */
            LfnIndex--;

            /* Do not try this entry for a match
             */
            goto NextOne;
        } else
#endif /* __WANTS_LONG_FILENAMES */
        /*else*/ {
            /* Skip volume labels also
             */
            if (dp->attr & FAT_ATTR_LABEL) {
                LfnIndex = -1;
                goto NextOne;
            }
        }

#if __WANTS_LONG_FILENAMES
        /* If the checksum does not match cancel out the long filename
         */
        DumpDirentry(dp,DirClusterNum,FileOffset);
        if (Lfn->Length &&
            Lfn->Sum != FatChecksum(dp->name))
            Lfn->Length = 0;
        else
            DumpLfn(Lfn);
#endif

        /* We have a candidate for a match, try it.
         */
        if ((*Match)(File, dp, Arg1, Arg2)) {
#if __WANTS_FATFS_WRITES
            File->MyDirentry = OffsetInBlock(Fs,FileOffset) / sizeof *dp;
            File->MyDirentryBlockNum = DirClusterNum;
#endif
            *pDr = dp;
            return NO_ERROR;
        }

        /*
         * Not found, keep going
         */
    InvalidRecord:
        LfnIndex = -1;
    NextOne:
        FileOffset += sizeof(*dp);
        dp++;
    }

ErrorExit:
    return (E_FILE_NOT_FOUND);
}

/* Match a C string against a name entry
 * in a directory
 */
PRIVATE BOOL MatchStr(const BYTE *Str, const BYTE *FName)
{
    /*
     * Check for a name match
     * Note that FNAME is canonical
     */
    while (*Str == *FName++)
        if (*(++Str) == _EOS) break;
    return (*Str == _EOS);
}

#if __WANTS_LONG_FILENAMES
/* Match a C string against a long filename 
 */
PRIVATE BOOL MatchLfn(const BYTE *Str, PFAT_LONGNAME Lfn)
{
    /*
     * Check for a name match
     * NB: Lfn->Length is rounded upwards of 13x, Lfn->Name is zero-terminated.
     */
    UINT8 i = 0;
    while (*Str == (BYTE)Lfn->Name[i]) {
        Str++, i++;
        if ((*(Str) == _EOS) || (i >= Lfn->Length))
            break;
    }
    return ((*Str == (BYTE)Lfn->Name[i]) ||
            (IS_PATH_SEPARATOR(*Str) && (Lfn->Name[i] == _EOS)));
}
#endif

/*
 * Auxillary functions for FatSearchDirectory
 */
PRIVATE BOOL
MatchName(
    FATFILE *File,
    FAT_DIRECTORY_RECORD *dp,
    PTR Arg1,
    PTR Arg2)
{
#if __WANTS_LONG_FILENAMES
    /* Try the longname match first
     */
    if (Arg2 && File->Lfn && File->Lfn->Length &&
        MatchLfn((BYTE *)Arg2, File->Lfn)) {
        DBGME(0,printf(" Match(%s)\n", (BYTE*) Arg2));
        goto GottaMatch;
    }
#else
    UnusedParameter(Arg2);
#endif

    /*
     * Iff found, cache file/dir info.
     */
    if (!MatchStr((BYTE *)Arg1, dp->name))
        return FALSE;

#if __WANTS_LONG_FILENAMES
 GottaMatch:
#endif
    File->StartCluster = FatInt16a(dp->start);
    File->Attributes = dp->attr;

    if (dp->attr & FAT_ATTR_DIR)
        File->Size = FatGetDirLen(File->Fs, File->StartCluster);
    else
        File->Size = FatInt32a(dp->size);

    /* Invalidate cache 
     */
#if __WANTS_BUFFER_CACHE
    if (File->HeldBuffer != NULL) {
        BcacheSpillBuffer(File->HeldBuffer, FALSE);
        File->HeldBuffer = NULL;
    }
#else
    File->CachedClusterNumber = ~(DISK_ADDRESS)0;
#endif
    File->HeldBufferOffset = 0;
    return TRUE;
}

PRIVATE BOOL
MatchFirst(
    FATFILE *File,
    FAT_DIRECTORY_RECORD *dp,
    PTR Arg1,
    PTR Arg2)
{
    UnusedParameter(File);
    UnusedParameter(Arg1);
    UnusedParameter(Arg2);

    /* Skip dot and dot-dot
     */
    return (dp->name[0] != '.');
}

PRIVATE BOOL
MatchNext(
    FATFILE *File,
    FAT_DIRECTORY_RECORD *dp,
    PTR Arg1,
    PTR Arg2)
{
    BYTE ** ppName = (BYTE **) Arg1;

    /* First find the entry we should start from
     */
    if (*ppName == NULL)
        goto SecondPart;

    /* When found, cancel out pointer to mark the end
     * of the first search test
     */
#if __WANTS_LONG_FILENAMES
    if (Arg2 && File->Lfn && File->Lfn->Length &&
        MatchLfn((BYTE *)Arg2, File->Lfn)) {
        DBGME(0,printf(" MatchNext(%s)\n", (BYTE*) Arg2));

        /* what if there isnt an lfn to follow */
        File->Lfn->Length = 0;
        *ppName = NULL;
        return FALSE;
    }
#else
    UnusedParameter(File);
    UnusedParameter(Arg2);
#endif

    if (MatchStr(*ppName, dp->name))
        *ppName = NULL;
    return FALSE;

SecondPart:
    /* Skip dot and dot-dot (xxx unnecessary)
     */
    return (dp->name[0] != '.');
}

/* Repeatedly ask the device for one-single-long read/write operation,
 * until we got all the data we need.
 */
PRIVATE SCODE FatStubbornlyReadWrite(
    PIFILE Device,
    UINT64 Offset,
    BYTE  *Buffer,
    UINT32 Length,
    UINT32 *BytesTransferred,
    BOOL   fRead
    )
{
    SCODE sc;
    UINT32 BytesThisRound;
    UINT64 Bytes64;

    *BytesTransferred = 0;
    do {
        BytesThisRound = 0;

        sc = (fRead) ?
            Device->v->ReadAt(Device,
                              Offset,
                              Buffer,
                              Length,
                              &BytesThisRound) :
            Device->v->WriteAt(Device,
                               Offset,
                               Buffer,
                               Length,
                               &BytesThisRound);

        Uint32ToUint64(Bytes64,BytesThisRound);
        Offset = Uint64Add(Offset,Bytes64);
        Length -= BytesThisRound;
        Buffer += BytesThisRound;
        *BytesTransferred += BytesThisRound;
    } while (!FAILED(sc) && (Length > 0));

    return sc;

}

#if __WANTS_FATFS_WRITES
/* =============================================
 * Functions that modify permanent storage
 * =============================================
 */
PRIVATE const FAT_DIRECTORY_RECORD TemplateDir[2] = {
 {
     ".       ", "   ", FAT_ATTR_DIR, /* zero rest */
 },
 {
     "..      ", "   ", FAT_ATTR_DIR, /* zero rest */
 }
};

PRIVATE SCODE
FatFileCreateEntry( FATFILE *File, BYTE *pName, UINT Flags)
{
    UINT FileOffset, FirstOffset, LastOffset;
    UINT64 Offset64;
    FATFS * Fs = File->Fs;
    FAT_DIRECTORY_RECORD * dp = NULL, Dr;
    INT c;
    SCODE Sc;
    UINT DirClusterNum = 0;

    DBGOUT(DBGFS, 0, printf("FatFileCreateEntry %x %x \"%s\"\n",
                            (ADDRESS) File, Flags, pName));

    /* We come here right after FatSearchDirectory has done its thing
     */
    assert( (File->Attributes & FAT_ATTR_DIR) != 0 );

    /* First (and possibly only) pass is to the end of file.
     */
    LastOffset = File->Size;

    if (FatCachedBufferIsValid(File))
    {
        FirstOffset = FileOffset = File->HeldBufferOffset;
        goto StartFromCachedBlock;
    }

    FirstOffset = LastOffset; /* e.g. dont rescan */

 StartFromFirstBlock:

    FileOffset = 0;

    while (FileOffset < LastOffset) {

        /*
         * Read a new block if necessary
         */
        if (OffsetInBlock(Fs, FileOffset) == 0) {
        RetryRead:
            Uint32ToUint64(Offset64,FileOffset);
            Sc = FatFileReadAt((PIFILE)File, Offset64, 
                               NULL, BlockSize(Fs), NULL);
            if (FAILED(Sc)) {
                if (FatError(File, Sc))
                    goto ErrorExit;
                else
                    goto RetryRead;
            }

        StartFromCachedBlock:

            File->HeldBufferOffset = FileOffset;
            DirClusterNum = FatGetCachedCluster(Fs,File);
            dp = (FAT_DIRECTORY_RECORD *)FatGetCachedBuffer(File);
        }

        /* Is this entry free ?
         */
        c = dp->name[0];
        if (c == FAT_NAME_DELETED || c == FAT_NAME_EMPTY)
            goto FoundSlot;

        /*
         * Not free, keep going
         */
        FileOffset += sizeof(*dp);
        dp++;
    }

    /* Rollover to the beginning, if not done yet
     */
    if (FirstOffset != LastOffset) {
        LastOffset = FirstOffset;
        goto StartFromFirstBlock;
    }

    /* If we get here there is no room, and we have
     * to extend the directory. Cant do that on the root.
     */
#if __WANTS_GROW_DIRECTORIES
    DBGME(1,printf("extending directory %x %d\n", File->StartCluster, File->Size));
    Sc = FatFileGrow( File, File->Size + BlockSize(Fs));
    if (!FAILED(Sc)) {
        /* Say what the new size is
         */
        FileOffset = File->Size;
        File->Size = File->Size + BlockSize(Fs);
        LastOffset = File->Size;

        /* We must zero the new block
         */
    RetryZero:
        Uint32ToUint64(Offset64,FileOffset);
        Sc = FatFileReadAt((PIFILE)File, Offset64, 
                           NULL, BlockSize(Fs), NULL);
        if (FAILED(Sc)) {
            if (FatError(File, Sc)) {
                (void) FatFileShrink( File, File->Size - BlockSize(Fs), FALSE);
                goto ErrorExit;
            } else {
                goto RetryZero;
            }
        }

        File->HeldBufferOffset = FileOffset;
        DirClusterNum = FatGetCachedCluster(Fs,File);
        dp = (FAT_DIRECTORY_RECORD *)FatGetCachedBuffer(File);

        memset(dp,FAT_NAME_EMPTY,BlockSize(Fs));

        /* Unless we failed that is
         */
        if (FAILED(Sc)) {
            goto ErrorExit;
        }

        goto StartFromCachedBlock;
    }
#else
    DBGME(3,printf("extending directory compiled out. Sorry\n"));
    Sc = E_NOT_IMPLEMENTED;
#endif
    
 ErrorExit:
    DBGME(0,printf("ffce error %x\n", Sc));
    return Sc;

 FoundSlot:
    /* 'dp' points to the slot that is free/deleted. Take it.
     */
    DBGME(0,printf("ffce GotMilk %x,+%u\n", (ADDRESS)dp,FileOffset));
    DBGME(0,DumpDirentry(dp,DirClusterNum,FileOffset));

    memcpy( &Dr.name, pName, MAX83LEN);

    /* Fillup the new directory entry
     */
    MkFatInt32(Dr.size,0);

    c = (Flags & F_DIRECTORY) ? FAT_ATTR_DIR : FAT_ATTR_ARCHIVE;
    if (Flags & F_HIDDEN)
        c |= FAT_ATTR_HIDDEN;
    if (Flags & F_READONLY)
        c |= FAT_ATTR_RONLY;
    Dr.attr = (UINT8) c;

    /* Timestamp properly
     */
    TimeToFatTime(Dr.time, CurrentTime() );

    /* For a new directory, get a new block.
     * This might fail, on e.g. disk full.
     * I would be inclined to take a block for a file also,
     * since it will be needed anyways soon. But DOS does
     * not do it that way, and SCANDISK complains if we do.
     */
    if (Flags & F_DIRECTORY)
    {
        Sc = FatAllocEntry(Fs, 0, (PUINT)&c);
        if (FAILED(Sc))
            goto ErrorExit;
    } 
    else {
        c = 0x0; /* does not like 0xffff */
    }

    MkFatInt16(Dr.start,c);

    /* Write updated directory entry off to disk.
     * BUGBUG If we crash, it would be more polite
     * BUGBUG to have initialized any new directory.
     */
#ifdef mips
    memcpy(dp,&Dr,sizeof Dr);/* swl/swr not yet working on eMIPS */
#else
    *dp = Dr;
#endif
    DBGME(0,DumpDirentry(dp,DirClusterNum,FileOffset));

#if __WANTS_BUFFER_CACHE
    /* Mark dir-entry buffer dirty, and write it back
     */
    BcacheAddToBufferState(File->HeldBuffer, BLK_DIRTY);
    BcacheSpillBuffer(File->HeldBuffer, FALSE);
    File->HeldBuffer = NULL;
#else
    /* Write dir-entry buffer back, all of it
     */
 RetryWrite:
    Sc = FatFileWriteAt( (PIFILE)File, (UINT64)
                         (FileOffset - OffsetInBlock(Fs, FileOffset)),
                         NULL, BlockSize(Fs), NULL);
    if (FAILED(Sc)) {
        if (!FatError(File, Sc))
            goto RetryWrite;
        goto ErrorExit;
    }
#endif
    Sc = FatFlushMyBuffer( File );
    File->HeldBufferOffset = 0;

    File->MyDirentry = OffsetInBlock(Fs,FileOffset) / sizeof *dp;
    File->MyDirentryBlockNum = DirClusterNum;

    /* save this for later */
    if ((FileOffset = File->StartCluster) == FAT_ROOT)
        FileOffset = 0;

    /* Update the remaining information in the File structure
     */
    File->StartCluster = FatInt16a(Dr.start);
    File->Attributes = dp->attr | ATTR_MODIFIED;
    File->Size = (Flags & F_DIRECTORY) ? BlockSize(Fs) : 0;
    
    /* If this is a directory, must initialize it.
     * Which means dirty-caching its first block.
     */
    if (Flags & F_DIRECTORY) {

    RetryDirRead:

#if __WANTS_BUFFER_CACHE
        {
            DISK_ADDRESS BlockNum;

            /* This cant fail
             */
            (void) MapBlock(File, 0, &BlockNum);

            /* This one can
             */
            Sc = BcacheLookupWrite(Fs->Device, 
                                   BlockNum + Fs->FirstSector, 
                                   BlockSize(Fs),
                                   &File->HeldBuffer);
        }

#else
        Sc = FatFileReadAt((PIFILE)File, (UINT64)0, 
                               NULL, BlockSize(Fs), NULL);
    ErrInconsistent:
#endif

        if (FAILED(Sc)) {
            if (FatError(File, Sc)) {
                /* BUGBUG which is why this write should happen first
                 */
                DBGOUT(DBGFS,4,printf("Your file system is now inconsistent, sorry.\n"));
                goto ErrorExit;
            } else
                goto RetryDirRead;
        }

        dp = (FAT_DIRECTORY_RECORD *)FatGetCachedBuffer(File);

        memset(dp, 0, BlockSize(Fs));
        memcpy(dp, (PTR) &TemplateDir, sizeof TemplateDir);

        /* copy from '.' */
        memcpy(&dp[0].attr, &Dr.attr, (sizeof Dr) - MAX83LEN);

        /* Uhmm, shouldn't this be '..'s true time and date ?
         * But this is what DOS does..
         */
        memcpy(dp[1].time, Dr.time, sizeof Dr.time);
        memcpy(dp[1].date, Dr.date, sizeof Dr.date);

        MkFatInt16(dp[1].start,FileOffset);
        /* dp[1].size = 0; */

#if __WANTS_BUFFER_CACHE
        /* Mark directory buffer dirty, and release it.
         */
        BcacheAddToBufferState(File->HeldBuffer, BLK_DIRTY);
        BcacheSpillBuffer(File->HeldBuffer, FALSE);
        File->HeldBuffer = NULL;
#else
        /* Write dir-entry buffer back, all of it
         */
      RetryWrite2:
        Sc = FatFileWriteAt( (PIFILE)File, (UINT64)0,
                            NULL, BlockSize(Fs), NULL);
        if (FAILED(Sc)) {
            if (!FatError(File, Sc))
                goto RetryWrite2;
            goto ErrInconsistent;
        }
#endif
        File->HeldBufferOffset = 0;
    }

    /* Success !
     */
    DBGME(0,printf("ffce ok\n"));
    return NO_ERROR;
}

#if __WANTS_LONG_FILENAMES
PRIVATE SCODE
FatFileCreateLongEntry( FATFILE *File, _TCHAR *pName, BYTE *pName83, UINT Flags)
{
    /* Like I said, it would be a ton of code so.
     */
    int i;
    for (i = 0; i < MAX83LEN; i++)
        pName83[i] = _totupper(pName[i]);
    pName83[MAX83LEN] = 0; /* jic */
    return FatFileCreateEntry(File,pName83,Flags);
}
#else
#define FatFileCreateLongEntry( File, pName, pName83, Flags) FALSE
#endif


PRIVATE SCODE
FatFlushMyBuffer( FATFILE *File )
{
#if __WANTS_BUFFER_CACHE
    UnusedParameter(File);
    return S_OK;
#else
    PIFILE Device = File->Fs->Device;
    SCODE Sc = NO_ERROR;
    UINT bWritten;

    if ((File->Attributes & ATTR_MODIFIED) &&
        (File->CachedClusterNumber != ~(DISK_ADDRESS)0)) {

        Sc = FatStubbornlyReadWrite(Device,
                                (UINT64)(File->CachedClusterNumber +
                                         File->Fs->FirstSector)
                                * FAT_MIN_SECTOR_SIZE,
                                (BYTE *)File->HeldBuffer,
                                BlockSize(File->Fs), &bWritten, FALSE);
        File->CachedClusterNumber = ~(DISK_ADDRESS)0;
    }
    return Sc;
#endif
}


PRIVATE SCODE
FatSyncMyDirentry( FATFILE *File, UINT32 NewSize, UINT NewStart, BOOL RemoveIt)
{
    PTR Buffer = NULL;
    PIFILE Device;
    UINT BlockNum;
    SCODE Sc;
    FAT_DIRECTORY_RECORD *dp;
    FATFS * Fs = File->Fs;

#if DEBUG_WRITES
 printf("fsmd %d.%x %d.%x (%x %x)\n",
        File->Size, File->StartCluster,
        NewSize, NewStart,
        File->MyDirentry, File->MyDirentryBlockNum);
#endif

    File->Attributes |= ATTR_MODIFIED;

    if (!RemoveIt &&
        File->Size == NewSize &&
        File->StartCluster == NewStart)
        return NO_ERROR;

    /* Retrieve the cluster that contains the direntry
     */
    Device = Fs->Device;
    BlockNum = File->MyDirentryBlockNum;

 RetryDirRead:

#if __WANTS_BUFFER_CACHE
    Sc = BcacheLookupWrite(Device,
                           BlockNum/* + Fs->FirstSector*/, 
                           BlockSize(Fs),
                           &Buffer);

#else
    {
        UINT bRead;
        if (Buffer == NULL) {
            Sc = FatWireMemory( (ADDRESS *) &Buffer, BlockSize(Fs) );
        }
        Sc = FatStubbornlyReadWrite(Device,
                                 (UINT64)((BlockNum/* + Fs->FirstSector*/)
                                          * FAT_MIN_SECTOR_SIZE),
                                 Buffer, BlockSize(Fs), &bRead, TRUE);
    }
#endif

    if (FAILED(Sc)) {
        if (FatError(File, Sc))
            goto Exit;
        else
            goto RetryDirRead;
    }

    /* Find the direntry within the cluster
     */
    dp = (FAT_DIRECTORY_RECORD *)BcacheGetBufferData(Buffer);
    dp += File->MyDirentry;

    /* Adjust the start as requested
     */
    MkFatInt16(dp->start,NewStart);

    /* Directories must have zero size for DOS. Sigh
     */
    if (File->Attributes & FAT_ATTR_DIR) {
        MkFatInt32(dp->size,0);
    } else {
        MkFatInt32(dp->size,NewSize);
    }

    /* Mark the entry as deleted?
     */
    if (RemoveIt) {
        dp->name[0] = FAT_NAME_DELETED;
        File->Attributes |= ATTR_DELETED;

        /* BUGBUG if this is the last direntry in the block
         * we should free up the block here. Unless root.
         */
    }

    /* Now write it back
     */
#if __WANTS_BUFFER_CACHE
    /* Mark dir-entry buffer dirty, and write it back
     */
    BcacheAddToBufferState(Buffer, BLK_DIRTY);
    BcacheSpillBuffer(Buffer, FALSE);
#else
    {
        UINT bWritten;
        Sc = FatStubbornlyReadWrite(Device,
                                (UINT64)((BlockNum/* + Fs->FirstSector*/)
                                         * FAT_MIN_SECTOR_SIZE),
                                Buffer, BlockSize(Fs), &bWritten, FALSE);
    }
#endif

    if (FAILED(Sc)) {
//      printf("SyncMyDir failed!\n");

    } else {
        File->Size = NewSize;
        File->StartCluster = NewStart;
    }

 Exit:
#if __WANTS_BUFFER_CACHE
#else
    if (Buffer) {
        FatUnwireMemory( (ADDRESS) Buffer, BlockSize(Fs));
    }
#endif
    return Sc;
}

/* Extend a File's size
 * Follows DOS model, and does not zero new blocks
 */
PRIVATE SCODE
FatFileGrow( FATFILE *File, UINT32 NewSize)
{
    SCODE Sc;
    UINT NewEntry;
    DISK_ADDRESS LastEntry;
    INT32 Needs;
    FATFS *Fs;

#if DEBUG_WRITES
 printf("ffg %d-> %d ", File->Size, NewSize);
#endif

    if (NewSize <= File->Size)
        return NO_ERROR;

    if (File->StartCluster == FAT_ROOT)
        return E_OUT_OF_STRUCTURES; /* BUGBUG check */

    Sc = NO_ERROR;
    Fs = File->Fs;

    Mutex_Lock(&File->WriteLock);

    /* Check for a zero-sized file first
     */
    if (File->Size == 0) {
        Sc = FatAllocEntry( Fs, 0, &NewEntry);
        if (FAILED(Sc))
            goto Exit;
            
        LastEntry = NewEntry;

        /* How many more blocks do we need
         */
        Needs = OffsetToBlocks(Fs,NewSize-1);

    } else {
        Sc = MapCluster( File, File->Size - 1, &LastEntry);
        if (FAILED(Sc))
            goto Exit;

        NewEntry = File->StartCluster;

        /* How many blocks do we need
         */
        Needs = OffsetToBlocks(Fs,NewSize-1) - 
                OffsetToBlocks(Fs,File->Size-1);

    }

    /* Update parent's dir-entry.
     * No need to do this for directories, their size is 0.
     * We do not have last-accessed times either.
     */
    if (!(File->Attributes & FAT_ATTR_DIR))
        Sc = FatSyncMyDirentry( File, NewSize, NewEntry, FALSE);
    if (FAILED(Sc)) {
        /* Free up what we took
         */
        if (File->StartCluster != NewEntry)
            FatSetEntry(Fs, NewEntry, 0);
        goto Exit;
    }

    /* Take all blocks now, one by one
     */
#if DEBUG_WRITES
 printf("needs %d ", Needs);
#endif
    while (Needs > 0) {
        Sc = FatAllocEntry( Fs, LastEntry, &NewEntry);
        if (FAILED(Sc)) {
            NewSize -= BlocksToOffset(Fs,Needs);
            if (!(File->Attributes & FAT_ATTR_DIR))
                (void) FatSyncMyDirentry( File, NewSize, File->StartCluster, FALSE);
            goto Exit;
        }

        /* note FatAllocEntry does this: */
        /* FatSetEntry(Fs, LastEntry, (UINT16)NewEntry); */

        LastEntry = NewEntry;

        Needs--;
    }
#if DEBUG_WRITES
 printf("\n");
#endif

 Exit:
    Mutex_Unlock(&File->WriteLock);
    return Sc;
}

/* Shrink a File's size
 */
PRIVATE SCODE
FatFileShrink( FATFILE *File, UINT32 NewSize, BOOL RemoveIt)
{
    SCODE Sc;
    DISK_ADDRESS CurEntry, LastEntry;
    INT16 Evalue;
    FATFS *Fs;

    if (File->StartCluster == FAT_ROOT)
        return E_OUT_OF_STRUCTURES; /* BUGBUG check */

    if (!RemoveIt &&
        (NewSize >= File->Size))
        return NO_ERROR;

    Sc = NO_ERROR;
    Fs = File->Fs;

#if DEBUG_WRITES
 printf("ffshr %d-> %d ", File->Size, NewSize);
#endif

    Mutex_Lock(&File->WriteLock);

    /* Check for zero-sizing a file first
     */
    if (NewSize == 0) {
            
        LastEntry = File->StartCluster;

        /* We free the LastEntry block
         */
        CurEntry = 0;
        Evalue = 0;

    } else {

        Sc = MapCluster( File, NewSize-1, &LastEntry);
        if (FAILED(Sc))
            goto Exit;

        /* We keep the LastEntry block
         */
        CurEntry = File->StartCluster;
        Evalue = FAT_INVALID_ENTRY;
    }

    /* Truncate the file's direntry
     */
    Sc = FatSyncMyDirentry( File, NewSize, CurEntry, RemoveIt );
    if (FAILED(Sc))
        goto Exit;

    /* Free all blocks we just released, in the FAT
     */
    while (LastEntry > 1 && LastEntry < FAT_MAX_ENTRY) {

        CurEntry = FatGetEntry(Fs, LastEntry);

        /* Free current entry
         */
        FatSetEntry(Fs, LastEntry, Evalue);

        LastEntry = CurEntry;
        Evalue = 0;
    }

 Exit:
    Mutex_Unlock(&File->WriteLock);
    return Sc;
}

#else /* __WANTS_FATFS_WRITES */
#define FatFlushMyBuffer( _File_ )  S_OK
#endif /* __WANTS_FATFS_WRITES */

/* =============================================
 * Fat file system IFile interface
 * =============================================
 */
/*
 * Constructor.
 * Search starts from the root directory
 * or from the current directory.
 */
SCODE FatFileNew(FATNAME *pDir, const _TCHAR * pName, UINT Flags, FATFILE **pNew)
{
    _TCHAR * cp, *component = NULL;
    BOOL rc = FALSE, IsIllegal83 = TRUE;
    SCODE sc = NO_ERROR;
    FATFILE * File = NULL;
    FATFS * Fs;
    PIHEAP pIHeap = CurrentHeap();
    BYTE component83[MAX83LEN+1];

    DBGOUT(DBGFS, 0, printf("FatFileNew %x %s\n", (ADDRESS) pDir, pName));

    /* Sanity check 
     */
    *pNew = NULL;
    if (pDir == NULL || pDir->Volume == NULL) {
        return (E_INVALID_PARAMETER);
    }
    Fs = (FATFS*) pDir->Volume;

    /*
     * Allocate a File descriptor
     */
    File = (FATFILE *) pIHeap->v->Alloc(pIHeap, HEAP_ZERO_MEMORY, sizeof(FATFILE), 0);
    DBGOUT(DBGFS, 0, printf("ffn %x(%d)\n",(ADDRESS)File,sizeof(FATFILE)));
    if (File == NULL) {
        return (E_NOT_ENOUGH_MEMORY);
    }

    if (pDir->Dir == NULL) {
        /*
         * This is a Root name space
         */
        File->v = &FatFileVtbl;
        File->Fs = Fs;
        File->StartCluster = FAT_ROOT;
        File->Size = Fs->RootSize;
        File->Attributes = FAT_ATTR_DIR;
    } else {
        /*
         * This is some other directory
         */
        *File = *(pDir->Dir);
#if __WANTS_LONG_FILENAMES
        File->Lfn = NULL;
#endif
    }
    File->RefCnt = 1;
    File->HeldBufferOffset = 0;
    File->HeldBuffer = NULL;
#if __WANTS_BUFFER_CACHE
#else
    File->CachedClusterNumber = ~(DISK_ADDRESS)0;
#endif
#if __WANTS_FATFS_WRITES
    Mutex_Init(&File->WriteLock);
    File->MyDirentryBlockNum = (UINT)~0;
    File->MyDirentry = (UINT)~0;
#endif

    cp = (_TCHAR *) pName;

    while (*cp) {
        FAT_DIRECTORY_RECORD * dp = NULL;

        /*
         * Check that current node is a directory.
         */
        if ((File->Attributes & FAT_ATTR_DIR) == 0) {
            sc = E_INVALID_PARAMETER;
            goto out;
        }

        /*
         * Remove extra separators
         */
        while (IS_PATH_SEPARATOR(*cp))
            cp++;
        if (*cp == _EOS)
            break;

        /*
         * Get next component of path name, 
         * turn it into FAT-canonical form.
         */
        component = cp;
        IsIllegal83 = StrToFName(component83, (const _TUCHAR **)&cp);
        if (IsIllegal83 &&
            LongStrToFName(component83,(const _TCHAR **)&cp)) {
            sc = E_INVALID_PARAMETER;
            goto out;
        }

        /*
         * Look up component in current directory.
         * If found it gets cached into File.
         */
        sc = FatSearchDirectory(File, &dp, MatchName, (PTR)component83, (PTR)component);
        if (FAILED(sc))
            goto MaybeCreate;
    }

    /*
     * Found terminal component.
     */
    rc = TRUE;

 MaybeCreate:

#if __WANTS_FATFS_WRITES
    if (Flags & NAME_SPACE_CREATE) {

#if DEBUG_WRITES
        printf("fce %s %d %x %x %x\n", component83,rc,Flags,(ADDRESS)File,*cp);
#endif
        /* Does it exist already
         */
        if (rc && (Flags & NAME_SPACE_FAILIFEXIST)) {
            /* Ooops, set error and delete object
             */
            sc = E_ALREADY_EXISTS;
            rc = FALSE;
        }

        /* Did we check the terminal component, e.g.
         * does the parent directory exist
         */
        else if (!rc && File &&
                 ((*cp == _EOS) || IS_PATH_SEPARATOR(*cp))) {
            /* Yes and File now points to the parent directory.
             * Use 'rc' to cause file destruction, if necessary.
             */
            if (*cp != _EOS) Flags |= F_DIRECTORY;
            sc = (IsIllegal83) ?
                FatFileCreateLongEntry( File, component, component83, Flags ) :
                FatFileCreateEntry( File, component83, Flags );
            rc = !FAILED(sc);

            /* This might have changed the size of the current namespace,
             * by growing/shrinking the number of entries in it.
             * Since this object will stay around we must refresh the size
             */
            if (rc && pDir->Dir) {
                UINT NewSize = FatGetDirLen(File->Fs,pDir->Dir->StartCluster);
                if (pDir->Dir->Size != NewSize) {
                    DBGME(0,printf("\tDIRSIZE %d ==> %d\n",pDir->Dir->Size, NewSize));
                    pDir->Dir->Size = NewSize;
                }
            }
        }

        /* Otherwise its either ok to return the existing object,
         * or the request makes no sense. Error is set already.
         */

    }
#endif

 out:
    if (!rc && (File != NULL)) {
        (void) FatFileDestroy(File);
        File = NULL;
    }

    DBGOUT(DBGFS, 0, printf("FatFileNew %x %s --> %x %x\n",
                            (ADDRESS) Fs, pName, (ADDRESS) File, sc));
    *pNew = File;
    assert( FAILED(sc) || (File != NULL) );
    return sc;
}

/* Destructor
 */
PRIVATE BOOL
FatFileDestroy(FATFILE *File)
{
    PIHEAP pIHeap = CurrentHeap();

    DBGOUT(DBGFS, 0, printf("FatFileDestroy(%x)\n", (ADDRESS) File));

    if (File->HeldBuffer != NULL) {
#if __WANTS_BUFFER_CACHE
        BcacheSpillBuffer(File->HeldBuffer, TRUE/*??*/);
#else
        (void) FatFlushMyBuffer( File );
        FatUnwireMemory( (ADDRESS) File->HeldBuffer, BlockSize(File->Fs));
#endif
    }

#if __WANTS_FATFS_WRITES
    /* BUGBUG There should be a better strategy for checkpointing
     * the filesystem modifications. For now this will suffice.
     */
    if (File->Attributes & ATTR_MODIFIED) {
        /* Save the fat table first
         * BUGBUG A better way is to write the primary copy,
         * spill all writes, then write the other FAT  copies.
         */
        FatFsSpillFat(File->Fs);

        /* xxx SyncMyDirentry !! */

        /* Force completion of all pending writes for this disk
         */
        BcacheSpillTag( (UINT) File->Fs->Device );
    }
#endif

#if __WANTS_LONG_FILENAMES
    /* If we created/took a longname buffer hand it off to the volume object
     * BUGBUG Should ping-pong this more often, in each search.
     */
    if (File->Lfn) {
        if (File->Fs->Lfn == NULL) {
            File->Fs->Lfn = File->Lfn;
        } else {
            (void) pIHeap->v->Free(pIHeap, 0, File->Lfn);
        }
    }
#endif
    (void) pIHeap->v->Free(pIHeap, 0, File);

    return TRUE;
}

/* QueryInterface and refcounting
 */
SCODE MCT FatFileQueryInterface(PIFILE pThis, REFIID pIid, void* *ppNew)
{
    return GenericQueryInterface((PIUNKNOWN)pThis,pIid,ppNew,&IID_IFile);
}

UINT MCT FatFileAddRef(PIFILE pThis)
{
    FATFILE * File = (FATFILE *) pThis;

    return (++File->RefCnt);
}

UINT MCT FatFileRelease(PIFILE pThis)
{
    FATFILE * File = (FATFILE *) pThis;
    INT Refs;

    Refs = --File->RefCnt;
    if (Refs == 0)
        (void) FatFileDestroy(File);

    return Refs;
}

/*
 * Read from a file
 */
PRIVATE SCODE MCT
FatFileReadAt(PIFILE pThis, UINT64 Position, 
          BYTE *Buffer, UINT ByteCount, PUINT pSizeRead)
{   
    UINT oib, ReadSize, ClSize, BytesRead;
    DISK_ADDRESS BlockNum;
    DISK_ADDRESS CurOffset;
    SCODE Sc = NO_ERROR;
    FATFILE * File = (FATFILE *)pThis;
#if __WANTS_BUFFER_CACHE
#else
    PIFILE Device = File->Fs->Device;
    BYTE * IoBuffer = File->HeldBuffer;
#endif

    CurOffset = (DISK_ADDRESS) Uint64ToUint32(Position);

    DBGOUT(DBGFS, 0, printf("FatFileReadAt(%x, %x, %x, %x)\n", 
           (ADDRESS) File, CurOffset, (ADDRESS) Buffer, ByteCount));

    if (CurOffset >= File->Size) {
        if (CurOffset != File->Size)
            return E_INVALID_PARAMETER;

        BytesRead = 0; /* Read at EOF is not an error */
        goto out;
    }

    if (CurOffset + ByteCount > File->Size)
        ByteCount = File->Size - CurOffset;

    BytesRead = 0;

    while (ByteCount > 0) {
        oib = OffsetInBlock(File->Fs, CurOffset);
        BlockNum = OffsetToBlocks(File->Fs, CurOffset);

        ClSize = ReadSize = BlockSize(File->Fs);

        Sc = MapBlock(File, BlockNum, &BlockNum);
        if (FAILED(Sc))
            break;

#if __WANTS_BUFFER_CACHE
        if (File->HeldBuffer != NULL) {
            BcacheSpillBuffer(File->HeldBuffer, FALSE);
            File->HeldBuffer = NULL;
            File->HeldBufferOffset = 0;
        }
        /* Note BlockNum is a relative sector number. Make absolute
         */
        Sc = BcacheLookupRead(File->Fs->Device, 
                              BlockNum + File->Fs->FirstSector, 
                              ClSize, &File->HeldBuffer);
        if (!FAILED(Sc)) {
            File->HeldBufferOffset = CurOffset - oib;
            ReadSize -= oib;
            if (ReadSize > ByteCount)
                ReadSize = ByteCount;

            if (Buffer != NULL)
                memcpy(Buffer, BcacheGetBufferData(File->HeldBuffer) + oib, 
                       ReadSize);
        }
#else /* __WANTS_BUFFER_CACHE */
        /*
         * Do we have the block cached ?
         */
        if (IoBuffer && File->CachedClusterNumber == BlockNum) {
CopyCached:
            ReadSize -= oib;
            if (ReadSize > ByteCount)
                ReadSize = ByteCount;
            if (Buffer != NULL)
                memcpy(Buffer, IoBuffer+oib, ReadSize);
        }

        /*
         * Not cached, should we actually cache it ?
         * Dont cache large, aligned reads
         */
        else if ((Buffer != NULL) &&
                 (oib == 0) && ReadSize <= ByteCount) {

            Sc = FatStubbornlyReadWrite(Device,
                                     (UINT64)((BlockNum + File->Fs->FirstSector)
                                              * FAT_MIN_SECTOR_SIZE),
                                     Buffer, ClSize, &ReadSize, TRUE);
        }

        /*
         * Worth caching
         */
        else {
            if (IoBuffer == NULL) {
                (void) FatWireMemory( (ADDRESS *) &IoBuffer, ClSize);
                File->HeldBuffer = IoBuffer;
            }

            Sc = FatStubbornlyReadWrite(Device,
                                     (UINT64)(BlockNum + File->Fs->FirstSector)
                                     * FAT_MIN_SECTOR_SIZE,
                                     IoBuffer, ClSize, &ReadSize, TRUE);
            if (!FAILED(Sc)) {
                File->CachedClusterNumber = BlockNum;
                File->HeldBufferOffset = CurOffset - oib;
                goto CopyCached;
            }
        }
#endif /* __WANTS_BUFFER_CACHE */

        if (FAILED(Sc)) {
            if (FatError(File, Sc))
                goto out;
            else
                continue;
        }

        if (Buffer != NULL)
            Buffer = (BYTE *)Buffer + ReadSize;

        ByteCount -= ReadSize;
        CurOffset += ReadSize;
        BytesRead += ReadSize;
    }

    if (FAILED(Sc))
        (void) FatError(File, Sc);

out:
    if (CurOffset >= File->Size)
        Sc = S_FALSE;

    if (pSizeRead != NULL)
        *pSizeRead = BytesRead;
    return Sc;
}


/*
 * Write to a file
 */
PRIVATE SCODE MCT
FatFileWriteAt(PIFILE pThis, UINT64 Position, 
               const BYTE *pBuffer, UINT ByteCount, PUINT pSizeWritten)
{   
    SCODE Sc;
#if __WANTS_FATFS_WRITES
    UINT oib, WriteSize, ClSize, BytesWritten;
    DISK_ADDRESS BlockNum;
    DISK_ADDRESS FileOffset;
    FATFILE * File = (FATFILE *)pThis;
    PIFILE Device = File->Fs->Device;
#if __WANTS_BUFFER_CACHE
#else
    BYTE * IoBuffer = File->HeldBuffer;

    /* Internal ? */
    if (pBuffer == NULL)
        pBuffer = IoBuffer;
#endif

    FileOffset = (DISK_ADDRESS) Uint64ToUint32(Position);

    DBGOUT(DBGFS, 0, printf("FatFileWriteAt(%x, %x, %x, %x)\n", 
           (ADDRESS) File, FileOffset, (ADDRESS) pBuffer, ByteCount));

    Sc = E_INVALID_PARAMETER;
    BytesWritten = 0;

    File->Attributes |= ATTR_MODIFIED;

    /* BUGBUG error handling is non-existant
     */

    /* If we need to extend the file, do it now
     */
    Sc = FatFileGrow(File, Uint64ToUint32(Position) + ByteCount);
    if (FAILED(Sc))
        goto Exit;

    while (ByteCount > 0) {
        oib = OffsetInBlock(File->Fs, FileOffset);
        BlockNum = OffsetToBlocks(File->Fs, FileOffset);

        ClSize = WriteSize = BlockSize(File->Fs);

        Sc = MapBlock(File, BlockNum, &BlockNum);
        if (FAILED(Sc))
            break;

#if __WANTS_BUFFER_CACHE
        if (File->HeldBuffer != NULL) {
            BcacheSpillBuffer(File->HeldBuffer, FALSE);
            File->HeldBuffer = NULL;
            File->HeldBufferOffset = 0;
        }
        /* Note BlockNum is a relative sector number. Make absolute
         */
        Sc = BcacheLookupWrite(Device, 
                              BlockNum + File->Fs->FirstSector, 
                              ClSize, &File->HeldBuffer);
        if (!FAILED(Sc)) {
            File->HeldBufferOffset = FileOffset - oib;
            WriteSize -= oib;
            if (WriteSize > ByteCount)
                WriteSize = ByteCount;

            memcpy(BcacheGetBufferData(File->HeldBuffer) + oib, 
                   (PTR) pBuffer,
                   WriteSize);

            BcacheAddToBufferState(File->HeldBuffer, BLK_DIRTY);
    
        }
#else /* __WANTS_BUFFER_CACHE */
        /*
         * Do we have the block cached ?
         */
        if (IoBuffer && File->CachedClusterNumber == BlockNum) {
CopyCached:
            WriteSize -= oib;
            if (WriteSize > ByteCount)
                WriteSize = ByteCount;
            memcpy(IoBuffer+oib, (const PTR) pBuffer, WriteSize);
            File->Attributes |= ATTR_MODIFIED;
        }

        /*
         * Not cached, should we actually cache it ?
         * Dont cache large, aligned writes
         */
        else if ((oib == 0) && ClSize <= ByteCount) {

            Sc = FatStubbornlyReadWrite(Device,
                                    (UINT64)(BlockNum + File->Fs->FirstSector)
                                              * FAT_MIN_SECTOR_SIZE,
                                    (BYTE*)pBuffer, ClSize, &WriteSize, FALSE);
        }

        /*
         * Worth caching
         */
        else {
            if (IoBuffer == NULL) {
                (void) FatWireMemory( (ADDRESS *) &IoBuffer, ClSize);
                File->HeldBuffer = IoBuffer;
            } else {
                /* Do we need to write back the old block ?
                 */
                Sc = FatFlushMyBuffer( File );
                if (FAILED(Sc))
                    goto CheckErr;
            }

            Sc = FatStubbornlyReadWrite(Device,
                                     (UINT64)(BlockNum + File->Fs->FirstSector)
                                     * FAT_MIN_SECTOR_SIZE,
                                     IoBuffer, ClSize, &WriteSize, TRUE);
            if (!FAILED(Sc)) {
                File->CachedClusterNumber = BlockNum;
                File->HeldBufferOffset = FileOffset - oib;
                goto CopyCached;
            }
        }
    CheckErr:
#endif /* __WANTS_BUFFER_CACHE */

        if (FAILED(Sc)) {
            if (FatError(File, Sc))
                goto Exit;
            else
                continue;
        }

        pBuffer += WriteSize;
        ByteCount -= WriteSize;
        FileOffset += WriteSize;
        BytesWritten += WriteSize;
    }

    Sc = NO_ERROR;

 Exit:
    if (pSizeWritten)
        *pSizeWritten = BytesWritten;
    
#else
    Sc = E_ACCESS_DENIED;
#endif
    return Sc;
}

/* Get/Set a file's size
 */
SCODE MCT FatFileGetSize(PIFILE pThis, PUINT64 pSize)
{
    Uint32ToUint64(*pSize,((FATFILE*)pThis)->Size);
    return NO_ERROR;
}

SCODE MCT FatFileSetSize(PIFILE pThis, UINT64 Size)
{   
#if __WANTS_FATFS_WRITES
    FATFILE * File = (FATFILE *)pThis;
    UINT32 Size32;
    SCODE Sc;

    if (File->Attributes & FAT_ATTR_RONLY)
        goto ErrorExit;

    /* Cant handle more than 32 bits, sorry */
    Size32 = Uint64ToUint32(Size);
    Sc = (Size32 > File->Size) ? 
        FatFileGrow(File, Size32) :
        FatFileShrink(File, Size32, FALSE);

    return Sc;
 ErrorExit:
#endif
    return E_ACCESS_DENIED;
}

PRIVATE const struct IFileVtbl FatFileVtbl = {
    __VTABLE_COMPATIBILITY_FILLER_INITIALIZER

    FatFileQueryInterface,
    FatFileAddRef,
    FatFileRelease,
    FatFileReadAt,
    FatFileWriteAt,
    FatFileSetSize,
    FatFileGetSize
};


/* ================================================
 * Fat file system IName interface
 * ================================================
 */

/* Constructor
 */
SCODE FatNameNew(PIUNKNOWN Volume, PINAMESPACE *pName)
{
    FATNAME * pfn;
    PIHEAP pIHeap = CurrentHeap();

    *pName = NULL;
    if (Volume == NULL)
        return E_INVALID_PARAMETER;

    pfn = (FATNAME *) pIHeap->v->Alloc(pIHeap, HEAP_ZERO_MEMORY, sizeof(FATNAME), 0);
    DBGOUT(DBGFS, 0, printf("fnn %x(%d)\n",(ADDRESS)pfn,sizeof(FATNAME)));
    if (pfn == NULL)
        return E_NOT_ENOUGH_MEMORY;;

    pfn->v = &FatNameVtbl;
    pfn->RefCnt = 1;
    pfn->Volume = Volume;
    Volume->v->AddRef(Volume);

    *pName = (PINAMESPACE)pfn;
    return NO_ERROR;
}


SCODE MCT FatNameQueryInterface(PINAMESPACE pThis, REFIID pIid, void* *ppUnk)
{
#if 0
    FATNAME * pIName = (FATNAME *)pThis;

    /* unsafe */
    if (pIName->Dir && IidCmp(&IID_IFile, pIid)) {
        pIName->RefCnt++;
        *ppUnk = (PIUNKNOWN) pIName->Dir;
        return NO_ERROR;
    }
#endif
    return GenericQueryInterface((PIUNKNOWN)pThis,pIid,ppUnk,&IID_INameSpace);
}

/*
 * Add a reference to a Name object
 */
UINT MCT FatNameAddRef(PINAMESPACE pThis)
{
    FATNAME * pIName = (FATNAME *)pThis;

    /*printf("fnar-> %d\n",pIName->RefCnt+1); dont or deadlock. */
    return (++pIName->RefCnt);
}


/*
 * Release a reference to a Name object
 * When last ref is gone, destructor.
 */
UINT MCT FatNameRelease(PINAMESPACE pThis)
{
    FATNAME * pIName = (FATNAME *)pThis;
    INT Refs;

    Refs = --pIName->RefCnt;
    /*printf("fnr-> %d\n",Refs);*/
    if (Refs == 0) {
        PIHEAP pIHeap = CurrentHeap();
        if (pIName->Volume)
            pIName->Volume->v->Release(pIName->Volume);
        if (pIName->Dir)
            ((PIFILE) pIName->Dir)->v->Release((PIFILE)pIName->Dir);
        pIHeap->v->Free(pIHeap, 0, pIName);
    }
    return Refs;
}

/* Register not implemented
 */
SCODE MCT FatNameRegister(PINAMESPACE pThis, const _TCHAR * pName, 
                          PIUNKNOWN pObj, UINT Flags, PINAMESPACE pSNS)
{
    DBGOUT(DBGFS, 2, printf("FatNameRegister?!? %s\n", pName));
    return NO_ERROR;
}

SCODE MCT FatNameUnregister(PINAMESPACE pThis, const _TCHAR * pName)
{
#if __WANTS_FATFS_WRITES
    FATFILE * File = NULL;
    SCODE Sc;
    FAT_DIRECTORY_RECORD * dp = NULL;
    UINT FileOffset, NumEntries;
    UINT64 Offset64;

    DBGOUT(DBGFS, 0, printf("FatNameUnregister %x %s\n", (ADDRESS)pThis, pName));

    Sc = FatFileNew((FATNAME *)pThis, pName, NAME_SPACE_WRITE, &File);
    if (FAILED(Sc))
        return Sc;

    /* Files are simpler
     */
    if ((File->Attributes & FAT_ATTR_DIR) == 0)
        goto ShrinkIt;

    /* Cant do this to root, and FatFileShrink takes care of that
     * Make sure its an empty directory, e.g. only 2 entries.
     */
    FileOffset = 0;
    NumEntries = 0;
    while (FileOffset < File->Size) {
        INT c;

        /*
         * Read a new block if necessary
         */
        if (OffsetInBlock(File->Fs, FileOffset) == 0) {
        RetryRead:
            Uint32ToUint64(Offset64,FileOffset);
            Sc = FatFileReadAt((PIFILE)File, Offset64, 
                               NULL, BlockSize(File->Fs), NULL);
            if (FAILED(Sc)) {
                if (!FatError(File, Sc))
                    goto RetryRead;
                goto Exit;
            }

            File->HeldBufferOffset = FileOffset;
            dp = (FAT_DIRECTORY_RECORD *)FatGetCachedBuffer(File);
        }

        c = dp->name[0];

        /* We are done on the first free entry
         */
        if (c == FAT_NAME_EMPTY)
            break;

        /* Labels dont count
         */
        if (c == FAT_NAME_DELETED || dp->attr & FAT_ATTR_LABEL) {
        } else
            /* Stop now if we got past 2 valid entries
             */
            if (++NumEntries > 2)
                break;

        FileOffset += sizeof(*dp);
        dp++;
    }

    if (NumEntries > 2) {
        Sc = E_INVALID_PARAMETER;
        goto Exit;
    }


 ShrinkIt:
    Sc = FatFileShrink( File, 0, TRUE);

 Exit:
    ((PIFILE) File)->v->Release((PIFILE)File);
    return Sc;

#else /*__WANTS_FATFS_WRITES*/
    DBGOUT(DBGFS, 2, printf("FatNameUnregister?!? %s\n", pName));
    return NO_ERROR;
#endif /*__WANTS_FATFS_WRITES*/
}


/* Bind to a named object
 */
SCODE MCT FatNameBind(PINAMESPACE pThis, const _TCHAR * pName, 
                      NAME_SPACE_FLAGS Flags, PIUNKNOWN *ppObject)
{
    FATFILE * File = NULL;
    FATNAME * pfn;
    SCODE sc;
    _TCHAR c;

    DBGOUT(DBGFS, 0, printf("FatNameBind %s Flags %08x\n", pName, Flags));

    /* Lookup the pathname
     */
    sc = FatFileNew((FATNAME *)pThis, pName, Flags, &File);
    if (FAILED(sc))
        return sc;

    if ((File->Attributes & FAT_ATTR_DIR) == 0) {

        /* Simple case, just return the file
         * and we are done
         */
        *ppObject = (PIUNKNOWN) File;
        DBGOUT(DBGFS, 0, printf("FatNameBind ok %x\n", (ADDRESS)File));
        return NO_ERROR;
    }

    /*
     * Make sure she means it
     */
    if ((Flags & NAME_SPACE_CREATE) == 0) {
        c = pName[_tcslen(pName) - 1];
        if ((*pName != _EOS) && !IS_PATH_SEPARATOR(c)) {
            sc = E_INVALID_PARAMETER;
            goto FreeFile;
        }
    }

    /*
     * Create a new Name object
     */
    sc = FatNameNew((PIUNKNOWN)pThis, (PINAMESPACE *) &pfn);
    if (FAILED(sc))
        goto FreeFile;

    /*
     * Remember which directory it is
     */
    pfn->Dir = File;

    /*
     * Done
     */
    *ppObject = (PIUNKNOWN) pfn;
    DBGOUT(DBGFS, 0, printf("FatNameBind d-ok %x\n", (ADDRESS)pfn));

    return NO_ERROR;

FreeFile:
    ((PIFILE) File)->v->Release((PIFILE)File);
    *ppObject = NULL;
    DBGOUT(DBGFS, 0, printf("FatNameBind error %x\n", sc));
    return sc;
}

/*
 * Find first entry in a directory
 */
PRIVATE SCODE MCT FatNameFindFirst(PINAMESPACE pThis, const _TCHAR * pPrefix,
                           _TCHAR * pBuffer, UINT BufSize, PUINT pStrLen)
{   
    FATNAME * pIName = (FATNAME *)pThis;
    FATFILE * File;
    FAT_DIRECTORY_RECORD * dp;
    SCODE sc;

    DBGOUT(DBGFS, 0, printf("FatNameFindFirst %s %x\n", pPrefix, BufSize));

    if (pPrefix == NULL)
        pPrefix = MAPCODEPTR(_T(""));

    /* Check path exists
     */
    sc = FatFileNew(pIName, pPrefix, 0, &File);
    if (FAILED(sc))
        goto ErrorExit;

    /* It better be a directory
     */
    if ((File->Attributes & FAT_ATTR_DIR) == 0) {
        sc = E_ACCESS_DENIED;
        goto FreeFile;
    }
    
    /* Find first valid name in directory
     */
    sc = FatSearchDirectory(File, &dp, MatchFirst, (PTR)0, (PTR)0);
    if (FAILED(sc))
        goto FreeFile;

    /* Copy out, drop any padding
     */
#if __WANTS_LONG_FILENAMES
    if (File->Lfn && File->Lfn->Length)
        sc = LFNameToStr(File->Lfn,pBuffer,BufSize,dp->attr,pStrLen);
    else
#endif
    sc = FNameToStr(dp->name,pBuffer,BufSize,dp->attr|(dp->casebyte<<8),pStrLen);

    /* Release file
     */
    ((PIFILE)File)->v->Release((PIFILE)File);

    DBGOUT(DBGFS,0,printf("Exit FatNameFindFirst %s (sc %x)\n",pBuffer,sc));

    return sc;

FreeFile:
    ((PIFILE)File)->v->Release((PIFILE)File);
ErrorExit:
    DBGOUT(DBGFS,1,printf("Error exit FatNameFindFirst %x\n",sc));
    if (pStrLen)
        *pStrLen = 0;
    return sc;
}

/*
 * Find entry that follows pName in the same directory
 */
SCODE MCT FatNameFindNext(PINAMESPACE pThis, const _TCHAR * pPrefix, 
            const _TCHAR * pPrevious, _TCHAR * pBuffer, UINT BufSize, PUINT pStrLen)
{   
    FATNAME * pIName = (FATNAME *)pThis;
    FATFILE * File;
    FAT_DIRECTORY_RECORD * dp;
    BYTE *Buf;
    BYTE Buf83[MAX83LEN+1];
    BYTE * pSuf;
    SCODE sc;

    DBGOUT(DBGFS, 0, _tprintf(_T("FatNameFindNext %s::%s %x\n"),
        pPrefix?pPrefix:_T("NULL"), pPrevious?pPrevious:_T("NULL"), BufSize));

    if (pPrevious == NULL)
        return FatNameFindFirst(pThis, pPrefix, pBuffer, BufSize, pStrLen);

    if (pPrefix == NULL)
        pPrefix = MAPCODEPTR(_T(""));

    /* Verify last component, make it canonical
     */
    Buf = (BYTE*)pPrevious;
    if (StrToFName(Buf83, (const _TUCHAR **)&pPrevious) &&
        LongStrToFName(Buf83, &pPrevious))
        return E_INVALID_PARAMETER;

    pSuf = Buf83;

    /* Check path exists
     */
    sc = FatFileNew(pIName, pPrefix, 0, &File);
    if (FAILED(sc))
        goto ErrorExit;

    /* It better be a directory
     */
    if ((File->Attributes & FAT_ATTR_DIR) == 0) {
        sc = E_ACCESS_DENIED;
        goto FreeFile;
    }
    
    /* Find next valid name in directory
     */
    sc = FatSearchDirectory(File, &dp, MatchNext, (PTR)&pSuf, (PTR)Buf);

    if (FAILED(sc)) {
        if (sc == E_FILE_NOT_FOUND)
            sc = S_FALSE;
        goto FreeFile;
    }

    /* Copy out, drop any padding
     */
#if __WANTS_LONG_FILENAMES
    if (File->Lfn && File->Lfn->Length)
        sc = LFNameToStr(File->Lfn,pBuffer,BufSize,dp->attr,pStrLen);
    else
#endif
    sc = FNameToStr(dp->name,pBuffer,BufSize,dp->attr|(dp->casebyte<<8),pStrLen);

    /* Release file
     */
    ((PIFILE)File)->v->Release((PIFILE)File);

    DBGOUT(DBGFS,0,printf("Exit FatNameFindNext %s (sc %x)\n",pBuffer,sc));

    return sc;

FreeFile:
    ((PIFILE)File)->v->Release((PIFILE)File);
ErrorExit:
    DBGOUT(DBGFS, 1, printf("Error exit FatNameFindNext %x\n",sc));
    if (pStrLen)
        *pStrLen = 0;
    return sc;
}


/*
 * Rename/Copy not implemented
 */
SCODE MCT FatNameRename(PINAMESPACE pThis, const _TCHAR * pName, const _TCHAR * pNewName)
{
    DBGOUT(DBGFS, 2, printf("NameObjRename NYI\n"));
    return E_ACCESS_DENIED;
}

SCODE MCT FatNameCopy(PINAMESPACE pThis, const _TCHAR * pName, const _TCHAR * pNewCopy)
{
    DBGOUT(DBGFS, 2, printf("NameObjCopy NYI\n"));
    return E_ACCESS_DENIED;
}


SCODE MCT FatNameFlush(PINAMESPACE pThis, const _TCHAR * pName)
{
    DBGOUT(DBGNAME, 2, printf("FatNameFlush NYI\n"));
    return E_ACCESS_DENIED;
}

SCODE MCT FatNameGetCapabilities(PINAMESPACE pThis, const _TCHAR * pName, 
                                 NAME_SPACE_FLAGS *pFlags)
{
    FATFILE * File;
    UINT Flags;
    SCODE sc;

    DBGOUT(DBGNAME, 2, printf("FatNameGetCapabilities %s\n",pName));

    /* Lookup the pathname
     */
    sc = FatFileNew((FATNAME *)pThis, pName, 0, &File);
    if (FAILED(sc))
        return sc;

    /* Map FAT attributes to our bits
     */
    Flags = (File->Attributes & FAT_ATTR_DIR) ? F_DIRECTORY : F_MODIFY;
#if _WANTS_FATFS_WRITES
    if (File->Attributes & FAT_ATTR_RONLY)
        Flags |= F_READONLY;
    else
        Flags |= NAME_SPACE_WRITE | NAME_SPACE_SHARE_WRITE;
#else
    Flags |= F_READONLY;
#endif
    if (File->Attributes & FAT_ATTR_HIDDEN)
        Flags |= F_HIDDEN;
    Flags |= NAME_SPACE_READ | NAME_SPACE_SHARE_READ;

    /* Return result
     */
    if (pFlags != NULL)
        *pFlags = Flags;

    ((PIFILE) File)->v->Release((PIFILE)File);

    return NO_ERROR;
}



PRIVATE const struct INameSpaceVtbl FatNameVtbl = {
    __VTABLE_COMPATIBILITY_FILLER_INITIALIZER

    FatNameQueryInterface, 
    FatNameAddRef, 
    FatNameRelease, 
    FatNameRegister, 
    FatNameUnregister, 
    FatNameBind, 
    FatNameFindNext, 
    FatNameGetCapabilities
};


/* ================================================
 * Fat Volume operations, an IUnknown object.
 */

/*
 * Look at a device to see if it contains
 * a FAT filesystem volume.  Return a pointer
 * to a FatFS object if so, NULL otherwise.
 *
 * BUGBUG: Error code not propagated out.
 */
PRIVATE UINT nActiveVolumes = 0;

SCODE RTLCALLTYPE
FatFsNew(PIFILE Device, PINAMESPACE *pNew)
{
    UINT32 i;
    FAT_VOLUME_INFO *vdp;
    UINT8 *Buffer;
    UINT fatlen, start, rb;
    SCODE Sc;
    UINT64 Offset64;
    FATFS * Fs = NULL;
    PIHEAP pIHeap = CurrentHeap();

    *pNew = NULL;
    Buffer = (PTR)pIHeap->v->Alloc(pIHeap, 0, FAT_MIN_SECTOR_SIZE, 0);
    DBGME(0,printf("ffn %x(%d)\n",(ADDRESS)Buffer,FAT_MIN_SECTOR_SIZE));
    if (Buffer == NULL)
        return E_NOT_ENOUGH_MEMORY;

    /*
     * Read the Volume Label
     */
    vdp = (FAT_VOLUME_INFO *)Buffer;
    start = 0;

ReadLabel:
    Uint32ToUint64(Offset64,(start + FAT_LABEL_SECTOR) * FAT_MIN_SECTOR_SIZE);
    for (i=0, Sc = E_MEDIA_CHANGED; i<2 && Sc == E_MEDIA_CHANGED; i++) {
        Sc = FatStubbornlyReadWrite(Device, Offset64, Buffer,
                                 FAT_MIN_SECTOR_SIZE, &rb, TRUE);
    }
    if (FAILED(Sc))
        goto out;
    if (FatInt16(vdp->label.magic) != BIOS_LABEL_MAGIC) {
        Sc = E_UNRECOGNIZED_MEDIA;
#if 0
        printf("f16: %x %x != %x\n", Sc, FatInt16(vdp->label.magic), BIOS_LABEL_MAGIC);
        printf("Buffer @%x Sector %d:", (ADDRESS)Buffer, start);
        for (i = 0; i < 512; i++) {
            if ((i & 15) == 0)
                printf("\n%8x[%3d]",Buffer+i,i);
            printf(" %2x",Buffer[i]);
            if ((i & 15) == 15) {
                UINT j;
                printf("  ");
                for (j = (i & ~15); j <= i; j++)
                    printf("%c", isprint(Buffer[j]) ? Buffer[j] : '.');
            }
        }
        printf("\n");
#endif
        goto out;
    }

    /*
     * Looks like a FAT volume, allocate a descriptor
     */
    if (Fs == NULL) {
        Fs = (FATFS *) pIHeap->v->Alloc(pIHeap, HEAP_ZERO_MEMORY, sizeof(FATFS), 0);
        /*printf("ffn2 %x(%d)\n",Fs,sizeof(FATFS));*/
        if (Fs == NULL) {
            Sc = E_NOT_ENOUGH_MEMORY;
            goto out;
        }
        Fs->v = &FatVolumeVtbl;
        Fs->RefCnt = 0; /* CAVEAT EMPTOR */
        Fs->Device = Device;
    }

#if 0
    /* Sometimes useful debug code
     */
    {
        printf("Buffer @%x Sector %d:", Buffer, start);
        for (i = 0; i < 512; i++) {
            if ((i & 15) == 0)
                printf("\n%8x[%3d]",Buffer+i,i);
            printf(" %2x",Buffer[i]);
            if ((i & 15) == 15) {
                UINT j;
                printf("  ");
                for (j = (i & ~15); j <= i; j++)
                    printf("%c", isprint(Buffer[j]) ? Buffer[j] : '.');
            }
        }
        printf("\n");
    }
#endif

    /* Rudimentary code to deal with partitioning.
     * Only gets the first partition (extended or not)
     */
    {
        BIOS_PARTITION_INFO *pPart;

        pPart = (BIOS_PARTITION_INFO *)vdp->label.partitions;
        for (i = 0; i < 4; i++, pPart++) {

            DBGOUT(DBGFS, 0,
                   printf("Part%d: %x %x %x %x %x %x %x %x %x %x\n", i,
                        pPart->bootid, pPart->beghead, pPart->begsect,
                        pPart->begcyl, pPart->systid, pPart->endhead,
                        pPart->endsect, pPart->endcyl,
                        FatInt32(pPart->offset), FatInt32(pPart->n_sectors)));

            if ((pPart->systid == SYS_FAT) ||
                (pPart->systid == SYS_FAT16) ||
                (pPart->systid == SYS_EXT)) {

                /* Read the real Volume Label
                 */
                start += FatInt32(pPart->offset);
                Fs->FirstSector = start;
                goto ReadLabel;
            }
            if (pPart->systid == SYS_COMPAQ) {

                /* Skip the system configuration partition
                 */
                start += FatInt32(pPart->offset) + FatInt32(pPart->n_sectors);
                Fs->FirstSector = start;
                goto ReadLabel;
            }
        }
    }

    /*
     * Block and filesystem sizes
     */
    i = FatInt16(vdp->secsiz) * vdp->clsiz;
    Fs->LogBlockSize = 0;
    while (i > 1) {
        Fs->LogBlockSize++;
        i >>= 1;
    }

    i = FatInt16(vdp->psect);
    if (i == 0)
        i = FatInt32(vdp->bigsect);

    /*
     * What does the FAT look like
     */
    Fs->FatBits = (i > 4087) ? 16 : 12;

    fatlen = FatInt16(vdp->fatlen);
    Fs->FatLen = fatlen * FAT_MIN_SECTOR_SIZE;
    Fs->FatStart = FatInt16(vdp->nrsvsect);

#if __WANTS_FATFS_WRITES
    Mutex_Init(&Fs->WriteLock);
    Fs->NumFatCopies = vdp->nfat;
#endif

    /*
     * Rootdir and clusters
     */
    Fs->RootStart = Fs->FatStart + (vdp->nfat * fatlen);

    i = FatInt16(vdp->dirents) * sizeof(FAT_DIRECTORY_RECORD);
    Fs->RootSize = i;
    
    i = (i + FAT_MIN_SECTOR_SIZE - 1) / (UINT) FAT_MIN_SECTOR_SIZE;
    Fs->ClusterStart = i + Fs->RootStart;

    Fs->ClusterSize = vdp->clsiz;

#if __WANTS_BUFFER_CACHE
    nActiveVolumes++;
#endif

    /*
     * Do not read FAT now, do it on first access
     */

    *pNew = (PINAMESPACE)Fs;
    Sc = NO_ERROR;
out:
    if (Buffer)
        (void) pIHeap->v->Free(pIHeap, 0, Buffer);
    if (FAILED(Sc) && Fs)
        (void) pIHeap->v->Free(pIHeap, 0, Fs);

    return Sc;
}

/* Destructor
 */
PRIVATE void FatFsDestroy(FATFS *Fs)
{
    PIHEAP pIHeap = CurrentHeap();

    if (Fs->Fat) {
        FatFsSpillFat(Fs);
        FatUnwireMemory( (ADDRESS) Fs->Fat, Fs->FatLen);
    }
#if __WANTS_LONG_FILENAMES
    if (Fs->Lfn) {
        (void) pIHeap->v->Free(pIHeap, 0, Fs->Lfn);
    }
#endif
    pIHeap->v->Free(pIHeap, 0, Fs);

#if __WANTS_BUFFER_CACHE
    if (--nActiveVolumes == 0)
        BcacheDestroy();
#endif
}

/* QueryInterface and refcounting
 */
SCODE MCT FatFsQueryInterface(PIUNKNOWN pThis, REFIID pIid, void* *ppNew)
{
    return GenericQueryInterface((PIUNKNOWN)pThis,pIid,ppNew,&IID_IUnknown);
}

UINT MCT FatFsAddRef(PIUNKNOWN pThis)
{
    FATFS * Fs = (FATFS *) pThis;

    /*printf("ffar-> %d\n",Fs->RefCnt+1);*/
    return (++Fs->RefCnt);
}

UINT MCT FatFsRelease(PIUNKNOWN pThis)
{
    FATFS * Fs = (FATFS *) pThis;
    INT Refs;

    Refs = --Fs->RefCnt;
    /*printf("ffr-> %d\n",Refs);*/
    if (Refs == 0)
        (void) FatFsDestroy(Fs);

    return Refs;
}

PRIVATE const struct IUnknownVtbl FatVolumeVtbl = {
    __VTABLE_COMPATIBILITY_FILLER_INITIALIZER

    FatFsQueryInterface,
    FatFsAddRef,
    FatFsRelease,
};

/*
 * Read in the Fat table
 */
PRIVATE BOOL
FatFsCacheFat(FATFS *Fs)
{
    PIFILE Device = Fs->Device;
    SCODE  Sc;
    UINT64 Offset64;
    UINT   rb;

    /* Sanity check */
    if (Fs->Fat)
    return TRUE;

    /* Lazy-ed out things
     */
    Sc = FatWireMemory( (ADDRESS*)&Fs->Fat, Fs->FatLen);
    if (FAILED(Sc))
        return FALSE;

    Uint32ToUint64(Offset64,(Fs->FatStart + Fs->FirstSector) * FAT_MIN_SECTOR_SIZE);
    Sc = FatStubbornlyReadWrite(Device, Offset64, (BYTE *)Fs->Fat, 
                             Fs->FatLen, &rb, TRUE);

    if (FAILED(Sc) || rb != Fs->FatLen) {
        FatUnwireMemory( (ADDRESS) Fs->Fat, Fs->FatLen);
        Fs->Fat = NULL;
        return FALSE;
    }

#if __WANTS_FATFS_WRITES
    Fs->Dirty = 0;
    {
        UINT LastEntry;

        Fs->FirstFree = 0;
        Fs->NumFree = 0;
        LastEntry = (Fs->FatLen * NBBY) / Fs->FatBits;
        for (rb = 2; rb < LastEntry; rb++)
            if (FatGetEntry(Fs, rb) == 0)
                if (Fs->NumFree++ == 0)
                    Fs->FirstFree = rb;
        DBGOUT(DBGFS, 0, printf("Fat has %d free of %d tot, first is %d\n",
                                 Fs->NumFree, LastEntry, Fs->FirstFree));
    }
#endif


    return TRUE;
}


/* Write back (all copies of) the FAT table
 */
PRIVATE BOOL
FatFsSpillFat(FATFS *Fs)
{
#if __WANTS_FATFS_WRITES
    PIFILE Device = Fs->Device;
    UINT64 DiskOffset;
    SCODE Sc;
    UINT i, j;

    if (Fs->Fat == NULL)
        return TRUE;

    Mutex_Lock(&Fs->WriteLock);
    if (!Fs->Dirty)
        goto Exit;

#if DEBUG_WRITES
 printf("ffsf %x %d\n", (ADDRESS) Fs, Fs->Dirty);
#endif

    /* Write out primary
     */
    Uint32ToUint64(DiskOffset,(Fs->FatStart + Fs->FirstSector)
                               * FAT_MIN_SECTOR_SIZE);
    if (Fs->Dirty < 2)
        goto WriteSecondaries;

    Sc = FatStubbornlyReadWrite(Device, DiskOffset, (BYTE *)Fs->Fat, 
                            Fs->FatLen, &i, FALSE);
        
    if (FAILED(Sc) || i != Fs->FatLen) {
//      printf("Primary FAT checkpoint failure (err x%x or %d != %d)\n",

//              Sc, i, Fs->FatLen);

    } else
        Fs->Dirty = 1;

 WriteSecondaries:
    j = Fs->NumFatCopies - 1;
    for (i = 1; i < Fs->NumFatCopies; i++) {
        UINT rb;

        DiskOffset = Uint64AddUint32(DiskOffset,Fs->FatLen);

        Sc = FatStubbornlyReadWrite(Device, DiskOffset, (BYTE *)Fs->Fat, 
                                Fs->FatLen, &rb, FALSE);
        
        if (FAILED(Sc) || rb != Fs->FatLen) {
//          printf("Secondary FAT checkpoint failure (err x%x or %d != %d)\n",

//                  Sc, rb, Fs->FatLen);

        } else
            j--;
    }
    /* Did we complete all writes successfully
     */
    if (j == 0)
        Fs->Dirty = 0;

 Exit:
    Mutex_Unlock(&Fs->WriteLock);
#endif
    return TRUE;
}

/* Error handler
 */
PRIVATE BOOL FatError(FATFILE *File, SCODE Error)
{
    FATFS *NFs, *OFs = File->Fs, Tmp;
    PIHEAP pIHeap = CurrentHeap();
    SCODE sc;

    DBGOUT(DBGFS, 0, printf("FatError %x %x\n", (ADDRESS) File, Error));

    if (Error == E_MEDIA_CHANGED) {

#if __WANTS_BUFFER_CACHE
        /* For sanity, must flush Bcache. Cannot write, though..sigh.
         */
        if (File->HeldBuffer)
            BcacheFlushBuffer(File->HeldBuffer, TRUE);
        File->HeldBuffer = NULL;
        BcacheFlushTag((UINT)OFs->Device);
#endif
        /* if we did not read the fat I suppose its ok
         */
        if (OFs->Fat == NULL)
            return FALSE;

        /* Re-read volume info, if unchanged all is fine.
         */
        sc = FatFsNew(OFs->Device, (PINAMESPACE*)&NFs);
        if (FAILED(sc))
            goto Unrecov;

        if (memcmp((BYTE *)&NFs->FatBits, (BYTE *)&OFs->FatBits, 
                   sizeof(FATFS)-offsetof(FATFS, FatBits)) == 0) {

            /* Cache the FAT in, and see if that changed
             */
            if (FatFsCacheFat(NFs) &&
                memcmp((BYTE *)NFs->Fat, (BYTE *)OFs->Fat, NFs->FatLen) == 0) {

                /* same disk was put in again.. sheez
                 */
                FatFsDestroy(NFs);//NFs->v->Release(NFs) dont;

                return FALSE;
            }
        }
        /* Nope, something changed
         * Update inplace because we dont keep track of our children.
         */
        Tmp = *OFs;
        *OFs = *NFs;
        OFs->RefCnt = Tmp.RefCnt;
        if (Tmp.Fat)
            FatUnwireMemory( (ADDRESS) Tmp.Fat, Tmp.FatLen);
        pIHeap->v->Free(pIHeap, 0, NFs);

//        printf("You changed disk ! [try again?]\n");

    }

 Unrecov:
    File->Attributes |= ATTR_HAD_ERROR;
#if 0
    /* This might make things even worse */
    BcacheSpillTag((UINT)OFs->Device);
#endif
    return TRUE;
}

/* ================================================
 * Initialization
 * ================================================
 */

PINAMESPACE RTLCALLTYPE UserFatFsNew(PIFILE Device)
{
    PINAMESPACE Fs = NULL;
    SCODE sc = FatFsNew(Device, &Fs);

    if (!FAILED(sc)) {
        sc = FatNameNew((PIUNKNOWN)Fs, &Fs);
        if (FAILED(sc)) {
            FatFsDestroy((FATFS*)Fs);
            Fs = NULL;
        }
    }
    return (Fs);
}

PINAMESPACE RTLCALLTYPE InitFatFileSystem(_TCHAR *DeviceName)
{
    PIFILE Device;
    PINAMESPACE Fat = NULL;
    SCODE sc;

    /* Initialize the Buffer cache
     */
    DBGME(3,printf("\nBcacheInit()\n"));
    BcacheInit();

    /* Allow NULL as a just get ready indication
     */
    if (DeviceName) {

        sc = BindToObject(CurrentNameSpace(),DeviceName,
                          NAME_SPACE_READ|NAME_SPACE_WRITE,
                          &IID_IFile, (void *) &Device);
        if (!FAILED(sc)) {

            Fat = UserFatFsNew(Device);
            if (Fat == NULL)
                Device->v->Release(Device);

        } else {
            DBGOUT(DBGFS, 3, printf("Fat: %s not found (%x)\n",
                                    DeviceName, sc));
        }
    }
    return Fat;
}


©2004 Microsoft Corporation. All rights reserved.	Terms of Use	Privacy Statement	Accessibility	End User License Agreement

 

 

 

 

 

 

 

 

 

 

 

 
