Subversion Repositories freemyipod

Rev

Rev 862 | Rev 968 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id: fat.c 25459 2010-04-03 22:02:09Z gevaerts $
 *
 * Copyright (C) 2002 by Linus Nielsen Feltzing
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "global.h"
#include "thread.h"
#include "libc/include/string.h"
#include "libc/include/stdio.h"
#include "fat.h"
#include "storage.h"
#include "debug.h"
#include "panic.h"
#include "libc/include/ctype.h"

#define BYTES2INT16(array,pos) \
          (array[pos] | (array[pos+1] << 8 ))
#define BYTES2INT32(array,pos) \
    ((long)array[pos] | ((long)array[pos+1] << 8 ) | \
    ((long)array[pos+2] << 16 ) | ((long)array[pos+3] << 24 ))

#define FATTYPE_FAT12       0
#define FATTYPE_FAT16       1
#define FATTYPE_FAT32       2

/* BPB offsets; generic */
#define BS_JMPBOOT          0
#define BS_OEMNAME          3
#define BPB_BYTSPERSEC      11
#define BPB_SECPERCLUS      13
#define BPB_RSVDSECCNT      14
#define BPB_NUMFATS         16
#define BPB_ROOTENTCNT      17
#define BPB_TOTSEC16        19
#define BPB_MEDIA           21
#define BPB_FATSZ16         22
#define BPB_SECPERTRK       24
#define BPB_NUMHEADS        26
#define BPB_HIDDSEC         28
#define BPB_TOTSEC32        32

/* fat12/16 */
#define BS_DRVNUM           36
#define BS_RESERVED1        37
#define BS_BOOTSIG          38
#define BS_VOLID            39
#define BS_VOLLAB           43
#define BS_FILSYSTYPE       54

/* fat32 */
#define BPB_FATSZ32         36
#define BPB_EXTFLAGS        40
#define BPB_FSVER           42
#define BPB_ROOTCLUS        44
#define BPB_FSINFO          48
#define BPB_BKBOOTSEC       50
#define BS_32_DRVNUM        64
#define BS_32_BOOTSIG       66
#define BS_32_VOLID         67
#define BS_32_VOLLAB        71
#define BS_32_FILSYSTYPE    82

#define BPB_LAST_WORD       510


/* attributes */
#define FAT_ATTR_LONG_NAME   (FAT_ATTR_READ_ONLY | FAT_ATTR_HIDDEN | \
                              FAT_ATTR_SYSTEM | FAT_ATTR_VOLUME_ID)
#define FAT_ATTR_LONG_NAME_MASK (FAT_ATTR_READ_ONLY | FAT_ATTR_HIDDEN | \
                                 FAT_ATTR_SYSTEM | FAT_ATTR_VOLUME_ID | \
                                 FAT_ATTR_DIRECTORY | FAT_ATTR_ARCHIVE )

/* NTRES flags */
#define FAT_NTRES_LC_NAME    0x08
#define FAT_NTRES_LC_EXT     0x10

#define FATDIR_NAME          0
#define FATDIR_ATTR          11
#define FATDIR_NTRES         12
#define FATDIR_CRTTIMETENTH  13
#define FATDIR_CRTTIME       14
#define FATDIR_CRTDATE       16
#define FATDIR_LSTACCDATE    18
#define FATDIR_FSTCLUSHI     20
#define FATDIR_WRTTIME       22
#define FATDIR_WRTDATE       24
#define FATDIR_FSTCLUSLO     26
#define FATDIR_FILESIZE      28

#define FATLONG_ORDER        0
#define FATLONG_TYPE         12
#define FATLONG_CHKSUM       13
#define FATLONG_LAST_LONG_ENTRY 0x40
#define FATLONG_NAME_BYTES_PER_ENTRY 26
/* at most 20 LFN entries, keep coherent with fat_dir->longname size ! */
#define FATLONG_MAX_ORDER    20

#define FATLONG_NAME_CHUNKS 3
static unsigned char FATLONG_NAME_POS[FATLONG_NAME_CHUNKS] = {1, 14, 28};
static unsigned char FATLONG_NAME_SIZE[FATLONG_NAME_CHUNKS] = {10, 12, 4};

#define CLUSTERS_PER_FAT_SECTOR (SECTOR_SIZE / 4)
#define CLUSTERS_PER_FAT16_SECTOR (SECTOR_SIZE / 2)
#define DIR_ENTRIES_PER_SECTOR  (SECTOR_SIZE / DIR_ENTRY_SIZE)
#define DIR_ENTRY_SIZE       32
#define NAME_BYTES_PER_ENTRY 13
#define FAT_BAD_MARK         0x0ffffff7
#define FAT_EOF_MARK         0x0ffffff8
#define FAT_LONGNAME_PAD_BYTE 0xff
#define FAT_LONGNAME_PAD_UCS 0xffff

struct fsinfo {
    unsigned long freecount; /* last known free cluster count */
    unsigned long nextfree;  /* first cluster to start looking for free
                               clusters, or 0xffffffff for no hint */
};
/* fsinfo offsets */
#define FSINFO_FREECOUNT 488
#define FSINFO_NEXTFREE  492

/* Note: This struct doesn't hold the raw values after mounting if
 * bpb_bytspersec isn't 512. All sector counts are normalized to 512 byte
 * physical sectors. */
struct bpb
{
    int bpb_bytspersec;  /* Bytes per sector, typically 512 */
    unsigned int bpb_secperclus;  /* Sectors per cluster */
    int bpb_rsvdseccnt;  /* Number of reserved sectors */
    int bpb_numfats;     /* Number of FAT structures, typically 2 */
    int bpb_totsec16;    /* Number of sectors on the volume (old 16-bit) */
    int bpb_media;       /* Media type (typically 0xf0 or 0xf8) */
    int bpb_fatsz16;     /* Number of used sectors per FAT structure */
    unsigned long bpb_totsec32;    /* Number of sectors on the volume
                                     (new 32-bit) */
    unsigned int last_word;       /* 0xAA55 */

    /**** FAT32 specific *****/
    long bpb_fatsz32;
    long bpb_rootclus;
    long bpb_fsinfo;

    /* variables for internal use */
    unsigned long fatsize;
    unsigned long totalsectors;
    unsigned long rootdirsector;
    unsigned long firstdatasector;
    unsigned long startsector;
    unsigned long dataclusters;
    struct fsinfo fsinfo;
#ifdef HAVE_FAT16SUPPORT
    int bpb_rootentcnt;  /* Number of dir entries in the root */
    /* internals for FAT16 support */
    bool is_fat16; /* true if we mounted a FAT16 partition, false if FAT32 */
    unsigned int rootdiroffset; /* sector offset of root dir relative to start
                                 * of first pseudo cluster */
#endif /* #ifdef HAVE_FAT16SUPPORT */
#ifdef HAVE_MULTIVOLUME
#ifdef HAVE_MULTIDRIVE
    int drive; /* on which physical device is this located */
#endif
    bool mounted; /* flag if this volume is mounted */
#endif
};

static struct bpb fat_bpbs[NUM_VOLUMES]; /* mounted partition info */
static bool initialized = false;
static bool flush_fat_disabled = false;

static int update_fsinfo(IF_MV_NONVOID(struct bpb* fat_bpb));
static int flush_fat(IF_MV_NONVOID(struct bpb* fat_bpb));
static int bpb_is_sane(IF_MV_NONVOID(struct bpb* fat_bpb));
static void *cache_fat_sector(IF_MV2(struct bpb* fat_bpb,)
                              long secnum, bool dirty);
static void unlock_fat_sector(IF_MV2(struct bpb* fat_bpb,)
                              long secnum);
static void create_dos_name(const unsigned char *name, unsigned char *newname);
static void randomize_dos_name(unsigned char *name);
static unsigned long find_free_cluster(IF_MV2(struct bpb* fat_bpb,)
                                       unsigned long start);
static int transfer(IF_MV2(struct bpb* fat_bpb,) unsigned long start,
                    long count, char* buf, bool write );

#define FAT_CACHE_SIZE 4
#define FAT_CACHE_MASK (FAT_CACHE_SIZE-1)

struct fat_cache_entry
{
    long secnum;
    bool valid;
    int locked;
    bool dirty;
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_vol; /* shared cache for all volumes */
#endif
};

static char fat_cache_sectors[FAT_CACHE_SIZE][SECTOR_SIZE] CACHEALIGN_ATTR;
static struct fat_cache_entry fat_cache[FAT_CACHE_SIZE];
static struct mutex cache_mutex;
static struct mutex tempbuf_mutex;
static char fat_tempbuf[SECTOR_SIZE] CACHEALIGN_ATTR;
static bool tempbuf_locked;

#if defined(HAVE_HOTSWAP) && !(CONFIG_STORAGE & STORAGE_MMC) /* A better condition ?? */
void fat_lock(void)
{
    mutex_lock(&cache_mutex, TIMEOUT_BLOCK);
}

void fat_unlock(void)
{
    mutex_unlock(&cache_mutex);
}
#endif

static long cluster2sec(IF_MV2(struct bpb* fat_bpb,) long cluster)
{
#ifndef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
#ifdef HAVE_FAT16SUPPORT
    /* negative clusters (FAT16 root dir) don't get the 2 offset */
    int zerocluster = cluster < 0 ? 0 : 2;
#else
    const long zerocluster = 2;
#endif

    if (cluster > (long)(fat_bpb->dataclusters + 1))
    {
      DEBUGF( "cluster2sec() - Bad cluster number (%ld)", cluster);
        return -1;
    }

    return (cluster - zerocluster) * fat_bpb->bpb_secperclus 
           + fat_bpb->firstdatasector;
}

void fat_size_mv(int volume, unsigned long* size, unsigned long* free)
{
    struct bpb* fat_bpb = &fat_bpbs[volume];
    if (size)
      *size = fat_bpb->dataclusters * (fat_bpb->bpb_secperclus * SECTOR_SIZE / 1024);
    if (free)
      *free = fat_bpb->fsinfo.freecount * (fat_bpb->bpb_secperclus * SECTOR_SIZE / 1024);
}

void fat_size(IF_MV2(int volume,) unsigned long* size, unsigned long* free)
{
#ifndef HAVE_MULTIVOLUME
    const int volume = 0;
#endif
    fat_size_mv(volume, size, free);
}

void fat_init(void)
{
    unsigned int i;

    if (!initialized)
    {
        initialized = true;
        mutex_init(&cache_mutex);
        mutex_init(&tempbuf_mutex);
        tempbuf_locked = false;
    }

    /* mark the FAT cache as unused */
    for(i = 0;i < FAT_CACHE_SIZE;i++)
    {
        fat_cache[i].secnum = -1;
        fat_cache[i].valid = false;
        fat_cache[i].locked = 0;
        fat_cache[i].dirty = false;
#ifdef HAVE_MULTIVOLUME
        fat_cache[i].fat_vol = NULL;
#endif
    }
#ifdef HAVE_MULTIVOLUME
    /* mark the possible volumes as not mounted */
    for (i=0; i<NUM_VOLUMES;i++)
    {
        fat_bpbs[i].mounted = false;
    }
#endif
}

int fat_mount(IF_MV2(int volume,) IF_MD2(int drive,) long startsector)
{
#ifndef HAVE_MULTIVOLUME
    const int volume = 0;
#endif
    struct bpb* fat_bpb = &fat_bpbs[volume];
    int rc;
    int secmult;
    long datasec;
#ifdef HAVE_FAT16SUPPORT
    int rootdirsectors;
#endif

    /* Read the sector */
    unsigned char* buf = fat_get_sector_buffer();
    rc = storage_read_sectors(IF_MD2(drive,) startsector,1,buf);
    if (IS_ERR(rc))
    {
        fat_release_sector_buffer();
        DEBUGF("fat_mount() - Couldn't read BPB (error code %d)", rc);
        PASS_RC(rc, 3, 0);
    }

    memset(fat_bpb, 0, sizeof(struct bpb));
    fat_bpb->startsector    = startsector;
#ifdef HAVE_MULTIDRIVE
    fat_bpb->drive          = drive;
#endif

    fat_bpb->bpb_bytspersec = BYTES2INT16(buf,BPB_BYTSPERSEC);
    secmult = fat_bpb->bpb_bytspersec / SECTOR_SIZE; 
    /* Sanity check is performed later */

    fat_bpb->bpb_secperclus = secmult * buf[BPB_SECPERCLUS];
    fat_bpb->bpb_rsvdseccnt = secmult * BYTES2INT16(buf,BPB_RSVDSECCNT);
    fat_bpb->bpb_numfats    = buf[BPB_NUMFATS];
    fat_bpb->bpb_media      = buf[BPB_MEDIA];
    fat_bpb->bpb_fatsz16    = secmult * BYTES2INT16(buf,BPB_FATSZ16);
    fat_bpb->bpb_fatsz32    = secmult * BYTES2INT32(buf,BPB_FATSZ32);
    fat_bpb->bpb_totsec16   = secmult * BYTES2INT16(buf,BPB_TOTSEC16);
    fat_bpb->bpb_totsec32   = secmult * BYTES2INT32(buf,BPB_TOTSEC32);
    fat_bpb->last_word      = BYTES2INT16(buf,BPB_LAST_WORD);

    /* calculate a few commonly used values */
    if (fat_bpb->bpb_fatsz16 != 0)
        fat_bpb->fatsize = fat_bpb->bpb_fatsz16;
    else
        fat_bpb->fatsize = fat_bpb->bpb_fatsz32;

    if (fat_bpb->bpb_totsec16 != 0)
        fat_bpb->totalsectors = fat_bpb->bpb_totsec16;
    else
        fat_bpb->totalsectors = fat_bpb->bpb_totsec32;

#ifdef HAVE_FAT16SUPPORT
    fat_bpb->bpb_rootentcnt = BYTES2INT16(buf,BPB_ROOTENTCNT);
    if (!fat_bpb->bpb_bytspersec)
    {
        fat_release_sector_buffer();
        RET_ERR(1);
    }
    rootdirsectors = secmult * ((fat_bpb->bpb_rootentcnt * DIR_ENTRY_SIZE
                     + fat_bpb->bpb_bytspersec - 1) / fat_bpb->bpb_bytspersec);
#endif /* #ifdef HAVE_FAT16SUPPORT */

    fat_bpb->firstdatasector = fat_bpb->bpb_rsvdseccnt
#ifdef HAVE_FAT16SUPPORT
        + rootdirsectors
#endif
        + fat_bpb->bpb_numfats * fat_bpb->fatsize;

    /* Determine FAT type */
    datasec = fat_bpb->totalsectors - fat_bpb->firstdatasector;
    if (fat_bpb->bpb_secperclus)
        fat_bpb->dataclusters = datasec / fat_bpb->bpb_secperclus;
    else
    {
        fat_release_sector_buffer();
        RET_ERR(2);
    }

#ifdef TEST_FAT
    /*
      we are sometimes testing with "illegally small" fat32 images,
      so we don't use the proper fat32 test case for test code
    */
    if ( fat_bpb->bpb_fatsz16 )
#else
    if ( fat_bpb->dataclusters < 65525 )
#endif
    { /* FAT16 */
#ifdef HAVE_FAT16SUPPORT
        fat_bpb->is_fat16 = true;
        if (fat_bpb->dataclusters < 4085)
        { /* FAT12 */
            fat_release_sector_buffer();
            DEBUGF("This is FAT12. Go away!");
            RET_ERR(3);
        }
#else /* #ifdef HAVE_FAT16SUPPORT */
        fat_release_sector_buffer();
        DEBUGF("This is not FAT32. Go away!");
        RET_ERR(3);
#endif /* #ifndef HAVE_FAT16SUPPORT */
    }

#ifdef HAVE_FAT16SUPPORT
    if (fat_bpb->is_fat16)
    { /* FAT16 specific part of BPB */
        int dirclusters;  
        fat_bpb->rootdirsector = fat_bpb->bpb_rsvdseccnt
            + fat_bpb->bpb_numfats * fat_bpb->bpb_fatsz16;
        dirclusters = ((rootdirsectors + fat_bpb->bpb_secperclus - 1)
            / fat_bpb->bpb_secperclus); /* rounded up, to full clusters */
        /* I assign negative pseudo cluster numbers for the root directory,
           their range is counted upward until -1. */
        fat_bpb->bpb_rootclus = 0 - dirclusters; /* backwards, before the data*/
        fat_bpb->rootdiroffset = dirclusters * fat_bpb->bpb_secperclus
            - rootdirsectors;
    }
    else
#endif /* #ifdef HAVE_FAT16SUPPORT */
    { /* FAT32 specific part of BPB */
        fat_bpb->bpb_rootclus  = BYTES2INT32(buf,BPB_ROOTCLUS);
        fat_bpb->bpb_fsinfo    = secmult * BYTES2INT16(buf,BPB_FSINFO);
        fat_bpb->rootdirsector = cluster2sec(IF_MV2(fat_bpb,)
                                             fat_bpb->bpb_rootclus);
    }

    rc = bpb_is_sane(IF_MV(fat_bpb));
    if (rc < 0)
    {
        fat_release_sector_buffer();
        DEBUGF( "fat_mount() - BPB is not sane");
        RET_ERR(4);
    }

#ifdef HAVE_FAT16SUPPORT
    if (fat_bpb->is_fat16)
    {
        fat_bpb->fsinfo.freecount = 0xffffffff; /* force recalc below */
        fat_bpb->fsinfo.nextfree = 0xffffffff;
    }
    else
#endif /* #ifdef HAVE_FAT16SUPPORT */
    {
        /* Read the fsinfo sector */
        rc = storage_read_sectors(IF_MD2(drive,)
            startsector + fat_bpb->bpb_fsinfo, 1, buf);
        if (IS_ERR(rc))
        {
            fat_release_sector_buffer();
            DEBUGF( "fat_mount() - Couldn't read FSInfo (error code %d)", rc);
            PASS_RC(rc, 3, 5);
        }
        fat_bpb->fsinfo.freecount = BYTES2INT32(buf, FSINFO_FREECOUNT);
        fat_bpb->fsinfo.nextfree = BYTES2INT32(buf, FSINFO_NEXTFREE);
    }
    fat_release_sector_buffer();

    /* calculate freecount if unset */
    if ( fat_bpb->fsinfo.freecount == 0xffffffff )
    {
        fat_recalc_free(IF_MV(volume));
    }

    DEBUGF("Freecount: %ld",fat_bpb->fsinfo.freecount);
    DEBUGF("Nextfree: 0x%lx",fat_bpb->fsinfo.nextfree);
    DEBUGF("Cluster count: 0x%lx",fat_bpb->dataclusters);
    DEBUGF("Sectors per cluster: %d",fat_bpb->bpb_secperclus);
    DEBUGF("FAT sectors: 0x%lx",fat_bpb->fatsize);

#ifdef HAVE_MULTIVOLUME
    fat_bpb->mounted = true;
#endif

    return 0;
}

#ifdef HAVE_HOTSWAP
int fat_unmount(int volume, bool flush)
{
    int rc;
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[volume];
#else
    (void)volume;
#endif

    if(flush)
    {
        rc = flush_fat(IF_MV(fat_bpb)); /* the clean way, while still alive */
    }
    else
    {   /* volume is not accessible any more, e.g. MMC removed */
        int i;
        mutex_lock(&cache_mutex, TIMEOUT_BLOCK);
        for(i = 0;i < FAT_CACHE_SIZE;i++)
        {
            struct fat_cache_entry *fce = &fat_cache[i];
            if((fce->valid || fce->locked)
#ifdef HAVE_MULTIVOLUME
               && fce->fat_vol == fat_bpb
#endif
              )
            {
                fce->valid = false; /* discard all from that volume */
                fce->locked = 0;
                fce->dirty = false;
            }
        }
        mutex_unlock(&cache_mutex);
        rc = 0;
    }
#ifdef HAVE_MULTIVOLUME
    fat_bpb->mounted = false;
#endif
    return rc;
}
#endif /* #ifdef HAVE_HOTSWAP */

void fat_recalc_free(IF_MV_NONVOID(int volume))
{
#ifndef HAVE_MULTIVOLUME
    const int volume = 0;
#endif
    struct bpb* fat_bpb = &fat_bpbs[volume];
    long free = 0;
    unsigned long i;
#ifdef HAVE_FAT16SUPPORT
    if (fat_bpb->is_fat16)
    {
        for (i = 0; i<fat_bpb->fatsize; i++) {
            unsigned int j;
            unsigned short* fat = cache_fat_sector(IF_MV2(fat_bpb,) i, false);
            for (j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++) {
                unsigned int c = i * CLUSTERS_PER_FAT16_SECTOR + j;
                if ( c > fat_bpb->dataclusters+1 ) /* nr 0 is unused */
                    break;

                if (letoh16(fat[j]) == 0x0000) {
                    free++;
                    if ( fat_bpb->fsinfo.nextfree == 0xffffffff )
                        fat_bpb->fsinfo.nextfree = c;
                }
            }
            unlock_fat_sector(IF_MV2(fat_bpb,) i);
        }
    }
    else
#endif /* #ifdef HAVE_FAT16SUPPORT */
    {
        for (i = 0; i<fat_bpb->fatsize; i++) {
            unsigned int j;
            unsigned long* fat = cache_fat_sector(IF_MV2(fat_bpb,) i, false);
            for (j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++) {
                unsigned long c = i * CLUSTERS_PER_FAT_SECTOR + j;
                if ( c > fat_bpb->dataclusters+1 ) /* nr 0 is unused */
                    break;

                if (!(letoh32(fat[j]) & 0x0fffffff)) {
                    free++;
                    if ( fat_bpb->fsinfo.nextfree == 0xffffffff )
                        fat_bpb->fsinfo.nextfree = c;
                }
            }
            unlock_fat_sector(IF_MV2(fat_bpb,) i);
        }
    }
    fat_bpb->fsinfo.freecount = free;
    update_fsinfo(IF_MV(fat_bpb));
}

static int bpb_is_sane(IF_MV_NONVOID(struct bpb* fat_bpb))
{
#ifndef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    if(fat_bpb->bpb_bytspersec % SECTOR_SIZE)
    {
        DEBUGF( "bpb_is_sane() - Error: sector size is not sane (%d)",
                fat_bpb->bpb_bytspersec);
        return -1;
    }
    if((long)fat_bpb->bpb_secperclus * (long)fat_bpb->bpb_bytspersec
                                                                   > 128L*1024L)
    {
        DEBUGF( "bpb_is_sane() - Error: cluster size is larger than 128K "
                "(%d * %d = %d)",
                fat_bpb->bpb_bytspersec, fat_bpb->bpb_secperclus,
                fat_bpb->bpb_bytspersec * fat_bpb->bpb_secperclus);
        return -2;
    }
    if(fat_bpb->bpb_numfats != 2)
    {
        DEBUGF( "bpb_is_sane() - Warning: NumFATS is not 2 (%d)",
                fat_bpb->bpb_numfats);
    }
    if(fat_bpb->bpb_media != 0xf0 && fat_bpb->bpb_media < 0xf8)
    {
        DEBUGF( "bpb_is_sane() - Warning: Non-standard "
                "media type (0x%02x)",
                fat_bpb->bpb_media);
    }
    if(fat_bpb->last_word != 0xaa55)
    {
        DEBUGF( "bpb_is_sane() - Error: Last word is not "
                "0xaa55 (0x%04x)", fat_bpb->last_word);
        return -3;
    }

    if (fat_bpb->fsinfo.freecount >
        (fat_bpb->totalsectors - fat_bpb->firstdatasector)/
        fat_bpb->bpb_secperclus)
    {
        DEBUGF( "bpb_is_sane() - Error: FSInfo.Freecount > disk size "
                 "(0x%04lx)", fat_bpb->fsinfo.freecount);
        return -4;
    }

    return 0;
}

static void flush_fat_sector(struct fat_cache_entry *fce,
                             unsigned char *sectorbuf)
{
    int rc;
    long secnum;

    /* With multivolume, use only the FAT info from the cached sector! */
#ifdef HAVE_MULTIVOLUME
    secnum = fce->secnum + fce->fat_vol->startsector;
#else
    secnum = fce->secnum + fat_bpbs[0].startsector;
#endif

    /* Write to the first FAT */
    rc = storage_write_sectors(IF_MD2(fce->fat_vol->drive,)
                           secnum, 1,
                           sectorbuf);
    if(rc < 0)
    {
        panicf(PANIC_KILLUSERTHREADS, "flush_fat_sector() - Could not write sector %ld"
               " (error %d)",
               secnum, rc);
    }
#ifdef HAVE_MULTIVOLUME
    if(fce->fat_vol->bpb_numfats > 1)
#else
    if(fat_bpbs[0].bpb_numfats > 1)
#endif
    {
        /* Write to the second FAT */
#ifdef HAVE_MULTIVOLUME
        secnum += fce->fat_vol->fatsize;
#else
        secnum += fat_bpbs[0].fatsize;
#endif
        rc = storage_write_sectors(IF_MD2(fce->fat_vol->drive,)
                               secnum, 1, sectorbuf);
        if(rc < 0)
        {
            panicf(PANIC_KILLUSERTHREADS, "flush_fat_sector() - Could not write sector %ld"
                   " (error %d)",
                   secnum, rc);
        }
    }
    fce->dirty = false;
}

static void *cache_fat_sector(IF_MV2(struct bpb* fat_bpb,)
                              long fatsector, bool dirty)
{
#ifndef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    long secnum = fatsector + fat_bpb->bpb_rsvdseccnt;
    int cache_index = secnum & FAT_CACHE_MASK;
    struct fat_cache_entry *fce = &fat_cache[cache_index];
    unsigned char *sectorbuf = &fat_cache_sectors[cache_index][0];
    int rc;

    mutex_lock(&cache_mutex, TIMEOUT_BLOCK); /* make changes atomic */

    /* Delete the cache entry if it isn't the sector we want */
    if(fce->valid && (fce->secnum != secnum
#ifdef HAVE_MULTIVOLUME
        || fce->fat_vol != fat_bpb
#endif
    ))
    {
        /* Write back if it is dirty */
        if(fce->dirty)
        {
            flush_fat_sector(fce, sectorbuf);
        }
        fce->valid = false;
    }

    /* Load the sector if it is not cached */
    if(!fce->valid)
    {
        rc = storage_read_sectors(IF_MD2(fat_bpb->drive,)
                              secnum + fat_bpb->startsector,1,
                              sectorbuf);
        if(rc < 0)
        {
            DEBUGF( "cache_fat_sector() - Could not read sector %ld"
                    " (error %d)", secnum, rc);
            mutex_unlock(&cache_mutex);
            return NULL;
        }
        fce->valid = true;
        fce->secnum = secnum;
#ifdef HAVE_MULTIVOLUME
        fce->fat_vol = fat_bpb;
#endif
    }
    fce->locked++;
    if (dirty)
        fce->dirty = true; /* dirt remains, sticky until flushed */
    mutex_unlock(&cache_mutex);
    return sectorbuf;
}

static void unlock_fat_sector(IF_MV2(struct bpb* fat_bpb,) long fatsector)
{
#ifndef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    long secnum = fatsector + fat_bpb->bpb_rsvdseccnt;
    int cache_index = secnum & FAT_CACHE_MASK;
    fat_cache[cache_index].locked--;
}

void* fat_get_sector_buffer()
{
    mutex_lock(&tempbuf_mutex, TIMEOUT_BLOCK);
    if (tempbuf_locked)
        panicf(PANIC_KILLUSERTHREADS, "FAT: Tried to lock temporary sector buffer twice!");
    tempbuf_locked = true;
    return fat_tempbuf;
}

void fat_release_sector_buffer()
{
    tempbuf_locked = false;
    mutex_unlock(&tempbuf_mutex);
}

static unsigned long find_free_cluster(IF_MV2(struct bpb* fat_bpb,)
                                       unsigned long startcluster)
{
#ifndef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    unsigned long sector;
    unsigned long offset;
    unsigned long i;

#ifdef HAVE_FAT16SUPPORT
    if (fat_bpb->is_fat16)
    {
        sector = startcluster / CLUSTERS_PER_FAT16_SECTOR;
        offset = startcluster % CLUSTERS_PER_FAT16_SECTOR;

        for (i = 0; i<fat_bpb->fatsize; i++) {
            unsigned int j;
            unsigned int nr = (i + sector) % fat_bpb->fatsize;
            unsigned short* fat = cache_fat_sector(IF_MV2(fat_bpb,) nr, false);
            if ( !fat )
                break;
            for (j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++) {
                int k = (j + offset) % CLUSTERS_PER_FAT16_SECTOR;
                if (letoh16(fat[k]) == 0x0000) {
                    unsigned int c = nr * CLUSTERS_PER_FAT16_SECTOR + k;
                     /* Ignore the reserved clusters 0 & 1, and also
                        cluster numbers out of bounds */
                    if ( c < 2 || c > fat_bpb->dataclusters+1 )
                        continue;
                    unlock_fat_sector(IF_MV2(fat_bpb,) nr);
                    DEBUGF("find_free_cluster(%x) == %x",startcluster,c);
                    fat_bpb->fsinfo.nextfree = c;
                    return c;
                }
            }
            unlock_fat_sector(IF_MV2(fat_bpb,) nr);
            offset = 0;
        }
    }
    else
#endif /* #ifdef HAVE_FAT16SUPPORT */
    {
        sector = startcluster / CLUSTERS_PER_FAT_SECTOR;
        offset = startcluster % CLUSTERS_PER_FAT_SECTOR;

        for (i = 0; i<fat_bpb->fatsize; i++) {
            unsigned int j;
            unsigned long nr = (i + sector) % fat_bpb->fatsize;
            unsigned long* fat = cache_fat_sector(IF_MV2(fat_bpb,) nr, false);
            if ( !fat )
                break;
            for (j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++) {
                int k = (j + offset) % CLUSTERS_PER_FAT_SECTOR;
                if (!(letoh32(fat[k]) & 0x0fffffff)) {
                    unsigned long c = nr * CLUSTERS_PER_FAT_SECTOR + k;
                     /* Ignore the reserved clusters 0 & 1, and also
                        cluster numbers out of bounds */
                    if ( c < 2 || c > fat_bpb->dataclusters+1 )
                        continue;
                    unlock_fat_sector(IF_MV2(fat_bpb,) nr);
                    DEBUGF("find_free_cluster(%lx) == %lx",startcluster,c);
                    fat_bpb->fsinfo.nextfree = c;
                    return c;
                }
            }
            unlock_fat_sector(IF_MV2(fat_bpb,) nr);
            offset = 0;
        }
    }

    DEBUGF("find_free_cluster(%lx) == 0",startcluster);
    return 0; /* 0 is an illegal cluster number */
}

static int update_fat_entry(IF_MV2(struct bpb* fat_bpb,) unsigned long entry,
                            unsigned long val)
{
#ifndef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
#ifdef HAVE_FAT16SUPPORT
    if (fat_bpb->is_fat16)
    {
        int sector = entry / CLUSTERS_PER_FAT16_SECTOR;
        int offset = entry % CLUSTERS_PER_FAT16_SECTOR;
        unsigned short* sec;

        val &= 0xFFFF;

        DEBUGF("update_fat_entry(%x,%x)",entry,val);

        if (entry==val)
            panicf(PANIC_KILLUSERTHREADS, "Creating FAT loop: %lx,%lx",entry,val);

        if ( entry < 2 )
            panicf(PANIC_KILLUSERTHREADS, "Updating reserved FAT entry %ld.",entry);

        sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, true);
        if (!sec)
        {
            DEBUGF( "update_fat_entry() - Could not cache sector %d", sector);
            return -1;
        }

        if ( val ) {
            if (letoh16(sec[offset]) == 0x0000 && fat_bpb->fsinfo.freecount > 0)
                fat_bpb->fsinfo.freecount--;
        }
        else {
            if (letoh16(sec[offset]))
                fat_bpb->fsinfo.freecount++;
        }

        DEBUGF("update_fat_entry: %d free clusters",
                fat_bpb->fsinfo.freecount);

        sec[offset] = htole16(val);
        unlock_fat_sector(IF_MV2(fat_bpb,) sector);
    }
    else
#endif /* #ifdef HAVE_FAT16SUPPORT */
    {
        long sector = entry / CLUSTERS_PER_FAT_SECTOR;
        int offset = entry % CLUSTERS_PER_FAT_SECTOR;
        unsigned long* sec;

        DEBUGF("update_fat_entry(%lx,%lx)",entry,val);

        if (entry==val)
            panicf(PANIC_KILLUSERTHREADS, "Creating FAT loop: %lx,%lx",entry,val);

        if ( entry < 2 )
            panicf(PANIC_KILLUSERTHREADS, "Updating reserved FAT entry %ld.",entry);

        sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, true);
        if (!sec)
        {
            DEBUGF("update_fat_entry() - Could not cache sector %ld", sector);
            return -1;
        }

        if ( val ) {
            if (!(letoh32(sec[offset]) & 0x0fffffff) &&
                fat_bpb->fsinfo.freecount > 0)
                fat_bpb->fsinfo.freecount--;
        }
        else {
            if (letoh32(sec[offset]) & 0x0fffffff)
                fat_bpb->fsinfo.freecount++;
        }

        DEBUGF("update_fat_entry: %ld free clusters",
                fat_bpb->fsinfo.freecount);

        /* don't change top 4 bits */
        sec[offset] &= htole32(0xf0000000);
        sec[offset] |= htole32(val & 0x0fffffff);
        unlock_fat_sector(IF_MV2(fat_bpb,) sector);
    }

    return 0;
}

static long read_fat_entry(IF_MV2(struct bpb* fat_bpb,) unsigned long entry)
{
#ifdef HAVE_FAT16SUPPORT
#ifndef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    if (fat_bpb->is_fat16)
    {
        int sector = entry / CLUSTERS_PER_FAT16_SECTOR;
        int offset = entry % CLUSTERS_PER_FAT16_SECTOR;
        unsigned short* sec;

        sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, false);
        if (!sec)
        {
            DEBUGF( "read_fat_entry() - Could not cache sector %d", sector);
            return -1;
        }

        long val = letoh16(sec[offset]);
        unlock_fat_sector(IF_MV2(fat_bpb,) sector);

        return val;
    }
    else
#endif /* #ifdef HAVE_FAT16SUPPORT */
    {
        long sector = entry / CLUSTERS_PER_FAT_SECTOR;
        int offset = entry % CLUSTERS_PER_FAT_SECTOR;
        unsigned long* sec;

        sec = cache_fat_sector(IF_MV2(fat_bpb,) sector, false);
        if (!sec)
        {
            DEBUGF( "read_fat_entry() - Could not cache sector %ld", sector);
            return -1;
        }

        long val = letoh32(sec[offset]) & 0x0fffffff;
        unlock_fat_sector(IF_MV2(fat_bpb,) sector);

        return val;
    }
}

static long get_next_cluster(IF_MV2(struct bpb* fat_bpb,) long cluster)
{
    long next_cluster;
    long eof_mark = FAT_EOF_MARK;

#ifdef HAVE_FAT16SUPPORT
#ifndef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    if (fat_bpb->is_fat16)
    {
        eof_mark &= 0xFFFF; /* only 16 bit */
        if (cluster < 0) /* FAT16 root dir */
            return cluster + 1; /* don't use the FAT */
    }
#endif
    next_cluster = read_fat_entry(IF_MV2(fat_bpb,) cluster);

    /* is this last cluster in chain? */
    if ( next_cluster >= eof_mark )
        return 0;
    else
        return next_cluster;
}

static int update_fsinfo(IF_MV_NONVOID(struct bpb* fat_bpb))
{
#ifndef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    unsigned long* intptr;
    int rc;

#ifdef HAVE_FAT16SUPPORT
    if (fat_bpb->is_fat16)
        return 0; /* FAT16 has no FsInfo */
#endif /* #ifdef HAVE_FAT16SUPPORT */

    /* update fsinfo */
    unsigned char* fsinfo = fat_get_sector_buffer();
    rc = storage_read_sectors(IF_MD2(fat_bpb->drive,)
                          fat_bpb->startsector + fat_bpb->bpb_fsinfo, 1,fsinfo);
    if (rc < 0)
    {
        fat_release_sector_buffer();
        DEBUGF( "update_fsinfo() - Couldn't read FSInfo (error code %d)", rc);
        return rc * 10 - 1;
    }
    intptr = (long*)&(fsinfo[FSINFO_FREECOUNT]);
    *intptr = htole32(fat_bpb->fsinfo.freecount);

    intptr = (long*)&(fsinfo[FSINFO_NEXTFREE]);
    *intptr = htole32(fat_bpb->fsinfo.nextfree);

    rc = storage_write_sectors(IF_MD2(fat_bpb->drive,)
                           fat_bpb->startsector + fat_bpb->bpb_fsinfo,1,fsinfo);
    fat_release_sector_buffer();
    if (rc < 0)
    {
        DEBUGF( "update_fsinfo() - Couldn't write FSInfo (error code %d)", rc);
        return rc * 10 - 2;
    }

    return 0;
}

static int flush_fat(IF_MV_NONVOID(struct bpb* fat_bpb))
{
    int i;
    int rc;
    unsigned char *sec;
    if (flush_fat_disabled)
    {
        DEBUGF("flush_fat() skipped");
        return 0;
    }
    DEBUGF("flush_fat()");

    mutex_lock(&cache_mutex, TIMEOUT_BLOCK);
    for(i = 0;i < FAT_CACHE_SIZE;i++)
    {
        struct fat_cache_entry *fce = &fat_cache[i];
        if(fce->valid 
#ifdef HAVE_MULTIVOLUME
            && fce->fat_vol == fat_bpb
#endif
            && fce->dirty)
        {
            sec = fat_cache_sectors[i];
            flush_fat_sector(fce, sec);
        }
    }
    mutex_unlock(&cache_mutex);

    rc = update_fsinfo(IF_MV(fat_bpb));
    if (rc < 0)
        return rc * 10 - 3;

    return 0;
}

static void fat_time(unsigned short* date,
                     unsigned short* time,
                     unsigned short* tenth )
{
#if CONFIG_RTC
    struct tm* tm = get_time();

    if (date)
        *date = ((tm->tm_year - 80) << 9) |
            ((tm->tm_mon + 1) << 5) |
            tm->tm_mday;

    if (time)
        *time = (tm->tm_hour << 11) |
            (tm->tm_min << 5) |
            (tm->tm_sec >> 1);

    if (tenth)
        *tenth = (tm->tm_sec & 1) * 100;
#else

    if (date) *date = 0;
    if (time) *time = 0;
    if (tenth) *tenth = 0;

#endif /* CONFIG_RTC */
}

static int write_long_name(struct fat_file* file,
                           unsigned int firstentry,
                           unsigned int numentries,
                           const unsigned char* name,
                           const unsigned char* shortname,
                           bool is_directory)
{
    unsigned char* entry;
    unsigned int idx = firstentry % DIR_ENTRIES_PER_SECTOR;
    unsigned int sector = firstentry / DIR_ENTRIES_PER_SECTOR;
    unsigned char chksum = 0;
    unsigned int i, j=0;
    unsigned int nameidx=0, namelen = strlen(name);
    int rc;
    unsigned short name_utf16[namelen + 1];

    DEBUGF("write_long_name(file:%lx, first:%d, num:%d, name:%s)",
            file->firstcluster, firstentry, numentries, name);

    rc = fat_seek(file, sector);
    if (rc<0)
        return rc * 10 - 1;

    unsigned char* buf = fat_get_sector_buffer();
    rc = fat_readwrite(file, 1, buf, false);
    if (rc<1)
    {
        fat_release_sector_buffer();
        return rc * 10 - 2;
    }

    /* calculate shortname checksum */
    for (i=11; i>0; i--)
        chksum = ((chksum & 1) ? 0x80 : 0) + (chksum >> 1) + shortname[j++];

    /* calc position of last name segment */
    if ( namelen > NAME_BYTES_PER_ENTRY )
        for (nameidx=0;
             nameidx < (namelen - NAME_BYTES_PER_ENTRY);
             nameidx += NAME_BYTES_PER_ENTRY);

    /* we need to convert the name first    */
    /* since it is written in reverse order */
    for (i = 0; i <= namelen; i++)
        name_utf16[i] = *(name++);

    for (i=0; i < numentries; i++) {
        /* new sector? */
        if ( idx >= DIR_ENTRIES_PER_SECTOR ) {
            /* update current sector */
            rc = fat_seek(file, sector);
            if (rc<0)
            {
                fat_release_sector_buffer();
                return rc * 10 - 3;
            }

            rc = fat_readwrite(file, 1, buf, true);
            if (rc<1)
            {
                fat_release_sector_buffer();
                return rc * 10 - 4;
            }

            /* read next sector */
            rc = fat_readwrite(file, 1, buf, false);
            if (rc<0) {
                fat_release_sector_buffer();
                DEBUGF("Failed writing new sector: %d",rc);
                return rc * 10 - 5;
            }
            if (rc==0)
                /* end of dir */
                memset(buf, 0, SECTOR_SIZE);

            sector++;
            idx = 0;
        }

        entry = buf + idx * DIR_ENTRY_SIZE;

        /* verify this entry is free */
        if (entry[0] && entry[0] != 0xe5 )
        {
            fat_release_sector_buffer();
            panicf(PANIC_KILLUSERTHREADS, "Dir entry %d in sector %x is not free! "
                   "%02x %02x %02x %02x",
                   idx, sector,
                   entry[0], entry[1], entry[2], entry[3]);
        }

        memset(entry, 0, DIR_ENTRY_SIZE);
        if ( i+1 < numentries ) {
            /* longname entry */
            unsigned int k, l = nameidx;

            entry[FATLONG_ORDER] = numentries-i-1;
            if (i==0) {
                /* mark this as last long entry */
                entry[FATLONG_ORDER] |= FATLONG_LAST_LONG_ENTRY;

                /* pad name with 0xffff  */
                for (k=1; k<11; k++) entry[k] = FAT_LONGNAME_PAD_BYTE;
                for (k=14; k<26; k++) entry[k] = FAT_LONGNAME_PAD_BYTE;
                for (k=28; k<32; k++) entry[k] = FAT_LONGNAME_PAD_BYTE;
            };
            /* set name */
            for (k=0; k<5 && l <= namelen; k++) {
                entry[k*2 + 1] = (unsigned char)(name_utf16[l] & 0xff);
                entry[k*2 + 2] = (unsigned char)(name_utf16[l++] >> 8);
            }
            for (k=0; k<6 && l <= namelen; k++) {
                entry[k*2 + 14] = (unsigned char)(name_utf16[l] & 0xff);
                entry[k*2 + 15] = (unsigned char)(name_utf16[l++] >> 8);
            }
            for (k=0; k<2 && l <= namelen; k++) {
                entry[k*2 + 28] = (unsigned char)(name_utf16[l] & 0xff);
                entry[k*2 + 29] = (unsigned char)(name_utf16[l++] >> 8);
            }

            entry[FATDIR_ATTR] = FAT_ATTR_LONG_NAME;
            entry[FATDIR_FSTCLUSLO] = 0;
            entry[FATLONG_TYPE] = 0;
            entry[FATLONG_CHKSUM] = chksum;
            DEBUGF("Longname entry %d (%d): %s", idx, nameidx, name+nameidx);
        }
        else {
            /* shortname entry */
            unsigned short date=0, time=0, tenth=0;
            DEBUGF("Shortname entry: %s", shortname);
            memcpy(entry + FATDIR_NAME, shortname, 11);
            entry[FATDIR_ATTR] = is_directory?FAT_ATTR_DIRECTORY:0;
            entry[FATDIR_NTRES] = 0;

            fat_time(&date, &time, &tenth);
            entry[FATDIR_CRTTIMETENTH] = tenth;
            *(unsigned short*)(entry + FATDIR_CRTTIME) = htole16(time);
            *(unsigned short*)(entry + FATDIR_WRTTIME) = htole16(time);
            *(unsigned short*)(entry + FATDIR_CRTDATE) = htole16(date);
            *(unsigned short*)(entry + FATDIR_WRTDATE) = htole16(date);
            *(unsigned short*)(entry + FATDIR_LSTACCDATE) = htole16(date);
        }
        idx++;
        nameidx -= NAME_BYTES_PER_ENTRY;
    }

    /* update last sector */
    rc = fat_seek(file, sector);
    if (rc<0)
    {
        fat_release_sector_buffer();
        return rc * 10 - 6;
    }

    rc = fat_readwrite(file, 1, buf, true);
    fat_release_sector_buffer();
    if (rc<1)
        return rc * 10 - 7;

    DEBUGF("write_long_name: success");
    return 0;
}

static int fat_checkname(const unsigned char* newname)
{
    static const char invalid_chars[] = "\"*/:<>?\\|";
    int len = strlen(newname);
    /* More sanity checks are probably needed */
    if (len > 255 || newname[len - 1] == '.')
    {
        return -1;
    }
    while (*newname)
    {
        if (*newname < ' ' || strchr(invalid_chars, *newname) != NULL)
            return -1;
        newname++;
    }
    /* check trailing space(s) */
    if(*(--newname) == ' ')
        return -1;

    return 0;
}

static int add_dir_entry(struct fat_dir* dir,
                         struct fat_file* file,
                         const char* name,
                         bool is_directory,
                         bool dotdir)
{
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[dir->file.volume];
#else
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    unsigned char shortname[12];
    int rc;
    unsigned int sector;
    bool done = false;
    int entries_needed, entries_found = 0;
    int firstentry;

    DEBUGF( "add_dir_entry(%s,%lx)",
             name, file->firstcluster);

    /* Don't check dotdirs name for validity */
    if (dotdir == false) {
        rc = fat_checkname(name);
        if (rc < 0) {
            /* filename is invalid */
            return rc * 10 - 1;
        }
    }

#ifdef HAVE_MULTIVOLUME
    file->volume = dir->file.volume; /* inherit the volume, to make sure */
#endif

    /* The "." and ".." directory entries must not be long names */
    if(dotdir) {
        int i;
        strlcpy(shortname, name, 12);
        for(i = strlen(shortname); i < 12; i++)
            shortname[i] = ' ';

        entries_needed = 1;
    } else {
        create_dos_name(name, shortname);

        /* one dir entry needed for every 13 bytes of filename,
           plus one entry for the short name */
        entries_needed = (strlen(name) + (NAME_BYTES_PER_ENTRY-1))
                         / NAME_BYTES_PER_ENTRY + 1;
    }

    unsigned char* buf = fat_get_sector_buffer();
  restart:
    firstentry = -1;

    rc = fat_seek(&dir->file, 0);
    if (rc < 0)
    {
        fat_release_sector_buffer();
        return rc * 10 - 2;
    }

    /* step 1: search for free entries and check for duplicate shortname */
    for (sector = 0; !done; sector++)
    {
        unsigned int i;

        rc = fat_readwrite(&dir->file, 1, buf, false);
        if (rc < 0) {
            fat_release_sector_buffer();
            DEBUGF( "add_dir_entry() - Couldn't read dir"
                    " (error code %d)", rc);
            return rc * 10 - 3;
        }

        if (rc == 0) { /* current end of dir reached */
            DEBUGF("End of dir on cluster boundary");
            break;
        }

        /* look for free slots */
        for (i = 0; i < DIR_ENTRIES_PER_SECTOR; i++)
        {
            switch (buf[i * DIR_ENTRY_SIZE]) {
              case 0:
                entries_found += DIR_ENTRIES_PER_SECTOR - i;
                DEBUGF("Found end of dir %d",
                        sector * DIR_ENTRIES_PER_SECTOR + i);
                i = DIR_ENTRIES_PER_SECTOR - 1;
                done = true;
                break;

              case 0xe5:
                entries_found++;
                DEBUGF("Found free entry %d (%d/%d)",
                        sector * DIR_ENTRIES_PER_SECTOR + i,
                        entries_found, entries_needed);
                break;

              default:
                entries_found = 0;

                /* check that our intended shortname doesn't already exist */
                if (!strncmp(shortname, buf + i * DIR_ENTRY_SIZE, 11)) {
                    /* shortname exists already, make a new one */
                    randomize_dos_name(shortname);
                    DEBUGF("Duplicate shortname, changing to %s",
                            shortname);

                    /* name has changed, we need to restart search */
                    goto restart;
                }
                break;
            }
            if (firstentry < 0 && (entries_found >= entries_needed))
                firstentry = sector * DIR_ENTRIES_PER_SECTOR + i + 1
                             - entries_found;
        }
    }

    /* step 2: extend the dir if necessary */
    if (firstentry < 0)
    {
        DEBUGF("Adding new sector(s) to dir");
        rc = fat_seek(&dir->file, sector);
        if (rc < 0)
        {
            fat_release_sector_buffer();
            return rc * 10 - 4;
        }
        memset(buf, 0, SECTOR_SIZE);

        /* we must clear whole clusters */
        for (; (entries_found < entries_needed) ||
               (dir->file.sectornum < (int)fat_bpb->bpb_secperclus); sector++)
        {
            if (sector >= (65536/DIR_ENTRIES_PER_SECTOR))
            {
                fat_release_sector_buffer();
                return -5; /* dir too large -- FAT specification */
            }

            rc = fat_readwrite(&dir->file, 1, buf, true);
            if (rc < 1)  /* No more room or something went wrong */
            {
                fat_release_sector_buffer();
                return rc * 10 - 6;
            }

            entries_found += DIR_ENTRIES_PER_SECTOR;
        }

        firstentry = sector * DIR_ENTRIES_PER_SECTOR - entries_found;
    }
    fat_release_sector_buffer();

    /* step 3: add entry */
    sector = firstentry / DIR_ENTRIES_PER_SECTOR;
    DEBUGF("Adding longname to entry %d in sector %d",
            firstentry, sector);

    rc = write_long_name(&dir->file, firstentry,
                         entries_needed, name, shortname, is_directory);
    if (rc < 0)
        return rc * 10 - 7;

    /* remember where the shortname dir entry is located */
    file->direntry = firstentry + entries_needed - 1;
    file->direntries = entries_needed;
    file->dircluster = dir->file.firstcluster;
    DEBUGF("Added new dir entry %d, using %d slots.",
            file->direntry, file->direntries);

    return 0;
}

static unsigned char char2dos(unsigned char c, int* randomize)
{
    static const char invalid_chars[] = "\"*+,./:;<=>?[\\]|";

    if (c <= 0x20)
        c = 0;   /* Illegal char, remove */
    else if (strchr(invalid_chars, c) != NULL)
    {
        /* Illegal char, replace */
        c = '_';
        *randomize = 1; /* as per FAT spec */
    }
    else
        c = toupper(c);

    return c;
}

static void create_dos_name(const unsigned char *name, unsigned char *newname)
{
    int i;
    unsigned char *ext;
    int randomize = 0;

    /* Find extension part */
    ext = strrchr(name, '.');
    if (ext == name)         /* handle .dotnames */
        ext = NULL;

    /* needs to randomize? */
    if((ext && (strlen(ext) > 4)) ||
       ((ext ? (unsigned int)(ext-name) : strlen(name)) > 8) )
        randomize = 1;

    /* Name part */
    for (i = 0; *name && (!ext || name < ext) && (i < 8); name++)
    {
        unsigned char c = char2dos(*name, &randomize);
        if (c)
            newname[i++] = c;
    }

    /* Pad both name and extension */
    while (i < 11)
        newname[i++] = ' ';

    if (newname[0] == 0xe5) /* Special kanji character */
        newname[0] = 0x05;

    if (ext)
    {   /* Extension part */
        ext++;
        for (i = 8; *ext && (i < 11); ext++)
        {
            unsigned char c = char2dos(*ext, &randomize);
            if (c)
                newname[i++] = c;
        }
    }

    if(randomize)
        randomize_dos_name(newname);
}

static void randomize_dos_name(unsigned char *name)
{
    unsigned char* tilde = NULL;    /* ~ location */
    unsigned char* lastpt = NULL;   /* last point of filename */
    unsigned char* nameptr = name;  /* working copy of name pointer */
    unsigned char num[9];           /* holds number as string */
    int i = 0;
    int cnt = 1;
    int numlen;
    int offset;

    while(i++ < 8)
    {
        /* hunt for ~ and where to put it */
        if((!tilde) && (*nameptr == '~'))
            tilde = nameptr;
        if((!lastpt) && ((*nameptr == ' ' || *nameptr == '~')))
            lastpt = nameptr;
        nameptr++;
    }
    if(tilde)
    {
        /* extract current count and increment */
        memcpy(num,tilde+1,7-(unsigned int)(tilde-name));
        num[7-(unsigned int)(tilde-name)] = 0;
        cnt = atoi(num) + 1;
    }
    cnt %= 10000000; /* protection */
    snprintf(num, 9, "~%d", cnt);   /* allow room for trailing zero */
    numlen = strlen(num);           /* required space */
    offset = (unsigned int)(lastpt ? lastpt - name : 8); /* prev startpoint */
    if(offset > (8-numlen)) offset = 8-numlen;  /* correct for new numlen */

    memcpy(&name[offset], num, numlen);

    /* in special case of counter overflow: pad with spaces */
    for(offset = offset+numlen; offset < 8; offset++)
        name[offset] = ' ';
}

static int update_short_entry( struct fat_file* file, long size, int attr )
{
    int sector = file->direntry / DIR_ENTRIES_PER_SECTOR;
    unsigned long* sizeptr;
    unsigned short* clusptr;
    struct fat_file dir;
    int rc;

    DEBUGF("update_file_size(cluster:%lx entry:%d size:%ld)",
            file->firstcluster, file->direntry, size);

    /* create a temporary file handle for the dir holding this file */
    rc = fat_open(IF_MV2(file->volume,) file->dircluster, &dir, NULL);
    if (rc < 0)
        return rc * 10 - 1;

    rc = fat_seek( &dir, sector );
    if (rc<0)
        return rc * 10 - 2;

    unsigned char* buf = fat_get_sector_buffer();
    unsigned char* entry =
        buf + DIR_ENTRY_SIZE * (file->direntry % DIR_ENTRIES_PER_SECTOR);
    rc = fat_readwrite(&dir, 1, buf, false);
    if (rc < 1)
    {
        fat_release_sector_buffer();
        return rc * 10 - 3;
    }

    if (!entry[0] || entry[0] == 0xe5)
    {
        fat_release_sector_buffer();
        panicf(PANIC_KILLUSERTHREADS, "Updating size on empty dir entry %d", file->direntry);
    }

    entry[FATDIR_ATTR] = attr & 0xFF;

    clusptr = (short*)(entry + FATDIR_FSTCLUSHI);
    *clusptr = htole16(file->firstcluster >> 16);

    clusptr = (short*)(entry + FATDIR_FSTCLUSLO);
    *clusptr = htole16(file->firstcluster & 0xffff);

    sizeptr = (long*)(entry + FATDIR_FILESIZE);
    *sizeptr = htole32(size);

    {
#if CONFIG_RTC
        unsigned short time = 0;
        unsigned short date = 0;
#else
        /* get old time to increment from */
        unsigned short time = htole16(*(unsigned short*)(entry+FATDIR_WRTTIME));
        unsigned short date = htole16(*(unsigned short*)(entry+FATDIR_WRTDATE));
#endif
        fat_time(&date, &time, NULL);
        *(unsigned short*)(entry + FATDIR_WRTTIME) = htole16(time);
        *(unsigned short*)(entry + FATDIR_WRTDATE) = htole16(date);
        *(unsigned short*)(entry + FATDIR_LSTACCDATE) = htole16(date);
    }

    rc = fat_seek( &dir, sector );
    if (rc < 0)
    {
        fat_release_sector_buffer();
        return rc * 10 - 4;
    }

    rc = fat_readwrite(&dir, 1, buf, true);
    fat_release_sector_buffer();
    if (rc < 1)
        return rc * 10 - 5;

    return 0;
}

static int parse_direntry(struct fat_direntry *de, const unsigned char *buf)
{
    int i=0,j=0;
    unsigned char c;
    bool lowercase;

    memset(de, 0, sizeof(struct fat_direntry));
    de->attr = buf[FATDIR_ATTR];
    de->crttimetenth = buf[FATDIR_CRTTIMETENTH];
    de->crtdate = BYTES2INT16(buf,FATDIR_CRTDATE);
    de->crttime = BYTES2INT16(buf,FATDIR_CRTTIME);
    de->wrtdate = BYTES2INT16(buf,FATDIR_WRTDATE);
    de->wrttime = BYTES2INT16(buf,FATDIR_WRTTIME);
    de->filesize = BYTES2INT32(buf,FATDIR_FILESIZE);
    de->firstcluster = ((long)(unsigned)BYTES2INT16(buf,FATDIR_FSTCLUSLO)) |
        ((long)(unsigned)BYTES2INT16(buf,FATDIR_FSTCLUSHI) << 16);
    /* The double cast is to prevent a sign-extension to be done on CalmRISC16.
       (the result of the shift is always considered signed) */

    /* fix the name */
    lowercase = (buf[FATDIR_NTRES] & FAT_NTRES_LC_NAME);
    c = buf[FATDIR_NAME];
    if (c == 0x05)  /* special kanji char */
        c = 0xe5;
    i = 0;
    while (c != ' ') {
        de->name[j++] = lowercase ? tolower(c) : c;
        if (++i >= 8)
            break;
        c = buf[FATDIR_NAME+i];
    }
    if (buf[FATDIR_NAME+8] != ' ') {
        lowercase = (buf[FATDIR_NTRES] & FAT_NTRES_LC_EXT);
        de->name[j++] = '.';
        for (i = 8; (i < 11) && ((c = buf[FATDIR_NAME+i]) != ' '); i++)
            de->name[j++] = lowercase ? tolower(c) : c;
    }
    return 1;
}

int fat_open(IF_MV2(int volume,)
             long startcluster,
             struct fat_file *file,
             const struct fat_dir* dir)
{
    /* Remember where the file's dir entry is located
     * Do it before assigning other fields so that fat_open
     * can be called with file == &dir->file (see fat_opendir) */
    if ( dir ) {
        file->direntry = dir->entry - 1;
        file->direntries = dir->entrycount;
        file->dircluster = dir->file.firstcluster;
    }
    
    file->firstcluster = startcluster;
    file->lastcluster = startcluster;
    file->lastsector = 0;
    file->clusternum = 0;
    file->sectornum = 0;
    file->eof = false;
#ifdef HAVE_MULTIVOLUME
    file->volume = volume;
    /* fixme: remove error check when done */
    if (volume >= NUM_VOLUMES || !fat_bpbs[volume].mounted)
    {
        DEBUGF("fat_open() illegal volume %d", volume);
        return -1;
    }
#endif

    DEBUGF("fat_open(%lx), entry %d",startcluster,file->direntry);
    return 0;
}

int fat_create_file(const char* name,
                    struct fat_file* file,
                    struct fat_dir* dir)
{
    int rc;

    DEBUGF("fat_create_file(\"%s\",%lx,%lx)",name,(long)file,(long)dir);
    rc = add_dir_entry(dir, file, name, false, false);
    if (!rc) {
        file->firstcluster = 0;
        file->lastcluster = 0;
        file->lastsector = 0;
        file->clusternum = 0;
        file->sectornum = 0;
        file->eof = false;
    }

    return rc;
}

int fat_create_dir(const char* name,
                   struct fat_dir* dir)
{
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[dir->file.volume];
#else
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    int i;
    long sector;
    int rc;
    struct fat_file newdir;

    DEBUGF("fat_create_dir(\"%s\",%lx)",name,(long)dir);

    /* First, add the entry in the parent directory */
    rc = add_dir_entry(dir, &newdir, name, true, false);
    if (rc < 0)
        return rc * 10 - 1;

    /* Allocate a new cluster for the directory */
    newdir.firstcluster = find_free_cluster(IF_MV2(fat_bpb,)
                                            fat_bpb->fsinfo.nextfree);
    if(newdir.firstcluster == 0)
        return -6;

    update_fat_entry(IF_MV2(fat_bpb,) newdir.firstcluster, FAT_EOF_MARK);

    /* Clear the entire cluster */
    unsigned char* buf = fat_get_sector_buffer();
    sector = cluster2sec(IF_MV2(fat_bpb,) newdir.firstcluster);
    for(i = 0;i < (int)fat_bpb->bpb_secperclus;i++) {
        memset(buf, 0, SECTOR_SIZE);
        if (!i)
        {
            memcpy(buf, ".          \x10", 12);
            memcpy(&buf[0x20], "..         \x10", 12);
            ((uint16_t*)buf)[0xd] = newdir.firstcluster;
            ((uint16_t*)buf)[0xa] = newdir.firstcluster >> 16;
            if(dir->file.firstcluster != fat_bpb->bpb_rootclus)
            {
                ((uint16_t*)buf)[0x1d] = dir->file.firstcluster;
                ((uint16_t*)buf)[0x1a] = dir->file.firstcluster >> 16;
            }
        }
        rc = transfer(IF_MV2(fat_bpb,) sector + i, 1, buf, true );
        if (rc < 0)
        {
            fat_release_sector_buffer();
            return rc * 10 - 2;
        }
    }
    fat_release_sector_buffer();

    /* Set the firstcluster field in the direntry */
    update_short_entry(&newdir, 0, FAT_ATTR_DIRECTORY);

    rc = flush_fat(IF_MV(fat_bpb));
    if (rc < 0)
        return rc * 10 - 5;

    return 0;
}

int fat_truncate(const struct fat_file *file)
{
    /* truncate trailing clusters */
    long next;
    long last = file->lastcluster;
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[file->volume];
#endif

    DEBUGF("fat_truncate(%lx, %lx)", file->firstcluster, last);

    for ( last = get_next_cluster(IF_MV2(fat_bpb,) last); last; last = next ) {
        next = get_next_cluster(IF_MV2(fat_bpb,) last);
        update_fat_entry(IF_MV2(fat_bpb,) last,0);
    }
    if (file->lastcluster)
        update_fat_entry(IF_MV2(fat_bpb,) file->lastcluster,FAT_EOF_MARK);

    return 0;
}

int fat_closewrite(struct fat_file *file, long size, int attr)
{
    int rc;
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[file->volume];
#endif
    DEBUGF("fat_closewrite(size=%ld)",size);

    if (!size) {
        /* empty file */
        if ( file->firstcluster ) {
            update_fat_entry(IF_MV2(fat_bpb,) file->firstcluster, 0);
            file->firstcluster = 0;
        }
    }

    if (file->dircluster) {
        rc = update_short_entry(file, size, attr);
        if (rc < 0)
            return rc * 10 - 1;
    }

    flush_fat(IF_MV(fat_bpb));

#ifdef TEST_FAT
    if ( file->firstcluster ) {
        /* debug */
#ifdef HAVE_MULTIVOLUME
        struct bpb* fat_bpb = &fat_bpbs[file->volume];
#else
        struct bpb* fat_bpb = &fat_bpbs[0];
#endif
        long count = 0;
        long len;
        long next;
        for ( next = file->firstcluster; next;
              next = get_next_cluster(IF_MV2(fat_bpb,) next) ) {
            DEBUGF("cluster %ld: %lx", count, next);
            count++;
        }
        len = count * fat_bpb->bpb_secperclus * SECTOR_SIZE;
        DEBUGF("File is %ld clusters (chainlen=%ld, size=%ld)",
                count, len, size );
        if ( len > size + fat_bpb->bpb_secperclus * SECTOR_SIZE)
            panicf(PANIC_KILLUSERTHREADS, "Cluster chain is too long");
        if ( len < size )
            panicf(PANIC_KILLUSERTHREADS, "Cluster chain is too short");
    }
#endif

    return 0;
}

static int free_direntries(struct fat_file* file)
{
    struct fat_file dir;
    int numentries = file->direntries;
    unsigned int entry = file->direntry - numentries + 1;
    unsigned int sector = entry / DIR_ENTRIES_PER_SECTOR;
    int i;
    int rc;

    /* create a temporary file handle for the dir holding this file */
    rc = fat_open(IF_MV2(file->volume,) file->dircluster, &dir, NULL);
    if (rc < 0)
        return rc * 10 - 1;

    rc = fat_seek( &dir, sector );
    if (rc < 0)
        return rc * 10 - 2;

    unsigned char* buf = fat_get_sector_buffer();
    rc = fat_readwrite(&dir, 1, buf, false);
    if (rc < 1)
    {
        fat_release_sector_buffer();
        return rc * 10 - 3;
    }

    for (i=0; i < numentries; i++) {
        DEBUGF("Clearing dir entry %d (%d/%d)",
                entry, i+1, numentries);
        buf[(entry % DIR_ENTRIES_PER_SECTOR) * DIR_ENTRY_SIZE] = 0xe5;
        entry++;

        if ( (entry % DIR_ENTRIES_PER_SECTOR) == 0 ) {
            /* flush this sector */
            rc = fat_seek(&dir, sector);
            if (rc < 0)
            {
                fat_release_sector_buffer();
                return rc * 10 - 4;
            }

            rc = fat_readwrite(&dir, 1, buf, true);
            if (rc < 1)
            {
                fat_release_sector_buffer();
                return rc * 10 - 5;
            }

            if ( i+1 < numentries ) {
                /* read next sector */
                rc = fat_readwrite(&dir, 1, buf, false);
                if (rc < 1)
                {
                    fat_release_sector_buffer();
                    return rc * 10 - 6;
                }
            }
            sector++;
        }
    }

    if ( entry % DIR_ENTRIES_PER_SECTOR ) {
        /* flush this sector */
        rc = fat_seek(&dir, sector);
        if (rc < 0)
        {
            fat_release_sector_buffer();
            return rc * 10 - 7;
        }

        rc = fat_readwrite(&dir, 1, buf, true);
        if (rc < 1)
        {
            fat_release_sector_buffer();
            return rc * 10 - 8;
        }
    }

    fat_release_sector_buffer();
    return 0;
}

int fat_remove(struct fat_file* file)
{
    long next, last = file->firstcluster;
    int rc;
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[file->volume];
#endif

    DEBUGF("fat_remove(%lx)",last);

    while ( last ) {
        next = get_next_cluster(IF_MV2(fat_bpb,) last);
        update_fat_entry(IF_MV2(fat_bpb,) last,0);
        last = next;
    }

    if ( file->dircluster ) {
        rc = free_direntries(file);
        if (rc < 0)
            return rc * 10 - 1;
    }

    file->firstcluster = 0;
    file->dircluster = 0;

    rc = flush_fat(IF_MV(fat_bpb));
    if (rc < 0)
        return rc * 10 - 2;

    return 0;
}

int fat_rename(struct fat_file* file, 
                struct fat_dir* dir, 
                const unsigned char* newname,
                long size,
                int attr)
{
    int rc;
    struct fat_file newfile = *file;
    unsigned char* entry = NULL;
    unsigned short* clusptr = NULL;
    unsigned int parentcluster;
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[file->volume];

    if (file->volume != dir->file.volume) {
        DEBUGF("No rename across volumes!");
        return -1;
    }
#else
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif

    if ( !file->dircluster ) {
        DEBUGF("File has no dir cluster!");
        return -2;
    }

    /* create new name */
    rc = add_dir_entry(dir, &newfile, newname, false, false);
    if (rc < 0)
        return rc * 10 - 2;

    /* write size and cluster link */
    rc = update_short_entry(&newfile, size, attr);
    if (rc < 0)
        return rc * 10 - 3;

    /* remove old name */
    rc = free_direntries(file);
    if (rc < 0)
        return rc * 10 - 4;

    rc = flush_fat(IF_MV(fat_bpb));
    if (rc < 0)
        return rc * 10 - 5;

    /* if renaming a directory, update the .. entry to make sure
       it points to its parent directory (we don't check if it was a move) */
    if(FAT_ATTR_DIRECTORY == attr) {
        /* open the dir that was renamed, we re-use the newfile struct */

        rc = fat_open(IF_MV2(volume,) newfile.firstcluster, &newfile, NULL);
        if (rc < 0)
            return rc * 10 - 6;

        /* get the first sector of the dir */
        rc = fat_seek(&newfile, 0);
        if (rc < 0)
            return rc * 10 - 7;

        unsigned char* buf = fat_get_sector_buffer();
        rc = fat_readwrite(&newfile, 1, buf, false);
        if (rc < 0)
        {
            fat_release_sector_buffer();
            return rc * 10 - 8;
        }

        /* parent cluster is 0 if parent dir is the root - FAT spec (p.29) */
        if(dir->file.firstcluster == fat_bpb->bpb_rootclus)
            parentcluster = 0;
        else
            parentcluster = dir->file.firstcluster;

        entry = buf + DIR_ENTRY_SIZE;
        if(strncmp("..         ", entry, 11))
        {
            fat_release_sector_buffer();
            /* .. entry must be second entry according to FAT spec (p.29) */
            DEBUGF("Second dir entry is not double-dot!");
            return rc * 10 - 9;
        }
        clusptr = (short*)(entry + FATDIR_FSTCLUSHI);
        *clusptr = htole16(parentcluster >> 16);

        clusptr = (short*)(entry + FATDIR_FSTCLUSLO);
        *clusptr = htole16(parentcluster & 0xffff);

        /* write back this sector */
        rc = fat_seek(&newfile, 0);
        if (rc < 0)
        {
            fat_release_sector_buffer();
            return rc * 10 - 7;
        }

        rc = fat_readwrite(&newfile, 1, buf, true);
        fat_release_sector_buffer();
        if (rc < 1)
            return rc * 10 - 8;
    }

    return 0;
}

static long next_write_cluster(struct fat_file* file,
                              long oldcluster,
                              long* newsector)
{
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[file->volume];
#else
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    long cluster = 0;
    long sector;

    DEBUGF("next_write_cluster(%lx,%lx)",file->firstcluster, oldcluster);

    if (oldcluster)
        cluster = get_next_cluster(IF_MV2(fat_bpb,) oldcluster);

    if (!cluster) {
        if (oldcluster > 0)
            cluster = find_free_cluster(IF_MV2(fat_bpb,) oldcluster+1);
        else if (oldcluster == 0)
            cluster = find_free_cluster(IF_MV2(fat_bpb,)
                                        fat_bpb->fsinfo.nextfree);
#ifdef HAVE_FAT16SUPPORT
        else /* negative, pseudo-cluster of the root dir */
            return 0; /* impossible to append something to the root */
#endif

        if (cluster) {
            if (oldcluster)
                update_fat_entry(IF_MV2(fat_bpb,) oldcluster, cluster);
            else
                file->firstcluster = cluster;
            update_fat_entry(IF_MV2(fat_bpb,) cluster, FAT_EOF_MARK);
        }
        else {
#ifdef TEST_FAT
            if (fat_bpb->fsinfo.freecount>0)
                panicf(PANIC_KILLUSERTHREADS, "There is free space, but find_free_cluster() "
                       "didn't find it!");
#endif
            DEBUGF("next_write_cluster(): Disk full!");
            return 0;
        }
    }
    sector = cluster2sec(IF_MV2(fat_bpb,) cluster);
    if (sector<0)
        return 0;

    *newsector = sector;
    return cluster;
}

static int transfer(IF_MV2(struct bpb* fat_bpb,) 
                    unsigned long start, long count, char* buf, bool write )
{
#ifndef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    int rc;

    DEBUGF("transfer(s=%lx, c=%lx, %s)",
        start+ fat_bpb->startsector, count, write?"write":"read");
    if (write) {
        unsigned long firstallowed;
#ifdef HAVE_FAT16SUPPORT
        if (fat_bpb->is_fat16)
            firstallowed = fat_bpb->rootdirsector;
        else
#endif
            firstallowed = fat_bpb->firstdatasector;

        if (start < firstallowed)
            panicf(PANIC_KILLUSERTHREADS, "Write %ld before data", firstallowed - start);
        if (start + count > fat_bpb->totalsectors)
            panicf(PANIC_KILLUSERTHREADS, "Write %ld after data",
                start + count - fat_bpb->totalsectors);
        rc = storage_write_sectors(IF_MD2(fat_bpb->drive,)
                               start + fat_bpb->startsector, count, buf);
    }
    else
        rc = storage_read_sectors(IF_MD2(fat_bpb->drive,)
                              start + fat_bpb->startsector, count, buf);
    if (rc < 0) {
        DEBUGF( "transfer() - Couldn't %s sector %lx"
                " (error code %d)", 
                write ? "write":"read", start, rc);
        return rc;
    }
    return 0;
}


long fat_readwrite( struct fat_file *file, long sectorcount,
                   void* buf, bool write )
{
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[file->volume];
#else
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    long cluster = file->lastcluster;
    long sector = file->lastsector;
    long clusternum = file->clusternum;
    long numsec = file->sectornum;
    bool eof = file->eof;
    long first=0, last=0;
    long i;
    int rc;

    DEBUGF( "fat_readwrite(file:%lx,count:0x%lx,buf:%lx,%s)",
             file->firstcluster,sectorcount,(long)buf,write?"write":"read");
    DEBUGF( "fat_readwrite: sec=%lx numsec=%ld eof=%d",
             sector,numsec, eof?1:0);

    if ( eof && !write)
        return 0;

    /* find sequential sectors and write them all at once */
    for (i=0; (i < sectorcount) && (sector > -1); i++ ) {
        numsec++;
        if ( numsec > (long)fat_bpb->bpb_secperclus || !cluster ) {
            long oldcluster = cluster;
            long oldsector = sector;
            long oldnumsec = numsec;
            if (write)
                cluster = next_write_cluster(file, cluster, &sector);
            else {
                cluster = get_next_cluster(IF_MV2(fat_bpb,) cluster);
                sector = cluster2sec(IF_MV2(fat_bpb,) cluster);
            }

            clusternum++;
            numsec=1;

            if (!cluster) {
                eof = true;
                if ( write ) {
                    /* remember last cluster, in case
                       we want to append to the file */
                    sector = oldsector;
                    cluster = oldcluster;
                    numsec = oldnumsec;
                    clusternum--;
                    i = -1; /* Error code */
                    break;
                }
            }
            else
                eof = false;
        }
        else {
            if (sector)
                sector++;
            else {
                /* look up first sector of file */
                sector = cluster2sec(IF_MV2(fat_bpb,) file->firstcluster);
                numsec=1;
#ifdef HAVE_FAT16SUPPORT
                if (file->firstcluster < 0)
                {   /* FAT16 root dir */
                    sector += fat_bpb->rootdiroffset;
                    numsec += fat_bpb->rootdiroffset;
                }
#endif
            }
        }

        if (!first)
            first = sector;

        if ( ((sector != first) && (sector != last+1)) || /* not sequential */
             (last-first+1 == 256) ) { /* max 256 sectors per ata request */
            long count = last - first + 1;
            rc = transfer(IF_MV2(fat_bpb,) first, count, buf, write );
            if (rc < 0)
                return rc * 10 - 1;

            buf = (char *)buf + count * SECTOR_SIZE;
            first = sector;
        }

        if ((i == sectorcount-1) && /* last sector requested */
            (!eof))
        {
            long count = sector - first + 1;
            rc = transfer(IF_MV2(fat_bpb,) first, count, buf, write );
            if (rc < 0)
                return rc * 10 - 2;
        }

        last = sector;
    }

    file->lastcluster = cluster;
    file->lastsector = sector;
    file->clusternum = clusternum;
    file->sectornum = numsec;
    file->eof = eof;

    /* if eof, don't report last block as read/written */
    if (eof)
        i--;

    DEBUGF("Sectors written: %ld", i);
    return i;
}

int fat_seek(struct fat_file *file, unsigned long seeksector )
{
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[file->volume];
#else
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    long clusternum=0, numclusters=0, sectornum=0, sector=0;
    long cluster = file->firstcluster;
    long i;

#ifdef HAVE_FAT16SUPPORT
    if (cluster < 0) /* FAT16 root dir */
        seeksector += fat_bpb->rootdiroffset;
#endif

    file->eof = false;
    if (seeksector) {
        /* we need to find the sector BEFORE the requested, since
           the file struct stores the last accessed sector */
        seeksector--;
        numclusters = clusternum = seeksector / fat_bpb->bpb_secperclus;
        sectornum = seeksector % fat_bpb->bpb_secperclus;

        if (file->clusternum && clusternum >= file->clusternum)
        {
            cluster = file->lastcluster;
            numclusters -= file->clusternum;
        }

        for (i=0; i<numclusters; i++) {
            cluster = get_next_cluster(IF_MV2(fat_bpb,) cluster);
            if (!cluster) {
                DEBUGF("Seeking beyond the end of the file! "
                       "(sector %ld, cluster %ld)", seeksector, i);
                return -1;
            }
        }

        sector = cluster2sec(IF_MV2(fat_bpb,) cluster) + sectornum;
    }
    else {
        sectornum = -1;
    }

    DEBUGF("fat_seek(%lx, %lx) == %lx, %lx, %lx",
            file->firstcluster, seeksector, cluster, sector, sectornum);

    file->lastcluster = cluster;
    file->lastsector = sector;
    file->clusternum = clusternum;
    file->sectornum = sectornum + 1;
    return 0;
}

int fat_opendir(IF_MV2(int volume,) 
                struct fat_dir *dir, unsigned long startcluster,
                const struct fat_dir *parent_dir)
{
#ifdef HAVE_MULTIVOLUME
    struct bpb* fat_bpb = &fat_bpbs[volume];
    /* fixme: remove error check when done */
    if (volume >= NUM_VOLUMES || !fat_bpbs[volume].mounted)
    {
        DEBUGF("fat_open() illegal volume %d", volume);
        return -1;
    }
#else
    struct bpb* fat_bpb = &fat_bpbs[0];
#endif
    int rc;

    if (startcluster == 0)
        startcluster = fat_bpb->bpb_rootclus;

    rc = fat_open(IF_MV2(volume,) startcluster, &dir->file, parent_dir);
    if(rc)
    {
        DEBUGF( "fat_opendir() - Couldn't open dir"
                " (error code %d)", rc);
        return rc * 10 - 1;
    }
    
    /* assign them after fat_open call so that fat_opendir can be called with the same
     * fat_dir as parent and result */
    dir->entry = 0;
    dir->sector = 0;

    return 0;
}

int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry)
{
    bool done = false;
    int i, j;
    int rc;
    int order;
    unsigned char firstbyte;
    /* Long file names are stored in special entries. Each entry holds
       up to 13 characters. Names can be max 255 chars (not bytes!) long */
    /* The number of long entries in the long name can be retrieve from the first
     * long entry because there are stored in reverse order and have an ordinal */
    int nb_longs = 0;
    /* The long entries are expected to be in order, so remember the last ordinal */
    int last_long_ord = 0;

    dir->entrycount = 0;

    while(!done)
    {
        if ( !(dir->entry % DIR_ENTRIES_PER_SECTOR) || !dir->sector )
        {
            rc = fat_readwrite(&dir->file, 1, dir->sectorcache, false);
            if (rc == 0) {
                /* eof */
                DEBUGF("fat_getnext() - Reached end of dir cluster chain");
                entry->name[0] = 0;
                break;
            }
            if (rc < 0) {
                DEBUGF("fat_getnext() - Couldn't read dir"
                       " (error code %d)", rc);
                return rc * 10 - 1;
            }
            dir->sector = dir->file.lastsector;
        }

        for (i = dir->entry % DIR_ENTRIES_PER_SECTOR;
             i < DIR_ENTRIES_PER_SECTOR; i++) {
            unsigned int entrypos = i * DIR_ENTRY_SIZE;

            firstbyte = dir->sectorcache[entrypos];
            dir->entry++;

            if (firstbyte == 0xe5) {
                /* free entry */
                dir->entrycount = 0;
                continue;
            }

            if (firstbyte == 0) {
                /* last entry */
                DEBUGF("fat_getnext() - Reached final directory entry");
                entry->name[0] = 0;
                dir->entrycount = 0;
                return 0;
            }

            dir->entrycount++;

            /* LFN entry? */
            if ( ( dir->sectorcache[entrypos + FATDIR_ATTR] &
                   FAT_ATTR_LONG_NAME_MASK ) == FAT_ATTR_LONG_NAME ) {
                /* extract ordinal */
                order = dir->sectorcache[entrypos + FATLONG_ORDER] & ~FATLONG_LAST_LONG_ENTRY;
                /* is this entry the first long entry ? (first in order but containing last part) */
                if (dir->sectorcache[entrypos + FATLONG_ORDER] & FATLONG_LAST_LONG_ENTRY) {
                    /* check that order is not too big ! (and non-zero) */
                    if(order <= 0 || order > FATLONG_MAX_ORDER)
                        continue; /* ignore the whole LFN, will trigger lots of warnings */
                    nb_longs = order;
                    last_long_ord = order;
                }
                else {
                    /* check orphan entry */
                    if (nb_longs == 0) {
                        DEBUGF("fat warning: orphan LFN entry");
                        /* ignore */
                        continue;
                    }
                    
                    /* check order */
                    if (order != (last_long_ord - 1)) {
                        DEBUGF("fat warning: wrong LFN ordinal");
                        /* ignore the whole LFN, will trigger lots of warnings */
                        nb_longs = 0;
                    }

                    last_long_ord = order;
                }

                /* copy part, reuse [order] for another purpose :) */
                order = (order - 1) * FATLONG_NAME_BYTES_PER_ENTRY;
                for(j = 0; j < FATLONG_NAME_CHUNKS; j++) {
                    memcpy(dir->longname + order,
                            dir->sectorcache + entrypos + FATLONG_NAME_POS[j],
                            FATLONG_NAME_SIZE[j]);
                    order += FATLONG_NAME_SIZE[j];
                }
            }
            else {
                if ( parse_direntry(entry, dir->sectorcache + entrypos) ) {

                    /* don't return volume id entry */
                    if ( (entry->attr &
                          (FAT_ATTR_VOLUME_ID|FAT_ATTR_DIRECTORY))
                         == FAT_ATTR_VOLUME_ID)
                        continue;

                    /* replace shortname with longname? */
                    /* check that the long name is complete */
                    if (nb_longs != 0 && last_long_ord == 1) {
                        /* hold a copy of the shortname in case the long one is too long */
                        unsigned char shortname[13]; /* 8+3+dot+\0 */
                        int longname_utf8len = 0;
                        /* One character at a time, add 1 for trailing \0, 4 is the maximum size
                         * of a UTF8 encoded character in rockbox */
                        unsigned char longname_utf8segm[4 + 1];
                        unsigned short ucs;
                        int segm_utf8len;
                        /* Temporarily store short name */
                        strcpy(shortname, entry->name);
                        entry->name[0] = 0;

                        /* Convert the FAT name to a utf8-encoded one.
                         * The name is not necessary NUL-terminated ! */
                        for (j = 0; j < nb_longs * FATLONG_NAME_BYTES_PER_ENTRY; j += 2) {
                            ucs = dir->longname[j] | (dir->longname[j + 1] << 8);
                            if(ucs == 0 || ucs == FAT_LONGNAME_PAD_UCS)
                                break;
                            /* utf8encode will return a pointer after the converted
                             * string, subtract the pointer to the start to get the length of it */
                            segm_utf8len = 1;

                            /* warn the trailing zero ! (FAT_FILENAME_BYTES includes it) */
                            if (longname_utf8len + segm_utf8len >= FAT_FILENAME_BYTES) {
                                /* force use of short name */
                                longname_utf8len = FAT_FILENAME_BYTES + 1;
                                break; /* fallback later */
                            }
                            else {
                                if (ucs < 128) longname_utf8segm[0] = (unsigned char)ucs;
                                else longname_utf8segm[0] = '?';
                                longname_utf8segm[segm_utf8len] = 0;
                                strcat(entry->name + longname_utf8len, longname_utf8segm);
                                longname_utf8len += segm_utf8len;
                            }
                        }

                        /* Does the utf8-encoded name fit into the entry? */
                        /* warn the trailing zero ! (FAT_FILENAME_BYTES includes it) */
                        if (longname_utf8len >= FAT_FILENAME_BYTES) {
                            /* Take the short DOS name. Need to utf8-encode it
                               since it may contain chars from the upper half of
                               the OEM code page which wouldn't be a valid utf8.
                               Beware: this file will be shown with strange
                               glyphs in file browser since unicode 0x80 to 0x9F
                               are control characters. */
                            DEBUGF("SN-DOS: %s", shortname);
                            unsigned char *utf8;
                            memcpy(entry->name, shortname, strlen(shortname));
                            *(entry->name + strlen(shortname)) = 0;
                            DEBUGF("SN: %s", entry->name);
                        } else {
                            DEBUGF("LN: %s", entry->name);
                            DEBUGF("LNLen: %d", longname_utf8len);
                        }
                    }
                    done = true;
                    i++;
                    break;
                }
            }
        }
    }
    return 0;
}

unsigned int fat_get_cluster_size(IF_MV_NONVOID(int volume))
{
#ifndef HAVE_MULTIVOLUME
    const int volume = 0;
#endif
    struct bpb* fat_bpb = &fat_bpbs[volume];
    return fat_bpb->bpb_secperclus * SECTOR_SIZE;
}

#ifdef HAVE_MULTIVOLUME
bool fat_ismounted(int volume)
{
    return (volume<NUM_VOLUMES && fat_bpbs[volume].mounted);
}
#endif

void fat_enable_flushing(bool state)
{
    flush_fat_disabled = !state;
    if (state) flush_fat();
}