diff options
Diffstat (limited to 'firmware/drivers/fat.c')
-rw-r--r-- | firmware/drivers/fat.c | 4206 |
1 files changed, 2230 insertions, 1976 deletions
diff --git a/firmware/drivers/fat.c b/firmware/drivers/fat.c index fb75355898..44e5ab2f4c 100644 --- a/firmware/drivers/fat.c +++ b/firmware/drivers/fat.c @@ -8,6 +8,7 @@ * $Id$ * * Copyright (C) 2002 by Linus Nielsen Feltzing + * Copyright (C) 2014 by Michael Sevakis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -18,27 +19,44 @@ * KIND, either express or implied. * ****************************************************************************/ -#include <stdio.h> +#include "config.h" +#include "system.h" +#include "sys/types.h" #include <string.h> -#include <stdlib.h> #include <ctype.h> -#include <stdbool.h> -#include "fat.h" +#include <stdlib.h> +#include <stdio.h> +#include "fs_attr.h" +#include "pathfuncs.h" +#include "disk_cache.h" +#include "file_internal.h" /* for struct filestr_cache */ #include "storage.h" -#include "debug.h" -#include "panic.h" -#include "system.h" #include "timefuncs.h" -#include "kernel.h" #include "rbunicode.h" +#include "debug.h" +#include "panic.h" /*#define LOGF_ENABLE*/ #include "logf.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 BYTES2INT32(array, pos) \ + (((uint32_t)array[pos+0] << 0) | \ + ((uint32_t)array[pos+1] << 8) | \ + ((uint32_t)array[pos+2] << 16) | \ + ((uint32_t)array[pos+3] << 24)) + +#define INT322BYTES(array, pos, val) \ + ((array[pos+0] = (uint32_t)(val) >> 0), \ + (array[pos+1] = (uint32_t)(val) >> 8), \ + (array[pos+2] = (uint32_t)(val) >> 16), \ + (array[pos+3] = (uint32_t)(val) >> 24)) + +#define BYTES2INT16(array, pos) \ + (((uint32_t)array[pos+0] << 0) | \ + ((uint32_t)array[pos+1] << 8)) + +#define INT162BYTES(array, pos, val) \ + ((array[pos+0] = (uint16_t)(val) >> 0), \ + (array[pos+1] = (uint16_t)(val) >> 8)) #define FATTYPE_FAT12 0 #define FATTYPE_FAT16 1 @@ -83,82 +101,129 @@ #define BPB_LAST_WORD 510 +/* Short and long name directory entry template */ +union raw_dirent +{ + struct /* short entry */ + { + uint8_t name[8+3]; /* 0 */ + uint8_t attr; /* 11 */ + uint8_t ntres; /* 12 */ + uint8_t crttimetenth; /* 13 */ + uint16_t crttime; /* 14 */ + uint16_t crtdate; /* 16 */ + uint16_t lstaccdate; /* 18 */ + uint16_t fstclushi; /* 20 */ + uint16_t wrttime; /* 22 */ + uint16_t wrtdate; /* 24 */ + uint16_t fstcluslo; /* 26 */ + uint32_t filesize; /* 28 */ + /* 32 */ + }; + struct /* long entry */ + { + uint8_t ldir_ord; /* 0 */ + uint8_t ldir_name1[10]; /* 1 */ + uint8_t ldir_attr; /* 11 */ + uint8_t ldir_type; /* 12 */ + uint8_t ldir_chksum; /* 13 */ + uint8_t ldir_name2[12]; /* 14 */ + uint16_t ldir_fstcluslo; /* 26 */ + uint8_t ldir_name3[4]; /* 28 */ + /* 32 */ + }; + struct /* raw byte array */ + { + uint8_t data[32]; /* 0 */ + /* 32 */ + }; +}; + + +/* at most 20 LFN entries */ +#define FATLONG_MAX_ORDER 20 +#define FATLONG_NAME_CHARS 13 +#define FATLONG_ORD_F_LAST 0x40 /* 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 ) +#define ATTR_LONG_NAME (ATTR_READ_ONLY | ATTR_HIDDEN | \ + ATTR_SYSTEM | ATTR_VOLUME_ID) +#define ATTR_LONG_NAME_MASK (ATTR_READ_ONLY | ATTR_HIDDEN | \ + ATTR_SYSTEM | ATTR_VOLUME_ID | \ + ATTR_DIRECTORY | ATTR_ARCHIVE ) + +#define IS_LDIR_ATTR(attr) \ + (((attr) & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) + +#define IS_VOL_ID_ATTR(attr) \ + (((attr) & (ATTR_VOLUME_ID | ATTR_DIRECTORY)) == ATTR_VOLUME_ID) /* 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 { - uint32_t freecount; /* last known free cluster count */ - uint32_t nextfree; /* first cluster to start looking for free - clusters, or 0xffffffff for no hint */ +#define FAT_NTRES_LC_NAME 0x08 +#define FAT_NTRES_LC_EXT 0x10 + +#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 FAT_BAD_MARK 0x0ffffff7 +#define FAT_EOF_MARK 0x0ffffff8 +#define FAT16_BAD_MARK 0xfff7 +#define FAT16_EOF_MARK 0xfff8 + +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 +#ifdef HAVE_FAT16SUPPORT +#define BPB_FN_SET16(bpb, fn) (bpb)->fn##__ = fn##16 +#define BPB_FN_SET32(bpb, fn) (bpb)->fn##__ = fn##32 +#define BPB_FN_DECL(fn, args...) (*fn##__)(struct bpb *bpb , ##args) +#define BPB_CALL(fn, bpb, args...) ((bpb)->fn##__(bpb , ##args)) + +#define get_next_cluster(bpb, cluster) \ + BPB_CALL(get_next_cluster, (bpb), (cluster)) +#define find_free_cluster(bpb, startcluster) \ + BPB_CALL(find_free_cluster, (bpb), (startcluster)) +#define update_fat_entry(bpb, entry, value) \ + BPB_CALL(update_fat_entry, (bpb), (entry), (value)) +#define fat_recalc_free_internal(bpb) \ + BPB_CALL(fat_recalc_free_internal, (bpb)) +#else /* !HAVE_FAT16SUPPORT */ +#define get_next_cluster get_next_cluster32 +#define find_free_cluster find_free_cluster32 +#define update_fat_entry update_fat_entry32 +#define fat_recalc_free_internal fat_recalc_free_internal32 +#endif /* HAVE_FAT16SUPPORT */ +struct bpb; +static void update_fsinfo32(struct bpb *fat_bpb); + /* 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 +static struct bpb +{ + unsigned long bpb_bytspersec; /* Bytes per sector, typically 512 */ + unsigned long bpb_secperclus; /* Sectors per cluster */ + unsigned long bpb_rsvdseccnt; /* Number of reserved sectors */ + unsigned long bpb_totsec16; /* Number of sectors on volume (old 16-bit) */ + uint8_t bpb_numfats; /* Number of FAT structures, typically 2 */ + uint8_t bpb_media; /* Media type (typically 0xf0 or 0xf8) */ + uint16_t 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 */ + uint16_t last_word; /* 0xAA55 */ + long bpb_rootclus; /**** FAT32 specific *****/ - long bpb_fatsz32; - long bpb_rootclus; - long bpb_fsinfo; + unsigned long bpb_fatsz32; + unsigned long bpb_fsinfo; /* variables for internal use */ unsigned long fatsize; @@ -167,936 +232,1246 @@ struct bpb unsigned long firstdatasector; unsigned long startsector; unsigned long dataclusters; + unsigned long fatrgnstart; + unsigned long fatrgnend; struct fsinfo fsinfo; #ifdef HAVE_FAT16SUPPORT - int bpb_rootentcnt; /* Number of dir entries in the root */ + unsigned 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 */ + unsigned long rootdirsectornum; /* sector offset of root dir relative to start + * of first pseudo cluster */ +#endif /* HAVE_FAT16SUPPORT */ + + /** Additional information kept for each volume **/ +#ifdef HAVE_FAT16SUPPORT + uint8_t is_fat16; /* true if we mounted a FAT16 partition, false if FAT32 */ #endif - bool mounted; /* flag if this volume is mounted */ +#ifdef HAVE_MULTIDRIVE + uint8_t drive; /* on which physical device is this located */ #endif -}; - -static struct bpb fat_bpbs[NUM_VOLUMES]; /* mounted partition info */ -static bool initialized = 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_MV(struct bpb* fat_bpb,) - long secnum, bool dirty); -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_MV(struct bpb* fat_bpb,) - unsigned long start); -static int transfer(IF_MV(struct bpb* fat_bpb,) unsigned long start, - long count, char* buf, bool write ); - -#define FAT_CACHE_SIZE 0x20 -#define FAT_CACHE_MASK (FAT_CACHE_SIZE-1) - -struct fat_cache_entry -{ - long secnum; - bool inuse; - bool dirty; #ifdef HAVE_MULTIVOLUME - struct bpb* fat_vol ; /* shared cache for all volumes */ + uint8_t volume; /* on which volume is this located (shortcut) */ #endif + uint8_t mounted; /* true if volume is mounted, false otherwise */ +#ifdef HAVE_FAT16SUPPORT + /* some functions are different for different FAT types */ + long BPB_FN_DECL(get_next_cluster, long); + long BPB_FN_DECL(find_free_cluster, long); + int BPB_FN_DECL(update_fat_entry, unsigned long, unsigned long); + void BPB_FN_DECL(fat_recalc_free_internal); +#endif /* HAVE_FAT16SUPPORT */ + +} fat_bpbs[NUM_VOLUMES]; /* mounted partition info */ + +#define IS_FAT_SECTOR(bpb, sector) \ + (!((sector) >= (bpb)->fatrgnend || (sector) < (bpb)->fatrgnstart)) + +/* set error code and jump to routine exit */ +#define FAT_ERROR(_rc) \ + ({ __builtin_constant_p(_rc) ? \ + ({ if (_rc != RC) rc = (_rc); }) : \ + ({ rc = (_rc); }); \ + goto fat_error; }) + +#define FAT_BPB(volume) \ + ({ struct bpb * _bpb = &fat_bpbs[IF_MV_VOL(volume)]; \ + if (!_bpb->mounted) \ + { \ + DEBUGF("%s() - volume %d not mounted\n", \ + __func__, IF_MV_VOL(volume)); \ + _bpb = NULL; \ + } \ + _bpb; }) + +enum add_dir_entry_flags +{ + DIRENT_RETURN = 0x01, /* return the new short entry */ + DIRENT_TEMPL = 0x0e, /* all TEMPL flags */ + DIRENT_TEMPL_CRT = 0x02, /* use template crttime */ + DIRENT_TEMPL_WRT = 0x04, /* use template wrttime */ + DIRENT_TEMPL_ACC = 0x08, /* use template lstacc time */ + DIRENT_TEMPL_TIMES = 0x0e, /* keep all time fields */ }; -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 SHAREDBSS_ATTR; -static struct mutex tempbuf_mutex; -static char fat_tempbuf[SECTOR_SIZE] CACHEALIGN_ATTR; -static bool tempbuf_locked; +struct fatlong_parse_state +{ + int ord_max; + int ord; + uint8_t chksum; +}; -#if defined(HAVE_HOTSWAP) -void fat_lock(void) +static void cache_commit(struct bpb *fat_bpb) { - mutex_lock(&cache_mutex); + dc_lock_cache(); +#ifdef HAVE_FAT16SUPPORT + if (!fat_bpb->is_fat16) +#endif + update_fsinfo32(fat_bpb); + dc_commit_all(IF_MV(fat_bpb->volume)); + dc_unlock_cache(); } -void fat_unlock(void) +static void cache_discard(IF_MV_NONVOID(struct bpb *fat_bpb)) { - mutex_unlock(&cache_mutex); + dc_lock_cache(); + dc_discard_all(IF_MV(fat_bpb->volume)); + dc_unlock_cache(); } -#endif -static long cluster2sec(IF_MV(struct bpb* fat_bpb,) long cluster) +/* caches a FAT or data area sector */ +static void * cache_sector(struct bpb *fat_bpb, unsigned long secnum) { -#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 + unsigned int flags; + void *buf = dc_cache_probe(IF_MV(fat_bpb->volume,) secnum, &flags); - if (cluster > (long)(fat_bpb->dataclusters + 1)) + if (!flags) { - DEBUGF( "cluster2sec() - Bad cluster number (%ld)\n", cluster); - return -1; + int rc = storage_read_sectors(IF_MD(fat_bpb->drive,) + secnum + fat_bpb->startsector, 1, buf); + if (UNLIKELY(rc < 0)) + { + DEBUGF("%s() - Could not read sector %ld" + " (error %d)\n", __func__, secnum, rc); + dc_discard_buf(buf); + return NULL; + } } - return (cluster - zerocluster) * fat_bpb->bpb_secperclus - + fat_bpb->firstdatasector; + return buf; } -void fat_size(IF_MV(int volume,) unsigned long* size, unsigned long* free) +/* returns a raw buffer for a sector; buffer counts as INUSE but filesystem + * contents are NOT loaded before returning - use when completely overwriting + * a sector's contents in order to avoid a fill */ +static void * cache_sector_buffer(IF_MV(struct bpb *fat_bpb,) + unsigned long secnum) { -#ifndef HAVE_MULTIVOLUME - const int volume = 0; -#endif - 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); + unsigned int flags; + return dc_cache_probe(IF_MV(fat_bpb->volume,) secnum, &flags); } -void fat_init(void) +/* flush a cache buffer to storage */ +void dc_writeback_callback(IF_MV(int volume,) unsigned long sector, void *buf) { - unsigned int i; + struct bpb * const fat_bpb = &fat_bpbs[IF_MV_VOL(volume)]; + unsigned int copies = !IS_FAT_SECTOR(fat_bpb, sector) ? + 1 : fat_bpb->bpb_numfats; + + sector += fat_bpb->startsector; - if (!initialized) + while (1) { - initialized = true; - mutex_init(&cache_mutex); - mutex_init(&tempbuf_mutex); - tempbuf_locked = false; + int rc = storage_write_sectors(IF_MD(fat_bpb->drive,) sector, 1, buf); + if (rc < 0) + { + panicf("%s() - Could not write sector %ld" + " (error %d)\n", __func__, sector, rc); + } + + if (--copies == 0) + break; + + /* Update next FAT */ + sector += fat_bpb->fatsize; } +} -#ifdef HAVE_PRIORITY_SCHEDULING - /* Disable this because it is dangerous due to the assumption that - * mutex_unlock won't yield */ - mutex_set_preempt(&cache_mutex, false); -#endif +static void raw_dirent_set_fstclus(union raw_dirent *ent, long fstclus) +{ + ent->fstclushi = htole16(fstclus >> 16); + ent->fstcluslo = htole16(fstclus & 0xffff); +} - /* mark the FAT cache as unused */ - for(i = 0;i < FAT_CACHE_SIZE;i++) +static int bpb_is_sane(struct bpb *fat_bpb) +{ + if (fat_bpb->bpb_bytspersec % SECTOR_SIZE) { - fat_cache[i].secnum = 8; /* We use a "safe" sector just in case */ - fat_cache[i].inuse = false; - fat_cache[i].dirty = false; -#ifdef HAVE_MULTIVOLUME - fat_cache[i].fat_vol = NULL; -#endif + DEBUGF("%s() - Error: sector size is not sane (%lu)\n", + __func__, fat_bpb->bpb_bytspersec); + return -1; } -#ifdef HAVE_MULTIVOLUME - /* mark the possible volumes as not mounted */ - for (i=0; i<NUM_VOLUMES;i++) + + if (fat_bpb->bpb_secperclus * fat_bpb->bpb_bytspersec > 128*1024ul) { - fat_bpbs[i].mounted = false; + DEBUGF("%s() - Error: cluster size is larger than 128K " + "(%lu * %lu = %lu)\n", __func__, + fat_bpb->bpb_bytspersec, fat_bpb->bpb_secperclus, + fat_bpb->bpb_bytspersec * fat_bpb->bpb_secperclus); + return -2; } -#endif -} -/* fat_mount_internal is split out of fat_mount() to avoid having both the sector - * buffer used here and the sector buffer used by update_fsinfo() on stack */ -static int fat_mount_internal(IF_MV(int volume,) IF_MD(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 + if (fat_bpb->bpb_numfats != 2) + { + DEBUGF("%s() - Warning: NumFATS is not 2 (%u)\n", + __func__, fat_bpb->bpb_numfats); + } - unsigned char* buf = fat_get_sector_buffer(); - /* Read the sector */ - rc = storage_read_sectors(IF_MD(drive,) startsector,1,buf); - if(rc) + if (fat_bpb->bpb_media != 0xf0 && fat_bpb->bpb_media < 0xf8) { - fat_release_sector_buffer(); - DEBUGF( "fat_mount() - Couldn't read BPB (error code %d)\n", rc); - return rc * 10 - 1; + DEBUGF("%s() - Warning: Non-standard media type " + "(0x%02x)\n", __func__, fat_bpb->bpb_media); } - memset(fat_bpb, 0, sizeof(struct bpb)); - fat_bpb->startsector = startsector; -#ifdef HAVE_MULTIDRIVE - fat_bpb->drive = drive; -#endif + if (fat_bpb->last_word != 0xaa55) + { + DEBUGF("%s() - Error: Last word is not " + "0xaa55 (0x%04x)\n", __func__, fat_bpb->last_word); + return -3; + } - fat_bpb->bpb_bytspersec = BYTES2INT16(buf,BPB_BYTSPERSEC); - secmult = fat_bpb->bpb_bytspersec / SECTOR_SIZE; - /* Sanity check is performed later */ + if (fat_bpb->fsinfo.freecount > + (fat_bpb->totalsectors - fat_bpb->firstdatasector) / + fat_bpb->bpb_secperclus) + { + DEBUGF("%s() - Error: FSInfo.Freecount > disk size " + "(0x%04lx)\n", __func__, + (unsigned long)fat_bpb->fsinfo.freecount); + return -4; + } - 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); + return 0; +} - /* 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; +static uint8_t shortname_checksum(const unsigned char *shortname) +{ + /* calculate shortname checksum */ + uint8_t chksum = 0; - if (fat_bpb->bpb_totsec16 != 0) - fat_bpb->totalsectors = fat_bpb->bpb_totsec16; - else - fat_bpb->totalsectors = fat_bpb->bpb_totsec32; + for (unsigned int i = 0; i < 11; i++) + chksum = (chksum << 7) + (chksum >> 1) + shortname[i]; -#ifdef HAVE_FAT16SUPPORT - fat_bpb->bpb_rootentcnt = BYTES2INT16(buf,BPB_ROOTENTCNT); - if (!fat_bpb->bpb_bytspersec) - { - fat_release_sector_buffer(); - return -2; - } - rootdirsectors = secmult * ((fat_bpb->bpb_rootentcnt * DIR_ENTRY_SIZE - + fat_bpb->bpb_bytspersec - 1) / fat_bpb->bpb_bytspersec); -#endif /* #ifdef HAVE_FAT16SUPPORT */ + return chksum; +} - fat_bpb->firstdatasector = fat_bpb->bpb_rsvdseccnt -#ifdef HAVE_FAT16SUPPORT - + rootdirsectors -#endif - + fat_bpb->bpb_numfats * fat_bpb->fatsize; +static void parse_short_direntry(const union raw_dirent *ent, + struct fat_direntry *fatent) +{ + fatent->attr = ent->attr; + fatent->crttimetenth = ent->crttimetenth; + fatent->crttime = letoh16(ent->crttime); + fatent->crtdate = letoh16(ent->crtdate); + fatent->lstaccdate = letoh16(ent->lstaccdate); + fatent->wrttime = letoh16(ent->wrttime); + fatent->wrtdate = letoh16(ent->wrtdate); + fatent->filesize = letoh32(ent->filesize); + fatent->firstcluster = ((uint32_t)letoh16(ent->fstcluslo) ) | + ((uint32_t)letoh16(ent->fstclushi) << 16); - /* Determine FAT type */ - datasec = fat_bpb->totalsectors - fat_bpb->firstdatasector; - if (fat_bpb->bpb_secperclus) - fat_bpb->dataclusters = datasec / fat_bpb->bpb_secperclus; - else + /* fix the name */ + bool lowercase = ent->ntres & FAT_NTRES_LC_NAME; + unsigned char c = ent->name[0]; + + if (c == 0x05) /* special kanji char */ + c = 0xe5; + + int j = 0; + + for (int i = 0; c != ' '; c = ent->name[i]) { - fat_release_sector_buffer(); - return -2; - } + fatent->shortname[j++] = lowercase ? tolower(c) : c; -#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!\n"); - return -2; - } -#else /* #ifdef HAVE_FAT16SUPPORT */ - fat_release_sector_buffer(); - DEBUGF("This is not FAT32. Go away!\n"); - return -2; -#endif /* #ifndef HAVE_FAT16SUPPORT */ + if (++i >= 8) + break; } -#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_MV(fat_bpb,) - fat_bpb->bpb_rootclus); + if (ent->name[8] != ' ') + { + lowercase = ent->ntres & FAT_NTRES_LC_EXT; + fatent->shortname[j++] = '.'; + + for (int i = 8; i < 11 && (c = ent->name[i]) != ' '; i++) + fatent->shortname[j++] = lowercase ? tolower(c) : c; } - rc = bpb_is_sane(IF_MV(fat_bpb)); - if (rc < 0) + fatent->shortname[j] = 0; +} + +static unsigned char char2dos(unsigned char c, int *np) +{ + /* FIXME: needs conversion to OEM charset FIRST but there is currently + no unicode function for that! */ + + /* smallest tables with one-step lookup that directly map the lists; + here we're only concerned with what gets through the longname + filter (check_longname will have been called earlier so common + illegal chars are neither in these tables nor checked for) */ + static const unsigned char remove_chars_tbl[3] = + { 0, '.', ' ' }; + + static const unsigned char replace_chars_tbl[11] = + { ',', 0, 0, '[', ';', ']', '=', 0, 0, 0, '+' }; + + if (remove_chars_tbl[c % 3] == c) { - fat_release_sector_buffer(); - DEBUGF( "fat_mount() - BPB is not sane\n"); - return rc * 10 - 3; + /* Illegal char, remove */ + c = 0; + *np = 0; } - -#ifdef HAVE_FAT16SUPPORT - if (fat_bpb->is_fat16) + else if (c >= 0x80 || replace_chars_tbl[c % 11] == c) { - fat_bpb->fsinfo.freecount = 0xffffffff; /* force recalc below */ - fat_bpb->fsinfo.nextfree = 0xffffffff; + /* Illegal char, replace (note: NTFS behavior for extended chars) */ + c = '_'; + *np = 0; } else -#endif /* #ifdef HAVE_FAT16SUPPORT */ { - /* Read the fsinfo sector */ - rc = storage_read_sectors(IF_MD(drive,) - startsector + fat_bpb->bpb_fsinfo, 1, buf); - if (rc < 0) - { - fat_release_sector_buffer(); - DEBUGF( "fat_mount() - Couldn't read FSInfo (error code %d)\n", rc); - return rc * 10 - 4; - } - fat_bpb->fsinfo.freecount = BYTES2INT32(buf, FSINFO_FREECOUNT); - fat_bpb->fsinfo.nextfree = BYTES2INT32(buf, FSINFO_NEXTFREE); + c = toupper(c); } - fat_release_sector_buffer(); - return 0; -} -void* fat_get_sector_buffer() -{ - mutex_lock(&tempbuf_mutex); - if (tempbuf_locked) - panicf("FAT: Tried to lock temporary sector buffer twice!"); - tempbuf_locked = true; - return fat_tempbuf; + return c; } -void fat_release_sector_buffer() +/* convert long name into dos name, possibly recommending randomization */ +static void create_dos_name(unsigned char *basisname, + const unsigned char *name, int *np) { - tempbuf_locked = false; - mutex_unlock(&tempbuf_mutex); + int i; + + /* FIXME: needs conversion to OEM charset FIRST but there is currently + no unicode function for that! */ + + /* as per FAT spec, set "lossy conversion" flag if any destructive + alterations to the name occur other than case changes */ + *np = -1; + + /* find extension part */ + unsigned char *ext = strrchr(name, '.'); + if (ext && (ext == name || strchr(ext, ' '))) + ext = NULL; /* handle .dotnames / extensions cannot have spaces */ + + /* name part */ + for (i = 0; *name && (!ext || name < ext) && (i < 8); name++) + { + unsigned char c = char2dos(*name, np); + if (c) + basisname[i++] = c; + } + + /* pad both name and extension */ + while (i < 11) + basisname[i++] = ' '; + + if (basisname[0] == 0xe5) /* special kanji character */ + basisname[0] = 0x05; + + /* extension part */ + if (!ext++) + return; /* no extension */ + + for (i = 8; *ext && i < 11; ext++) + { + unsigned char c = char2dos(*ext, np); + if (c) + basisname[i++] = c; + } + + if (*ext) + *np = 0; /* extension too long */ } -#ifdef MAX_LOG_SECTOR_SIZE -int fat_get_bytes_per_sector(IF_MV_NONVOID(int volume)) +static void randomize_dos_name(unsigned char *dosname, + const unsigned char *basisname, + int *np) { -#ifdef HAVE_MULTIVOLUME - if(!fat_bpbs[volume].mounted) - return 0; - return fat_bpbs[volume].bpb_bytspersec; -#else - return fat_bpbs[0].bpb_bytspersec; -#endif + int n = *np; + + memcpy(dosname, basisname, 11); + + if (n < 0) + { + /* first one just copies */ + *np = 0; + return; + } + + /* the "~n" string can range from "~1" to "~999999" + of course a directory can have at most 65536 entries which means + the numbers will never be required to get that big in order to map + to a unique name */ + if (++n > 999999) + n = 1; + + unsigned char numtail[8]; /* holds "~n" */ + unsigned int numtaillen = snprintf(numtail, 8, "~%d", n); + + unsigned int basislen = 0; + while (basislen < 8 && basisname[basislen] != ' ') + basislen++; + + memcpy(dosname + MIN(8 - numtaillen, basislen), numtail, numtaillen); + + *np = n; } -#endif -int fat_mount(IF_MV(int volume,) IF_MD(int drive,) long startsector) +/* check long filename for validity */ +static int check_longname(const unsigned char *name) { -#ifndef HAVE_MULTIVOLUME - const int volume = 0; -#endif - struct bpb* fat_bpb = &fat_bpbs[volume]; - int rc; + /* smallest table with one-step lookup that directly maps the list */ + static const unsigned char invalid_chars_tbl[19] = + { + 0, ':', 0, '<', '*', '>', '?', 0, 0, + '/', '|', 0, 0, 0x7f, 0, '"', '\\', 0, 0 + }; - rc = fat_mount_internal(IF_MV(volume,) IF_MD(drive,) startsector); + if (!name) + return -1; - if(rc!=0) return rc; + unsigned int c = *name; - /* calculate freecount if unset */ - if ( fat_bpb->fsinfo.freecount == 0xffffffff ) + do { - fat_recalc_free(IF_MV(volume)); + if (c < 0x20 || invalid_chars_tbl[c % 19] == c) + return -2; } + while ((c = *++name)); - LDEBUGF("Freecount: %ld\n",(unsigned long)fat_bpb->fsinfo.freecount); - LDEBUGF("Nextfree: 0x%lx\n",(unsigned long)fat_bpb->fsinfo.nextfree); - LDEBUGF("Cluster count: 0x%lx\n",(unsigned long)fat_bpb->dataclusters); - LDEBUGF("Sectors per cluster: %d\n",fat_bpb->bpb_secperclus); - LDEBUGF("FAT sectors: 0x%lx\n",(unsigned long)fat_bpb->fatsize); - -#ifdef HAVE_MULTIVOLUME - fat_bpb->mounted = true; -#endif + /* check trailing space(s) and periods */ + c = *--name; + if (c == ' ' || c == '.') + return -3; return 0; } -int fat_unmount(int volume, bool flush) +/* Get first longname entry name offset */ +static inline unsigned int longent_char_first(void) { - int rc; -#ifdef HAVE_MULTIVOLUME - struct bpb* fat_bpb = &fat_bpbs[volume]; -#else - (void)volume; -#endif + return 1; +} - if(flush) +/* Get the next longname entry offset or 0 if the end is reached */ +static inline unsigned int longent_char_next(unsigned int i) +{ + switch (i += 2) { - rc = flush_fat(IF_MV(fat_bpb)); /* the clean way, while still alive */ + case 26: i -= 1; /* return 28 */ + case 11: i += 3; /* return 14 */ } - else - { /* volume is not accessible any more, e.g. MMC removed */ - int i; - mutex_lock(&cache_mutex); - for(i = 0;i < FAT_CACHE_SIZE;i++) - { - struct fat_cache_entry *fce = &fat_cache[i]; - if(fce->inuse -#ifdef HAVE_MULTIVOLUME - && fce->fat_vol == fat_bpb -#endif - ) - { - fce->inuse = false; /* discard all from that volume */ - fce->dirty = false; - } - } - mutex_unlock(&cache_mutex); - rc = 0; - } -#ifdef HAVE_MULTIVOLUME - fat_bpb->mounted = false; -#endif - return rc; + + return i < 32 ? i : 0; } -void fat_recalc_free(IF_MV_NONVOID(int volume)) +/* initialize the parse state; call before parsing first long entry */ +static void NO_INLINE fatlong_parse_start(struct fatlong_parse_state *lnparse) { -#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) + /* no inline so gcc can't figure out what isn't initialized here; + ord_max is king as to the validity of all other fields */ + lnparse->ord_max = -1; /* one resync per parse operation */ +} + +/* convert the FAT long name entry to a contiguous segment */ +static bool fatlong_parse_entry(struct fatlong_parse_state *lnparse, + const union raw_dirent *ent, + struct fat_direntry *fatent) +{ + int ord = ent->ldir_ord; + + if (ord & FATLONG_ORD_F_LAST) { - for (i = 0; i<fat_bpb->fatsize; i++) { - unsigned int j; - uint16_t* fat = cache_fat_sector(IF_MV(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; - } - } + /* this entry is the first long entry (first in order but + containing last part) */ + ord &= ~FATLONG_ORD_F_LAST; + + if (ord == 0 || ord > FATLONG_MAX_ORDER) + { + lnparse->ord_max = 0; + return true; } + + lnparse->ord_max = ord; + lnparse->ord = ord; + lnparse->chksum = ent->ldir_chksum; } else -#endif /* #ifdef HAVE_FAT16SUPPORT */ - { - for (i = 0; i<fat_bpb->fatsize; i++) { - unsigned int j; - uint32_t* fat = cache_fat_sector(IF_MV(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; - } - } + { + /* valid ordinals yet? */ + if (lnparse->ord_max <= 0) + { + if (lnparse->ord_max == 0) + return true; + + lnparse->ord_max = 0; + return false; /* try resync */ + } + + /* check ordinal continuity and that the checksum matches the + one stored in the last entry */ + if (ord == 0 || ord != lnparse->ord - 1 || + lnparse->chksum != ent->ldir_chksum) + { + lnparse->ord_max = 0; + return true; } } - 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) + /* so far so good; save entry information */ + lnparse->ord = ord; + + uint16_t *ucsp = fatent->ucssegs[ord - 1 + 5]; + unsigned int i = longent_char_first(); + + while ((*ucsp++ = BYTES2INT16(ent->data, i))) { - DEBUGF( "bpb_is_sane() - Error: sector size is not sane (%d)\n", - fat_bpb->bpb_bytspersec); - return -1; - } - if((long)fat_bpb->bpb_secperclus * SECTOR_SIZE > 128L*1024L) - { - /* We don't multiply by bpb_bytspersec here, because - * back in fat_mount_internal() bpb_secperclus has been - * "normalised" to 512 byte clusters, by multiplying with - * secmult. */ - DEBUGF( "bpb_is_sane() - Error: cluster size is larger than 128K " - "(%d * %d = %d)\n", - fat_bpb->bpb_bytspersec, - fat_bpb->bpb_secperclus / (fat_bpb->bpb_bytspersec / SECTOR_SIZE), - fat_bpb->bpb_bytspersec * fat_bpb->bpb_secperclus / - (fat_bpb->bpb_bytspersec / SECTOR_SIZE)); - return -2; + if (!(i = longent_char_next(i))) + return true; } - if(fat_bpb->bpb_numfats != 2) + + /* segment may end early only in last entry */ + if (ord == lnparse->ord_max) { - DEBUGF( "bpb_is_sane() - Warning: NumFATS is not 2 (%d)\n", - fat_bpb->bpb_numfats); + /* the only valid padding, if any, is 0xffff */ + do + { + if (!(i = longent_char_next(i))) + return true; + } + while (BYTES2INT16(ent->data, i) == 0xffff); } - if(fat_bpb->bpb_media != 0xf0 && fat_bpb->bpb_media < 0xf8) + + /* long filename is corrupt */ + lnparse->ord_max = 0; + return true; +} + +/* finish parsing of the longname entries and do the conversion to + UTF-8 if we have all the segments */ +static bool fatlong_parse_finish(struct fatlong_parse_state *lnparse, + const union raw_dirent *ent, + struct fat_direntry *fatent) +{ + parse_short_direntry(ent, fatent); + + /* ord_max must not have been set to <= 0 because of any earlier problems + and the ordinal immediately before the shortname entry must be 1 */ + if (lnparse->ord_max <= 0 || lnparse->ord != 1) + return false; + + /* check the longname checksums against the shortname checksum */ + if (lnparse->chksum != shortname_checksum(ent->name)) + return false; + + /* longname is good so far so convert all the segments to UTF-8 */ + unsigned char * const name = fatent->name; + unsigned char *p = name; + + /* ensure the last segment is NULL-terminated if it is filled */ + fatent->ucssegs[lnparse->ord_max + 5][0] = 0x0000; + + for (uint16_t *ucsp = fatent->ucssegs[5], ucc = *ucsp; + ucc; ucc = *++ucsp) { - DEBUGF( "bpb_is_sane() - Warning: Non-standard " - "media type (0x%02x)\n", - fat_bpb->bpb_media); + /* end should be hit before ever seeing padding */ + if (ucc == 0xffff) + return false; + + if ((p = utf8encode(ucc, p)) - name > FAT_DIRENTRY_NAME_MAX) + return false; } - if(fat_bpb->last_word != 0xaa55) + + /* longname ok */ + *p = '\0'; + return true; +} + +static unsigned long cluster2sec(struct bpb *fat_bpb, long cluster) +{ + long zerocluster = 2; + + /* negative clusters (FAT16 root dir) don't get the 2 offset */ +#ifdef HAVE_FAT16SUPPORT + if (fat_bpb->is_fat16 && cluster < 0) { - DEBUGF( "bpb_is_sane() - Error: Last word is not " - "0xaa55 (0x%04x)\n", fat_bpb->last_word); - return -3; + zerocluster = 0; } - - if (fat_bpb->fsinfo.freecount > - (fat_bpb->totalsectors - fat_bpb->firstdatasector)/ - fat_bpb->bpb_secperclus) + else +#endif /* HAVE_FAT16SUPPORT */ + if ((unsigned long)cluster > fat_bpb->dataclusters + 1) { - DEBUGF( "bpb_is_sane() - Error: FSInfo.Freecount > disk size " - "(0x%04lx)\n", (unsigned long)fat_bpb->fsinfo.freecount); - return -4; + DEBUGF( "%s() - Bad cluster number (%ld)\n", __func__, cluster); + return 0; } - return 0; + return (unsigned long)(cluster - zerocluster)*fat_bpb->bpb_secperclus + + fat_bpb->firstdatasector; } -static void flush_fat_sector(struct fat_cache_entry *fce, - unsigned char *sectorbuf) +#ifdef HAVE_FAT16SUPPORT +static long get_next_cluster16(struct bpb *fat_bpb, long startcluster) { - int rc; - long secnum; + /* if FAT16 root dir, dont use the FAT */ + if (startcluster < 0) + return startcluster + 1; - /* 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 + unsigned long entry = startcluster; + unsigned long sector = entry / CLUSTERS_PER_FAT16_SECTOR; + unsigned long offset = entry % CLUSTERS_PER_FAT16_SECTOR; + + dc_lock_cache(); - /* Write to the first FAT */ - rc = storage_write_sectors(IF_MD(fce->fat_vol->drive,) - secnum, 1, - sectorbuf); - if(rc < 0) + uint16_t *sec = cache_sector(fat_bpb, sector + fat_bpb->fatrgnstart); + if (!sec) { - panicf("flush_fat_sector() - Could not write sector %ld" - " (error %d)\n", - secnum, rc); + dc_unlock_cache(); + DEBUGF("%s: Could not cache sector %d\n", __func__, sector); + return -1; } -#ifdef HAVE_MULTIVOLUME - if(fce->fat_vol->bpb_numfats > 1) -#else - if(fat_bpbs[0].bpb_numfats > 1) -#endif + + long next = letoh16(sec[offset]); + + /* is this last cluster in chain? */ + if (next >= FAT16_EOF_MARK) + next = 0; + + dc_unlock_cache(); + return next; +} + +static long find_free_cluster16(struct bpb *fat_bpb, long startcluster) +{ + unsigned long entry = startcluster; + unsigned long sector = entry / CLUSTERS_PER_FAT16_SECTOR; + unsigned long offset = entry % CLUSTERS_PER_FAT16_SECTOR; + + for (unsigned long i = 0; i < fat_bpb->fatsize; i++) { - /* 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_MD(fce->fat_vol->drive,) - secnum, 1, sectorbuf); - if(rc < 0) + unsigned long nr = (i + sector) % fat_bpb->fatsize; + uint16_t *sec = cache_sector(fat_bpb, nr + fat_bpb->fatrgnstart); + if (!sec) + break; + + for (unsigned long j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++) { - panicf("flush_fat_sector() - Could not write sector %ld" - " (error %d)\n", - secnum, rc); + unsigned long k = (j + offset) % CLUSTERS_PER_FAT16_SECTOR; + + if (letoh16(sec[k]) == 0x0000) + { + unsigned long 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; + + DEBUGF("%s(%lx) == %x\n", __func__, startcluster, c); + + fat_bpb->fsinfo.nextfree = c; + return c; + } } + + offset = 0; } - fce->dirty = false; + + DEBUGF("%s(%lx) == 0\n", __func__, startcluster); + return 0; /* 0 is an illegal cluster number */ } -/* Note: The returned pointer is only safely valid until the next - task switch! (Any subsequent ata read/write may yield.) */ -static void *cache_fat_sector(IF_MV(struct bpb* fat_bpb,) - long fatsector, bool dirty) +static int update_fat_entry16(struct bpb *fat_bpb, unsigned long entry, + unsigned long val) { -#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; + unsigned long sector = entry / CLUSTERS_PER_FAT16_SECTOR; + unsigned long offset = entry % CLUSTERS_PER_FAT16_SECTOR; - mutex_lock(&cache_mutex); /* make changes atomic */ + val &= 0xFFFF; - /* Delete the cache entry if it isn't the sector we want */ - if(fce->inuse && (fce->secnum != secnum -#ifdef HAVE_MULTIVOLUME - || fce->fat_vol != fat_bpb -#endif - )) + DEBUGF("%s(entry:%lx,val:%lx)\n", __func__, entry, val); + + if (entry == val) + panicf("Creating FAT16 loop: %lx,%lx\n", entry, val); + + if (entry < 2) + panicf("Updating reserved FAT16 entry %lu\n", entry); + + dc_lock_cache(); + + int16_t *sec = cache_sector(fat_bpb, sector + fat_bpb->fatrgnstart); + if (!sec) { - /* Write back if it is dirty */ - if(fce->dirty) - { - flush_fat_sector(fce, sectorbuf); - } - fce->inuse = false; + dc_unlock_cache(); + DEBUGF("Could not cache sector %u\n", sector); + return -1; } - /* Load the sector if it is not cached */ - if(!fce->inuse) + uint16_t curval = letoh16(sec[offset]); + + if (val) { - rc = storage_read_sectors(IF_MD(fat_bpb->drive,) - secnum + fat_bpb->startsector,1, - sectorbuf); - if(rc < 0) - { - DEBUGF( "cache_fat_sector() - Could not read sector %ld" - " (error %d)\n", secnum, rc); - mutex_unlock(&cache_mutex); - return NULL; - } - fce->inuse = true; - fce->secnum = secnum; -#ifdef HAVE_MULTIVOLUME - fce->fat_vol = fat_bpb; -#endif + /* being allocated */ + if (curval == 0x0000 && fat_bpb->fsinfo.freecount > 0) + fat_bpb->fsinfo.freecount--; } - if (dirty) - fce->dirty = true; /* dirt remains, sticky until flushed */ - mutex_unlock(&cache_mutex); - return sectorbuf; + else + { + /* being freed */ + if (curval != 0x0000) + fat_bpb->fsinfo.freecount++; + } + + DEBUGF("%lu free clusters\n", (unsigned long)fat_bpb->fsinfo.freecount); + + sec[offset] = htole16(val); + dc_dirty_buf(sec); + + dc_unlock_cache(); + return 0; } -static unsigned long find_free_cluster(IF_MV(struct bpb* fat_bpb,) - unsigned long startcluster) +static void fat_recalc_free_internal16(struct bpb *fat_bpb) { -#ifndef HAVE_MULTIVOLUME - struct bpb* fat_bpb = &fat_bpbs[0]; -#endif - unsigned long sector; - unsigned long offset; - unsigned long i; + unsigned long free = 0; -#ifdef HAVE_FAT16SUPPORT - if (fat_bpb->is_fat16) + for (unsigned long i = 0; i < fat_bpb->fatsize; i++) { - sector = startcluster / CLUSTERS_PER_FAT16_SECTOR; - offset = startcluster % CLUSTERS_PER_FAT16_SECTOR; + uint16_t *sec = cache_sector(fat_bpb, i + fat_bpb->fatrgnstart); + if (!sec) + break; - for (i = 0; i<fat_bpb->fatsize; i++) { - unsigned int j; - unsigned int nr = (i + sector) % fat_bpb->fatsize; - uint16_t* fat = cache_fat_sector(IF_MV(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; - LDEBUGF("find_free_cluster(%lx) == %x\n",startcluster,c); - fat_bpb->fsinfo.nextfree = c; - return c; - } - } - offset = 0; + for (unsigned long j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++) + { + unsigned long c = i * CLUSTERS_PER_FAT16_SECTOR + j; + + if (c < 2 || c > fat_bpb->dataclusters + 1) /* nr 0 is unused */ + continue; + + if (letoh16(sec[j]) != 0x0000) + continue; + + free++; + if (fat_bpb->fsinfo.nextfree == 0xffffffff) + fat_bpb->fsinfo.nextfree = c; } } - 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; - uint32_t* fat = cache_fat_sector(IF_MV(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; - LDEBUGF("find_free_cluster(%lx) == %lx\n",startcluster,c); - fat_bpb->fsinfo.nextfree = c; - return c; - } - } - offset = 0; - } + fat_bpb->fsinfo.freecount = free; +} +#endif /* HAVE_FAT16SUPPORT */ + +static void update_fsinfo32(struct bpb *fat_bpb) +{ + uint8_t *fsinfo = cache_sector(fat_bpb, fat_bpb->bpb_fsinfo); + if (!fsinfo) + { + DEBUGF("%s() - Couldn't read FSInfo" + " (err code %d)", __func__, rc); + return; } - LDEBUGF("find_free_cluster(%lx) == 0\n",startcluster); - return 0; /* 0 is an illegal cluster number */ + INT322BYTES(fsinfo, FSINFO_FREECOUNT, fat_bpb->fsinfo.freecount); + INT322BYTES(fsinfo, FSINFO_NEXTFREE, fat_bpb->fsinfo.nextfree); + dc_dirty_buf(fsinfo); } -static int update_fat_entry(IF_MV(struct bpb* fat_bpb,) unsigned long entry, - unsigned long val) +static long get_next_cluster32(struct bpb *fat_bpb, long startcluster) { -#ifndef HAVE_MULTIVOLUME - struct bpb* fat_bpb = &fat_bpbs[0]; -#endif -#ifdef HAVE_FAT16SUPPORT - if (fat_bpb->is_fat16) + unsigned long entry = startcluster; + unsigned long sector = entry / CLUSTERS_PER_FAT_SECTOR; + unsigned long offset = entry % CLUSTERS_PER_FAT_SECTOR; + + dc_lock_cache(); + + uint32_t *sec = cache_sector(fat_bpb, sector + fat_bpb->fatrgnstart); + if (!sec) { - int sector = entry / CLUSTERS_PER_FAT16_SECTOR; - int offset = entry % CLUSTERS_PER_FAT16_SECTOR; - unsigned short* sec; + dc_unlock_cache(); + DEBUGF("%s: Could not cache sector %d\n", __func__, sector); + return -1; + } - val &= 0xFFFF; + long next = letoh32(sec[offset]) & 0x0fffffff; - LDEBUGF("update_fat_entry(%lx,%lx)\n",entry,val); + /* is this last cluster in chain? */ + if (next >= FAT_EOF_MARK) + next = 0; - if (entry==val) - panicf("Creating FAT loop: %lx,%lx\n",entry,val); + dc_unlock_cache(); + return next; +} - if ( entry < 2 ) - panicf("Updating reserved FAT entry %ld.\n",entry); +static long find_free_cluster32(struct bpb *fat_bpb, long startcluster) +{ + unsigned long entry = startcluster; + unsigned long sector = entry / CLUSTERS_PER_FAT_SECTOR; + unsigned long offset = entry % CLUSTERS_PER_FAT_SECTOR; - sec = cache_fat_sector(IF_MV(fat_bpb,) sector, true); + for (unsigned long i = 0; i < fat_bpb->fatsize; i++) + { + unsigned long nr = (i + sector) % fat_bpb->fatsize; + uint32_t *sec = cache_sector(fat_bpb, nr + fat_bpb->fatrgnstart); if (!sec) + break; + + for (unsigned long j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++) { - DEBUGF( "update_fat_entry() - Could not cache sector %d\n", sector); - return -1; - } + unsigned long k = (j + offset) % CLUSTERS_PER_FAT_SECTOR; - 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++; + if (!(letoh32(sec[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; + + DEBUGF("%s(%lx) == %lx\n", __func__, startcluster, c); + + fat_bpb->fsinfo.nextfree = c; + return c; + } } - LDEBUGF("update_fat_entry: %lu free clusters\n", - (unsigned long)fat_bpb->fsinfo.freecount); + offset = 0; + } + + DEBUGF("%s(%lx) == 0\n", __func__, startcluster); + return 0; /* 0 is an illegal cluster number */ +} + +static int update_fat_entry32(struct bpb *fat_bpb, unsigned long entry, + unsigned long val) +{ + unsigned long sector = entry / CLUSTERS_PER_FAT_SECTOR; + unsigned long offset = entry % CLUSTERS_PER_FAT_SECTOR; + + DEBUGF("%s(entry:%lx,val:%lx)\n", __func__, entry, val); + + if (entry == val) + panicf("Creating FAT32 loop: %lx,%lx\n", entry, val); + + if (entry < 2) + panicf("Updating reserved FAT32 entry %lu\n", entry); + + dc_lock_cache(); + + uint32_t *sec = cache_sector(fat_bpb, sector + fat_bpb->fatrgnstart); + if (!sec) + { + dc_unlock_cache(); + DEBUGF("Could not cache sector %u\n", sector); + return -1; + } - sec[offset] = htole16(val); + uint32_t curval = letoh32(sec[offset]); + + if (val) + { + /* being allocated */ + if (!(curval & 0x0fffffff) && fat_bpb->fsinfo.freecount > 0) + fat_bpb->fsinfo.freecount--; } else -#endif /* #ifdef HAVE_FAT16SUPPORT */ { - long sector = entry / CLUSTERS_PER_FAT_SECTOR; - int offset = entry % CLUSTERS_PER_FAT_SECTOR; - uint32_t* sec; + /* being freed */ + if (curval & 0x0fffffff) + fat_bpb->fsinfo.freecount++; + } + + DEBUGF("%lu free clusters\n", (unsigned long)fat_bpb->fsinfo.freecount); - LDEBUGF("update_fat_entry(%lx,%lx)\n",entry,val); + /* don't change top 4 bits */ + sec[offset] = htole32((curval & 0xf0000000) | (val & 0x0fffffff)); + dc_dirty_buf(sec); - if (entry==val) - panicf("Creating FAT loop: %lx,%lx\n",entry,val); + dc_unlock_cache(); + return 0; +} - if ( entry < 2 ) - panicf("Updating reserved FAT entry %ld.\n",entry); +static void fat_recalc_free_internal32(struct bpb *fat_bpb) +{ + unsigned long free = 0; - sec = cache_fat_sector(IF_MV(fat_bpb,) sector, true); + for (unsigned long i = 0; i < fat_bpb->fatsize; i++) + { + uint32_t *sec = cache_sector(fat_bpb, i + fat_bpb->fatrgnstart); if (!sec) + break; + + for (unsigned long j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++) { - DEBUGF("update_fat_entry() - Could not cache sector %ld\n", sector); - return -1; - } + unsigned long c = i * CLUSTERS_PER_FAT_SECTOR + j; - 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++; - } + if (c < 2 || c > fat_bpb->dataclusters + 1) /* nr 0 is unused */ + continue; - LDEBUGF("update_fat_entry: %ld free clusters\n", - (unsigned long)fat_bpb->fsinfo.freecount); + if (letoh32(sec[j]) & 0x0fffffff) + continue; - /* don't change top 4 bits */ - sec[offset] &= htole32(0xf0000000); - sec[offset] |= htole32(val & 0x0fffffff); + free++; + if (fat_bpb->fsinfo.nextfree == 0xffffffff) + fat_bpb->fsinfo.nextfree = c; + } } - return 0; + fat_bpb->fsinfo.freecount = free; + update_fsinfo32(fat_bpb); } -static long read_fat_entry(IF_MV(struct bpb* fat_bpb,) unsigned long entry) +static int fat_mount_internal(struct bpb *fat_bpb) { + int rc; + /* safe to grab buffer: bpb is irrelevant and no sector will be cached + for this volume since it isn't mounted */ + uint8_t * const buf = dc_get_buffer(); + if (!buf) + FAT_ERROR(-1); + + /* Read the sector */ + rc = storage_read_sectors(IF_MD(fat_bpb->drive,) fat_bpb->startsector, + 1, buf); + if(rc) + { + DEBUGF("%s() - Couldn't read BPB" + " (err %d)\n", __func__, rc); + FAT_ERROR(rc * 10 - 2); + } + + fat_bpb->bpb_bytspersec = BYTES2INT16(buf, BPB_BYTSPERSEC); + unsigned long 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; + + fat_bpb->fatrgnstart = fat_bpb->bpb_rsvdseccnt; + fat_bpb->fatrgnend = fat_bpb->bpb_rsvdseccnt + fat_bpb->fatsize; + + if (fat_bpb->bpb_totsec16 != 0) + fat_bpb->totalsectors = fat_bpb->bpb_totsec16; + else + fat_bpb->totalsectors = fat_bpb->bpb_totsec32; + + unsigned int rootdirsectors = 0; +#ifdef HAVE_FAT16SUPPORT + fat_bpb->bpb_rootentcnt = BYTES2INT16(buf, BPB_ROOTENTCNT); + + if (!fat_bpb->bpb_bytspersec) + FAT_ERROR(-3); + + rootdirsectors = secmult * ((fat_bpb->bpb_rootentcnt * DIR_ENTRY_SIZE + + fat_bpb->bpb_bytspersec - 1) / fat_bpb->bpb_bytspersec); +#endif /* HAVE_FAT16SUPPORT */ + + fat_bpb->firstdatasector = fat_bpb->bpb_rsvdseccnt + + fat_bpb->bpb_numfats * fat_bpb->fatsize + + rootdirsectors; + + /* Determine FAT type */ + unsigned long datasec = fat_bpb->totalsectors - fat_bpb->firstdatasector; + + if (!fat_bpb->bpb_secperclus) + FAT_ERROR(-4); + + fat_bpb->dataclusters = datasec / fat_bpb->bpb_secperclus; + +#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 /* !TEST_FAT */ + if (fat_bpb->dataclusters < 65525) +#endif /* TEST_FAT */ + { /* FAT16 */ +#ifdef HAVE_FAT16SUPPORT + fat_bpb->is_fat16 = true; + if (fat_bpb->dataclusters < 4085) + { /* FAT12 */ + DEBUGF("This is FAT12. Go away!\n"); + FAT_ERROR(-5); + } +#else /* !HAVE_FAT16SUPPORT */ + DEBUGF("This is not FAT32. Go away!\n"); + FAT_ERROR(-6); +#endif /* HAVE_FAT16SUPPORT */ + } + #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; + /* FAT16 specific part of BPB */ + fat_bpb->rootdirsector = fat_bpb->bpb_rsvdseccnt + + fat_bpb->bpb_numfats * fat_bpb->bpb_fatsz16; + long 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->rootdirsectornum = dirclusters * fat_bpb->bpb_secperclus + - rootdirsectors; + } + else +#endif /* 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(fat_bpb, fat_bpb->bpb_rootclus); + } - sec = cache_fat_sector(IF_MV(fat_bpb,) sector, false); - if (!sec) - { - DEBUGF( "read_fat_entry() - Could not cache sector %d\n", sector); - return -1; - } + rc = bpb_is_sane(fat_bpb); + if (rc < 0) + { + DEBUGF("%s: BPB is insane!\n", __func__); + FAT_ERROR(rc * 10 - 7); + } - return letoh16(sec[offset]); +#ifdef HAVE_FAT16SUPPORT + if (fat_bpb->is_fat16) + { + fat_bpb->fsinfo.freecount = 0xffffffff; /* force recalc later */ + fat_bpb->fsinfo.nextfree = 0xffffffff; } else -#endif /* #ifdef HAVE_FAT16SUPPORT */ +#endif /* HAVE_FAT16SUPPORT */ { - long sector = entry / CLUSTERS_PER_FAT_SECTOR; - int offset = entry % CLUSTERS_PER_FAT_SECTOR; - uint32_t* sec; + /* Read the fsinfo sector */ + rc = storage_read_sectors(IF_MD(fat_bpb->drive,) + fat_bpb->startsector + fat_bpb->bpb_fsinfo, 1, buf); - sec = cache_fat_sector(IF_MV(fat_bpb,) sector, false); - if (!sec) + if (rc < 0) { - DEBUGF( "read_fat_entry() - Could not cache sector %ld\n", sector); - return -1; + DEBUGF("%s() - Couldn't read FSInfo" + " (error code %d)\n", __func__, rc); + FAT_ERROR(rc * 10 - 8); } - return letoh32(sec[offset]) & 0x0fffffff; + fat_bpb->fsinfo.freecount = BYTES2INT32(buf, FSINFO_FREECOUNT); + fat_bpb->fsinfo.nextfree = BYTES2INT32(buf, FSINFO_NEXTFREE); } -} - -static long get_next_cluster(IF_MV(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 + /* Fix up calls that change per FAT type */ 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 */ + BPB_FN_SET16(fat_bpb, get_next_cluster); + BPB_FN_SET16(fat_bpb, find_free_cluster); + BPB_FN_SET16(fat_bpb, update_fat_entry); + BPB_FN_SET16(fat_bpb, fat_recalc_free_internal); } -#endif - next_cluster = read_fat_entry(IF_MV(fat_bpb,) cluster); - - /* is this last cluster in chain? */ - if ( next_cluster >= eof_mark ) - return 0; else - return next_cluster; + { + BPB_FN_SET32(fat_bpb, get_next_cluster); + BPB_FN_SET32(fat_bpb, find_free_cluster); + BPB_FN_SET32(fat_bpb, update_fat_entry); + BPB_FN_SET32(fat_bpb, fat_recalc_free_internal); + } +#endif /* HAVE_FAT16SUPPORT */ + + rc = 0; +fat_error: + dc_release_buffer(buf); + return rc; } -static int update_fsinfo(IF_MV_NONVOID(struct bpb* fat_bpb)) +static union raw_dirent * cache_direntry(struct bpb *fat_bpb, + struct fat_filestr *filestr, + unsigned int entry) { -#ifndef HAVE_MULTIVOLUME - struct bpb* fat_bpb = &fat_bpbs[0]; -#endif - uint32_t* intptr; - int rc; + filestr->eof = false; -#ifdef HAVE_FAT16SUPPORT - if (fat_bpb->is_fat16) - return 0; /* FAT16 has no FsInfo */ -#endif /* #ifdef HAVE_FAT16SUPPORT */ + if (entry >= MAX_DIRENTRIES) + { + DEBUGF("%s() - Dir is too large (entry %u)\n", __func__, entry); + return NULL; + } - unsigned char* fsinfo = fat_get_sector_buffer(); - /* update fsinfo */ - rc = storage_read_sectors(IF_MD(fat_bpb->drive,) - fat_bpb->startsector + fat_bpb->bpb_fsinfo, 1,fsinfo); - if (rc < 0) + unsigned long sector = entry / DIR_ENTRIES_PER_SECTOR; + + if (fat_query_sectornum(filestr) != sector + 1) { - fat_release_sector_buffer(); - DEBUGF( "update_fsinfo() - Couldn't read FSInfo (error code %d)", rc); - return rc * 10 - 1; + int rc = fat_seek(filestr, sector + 1); + if (rc < 0) + { + if (rc == FAT_SEEK_EOF) + { + DEBUGF("%s() - End of dir (entry %u)\n", __func__, entry); + fat_seek(filestr, sector); + filestr->eof = true; + } + + return NULL; + } } - intptr = (uint32_t*)&(fsinfo[FSINFO_FREECOUNT]); - *intptr = htole32(fat_bpb->fsinfo.freecount); - intptr = (uint32_t*)&(fsinfo[FSINFO_NEXTFREE]); - *intptr = htole32(fat_bpb->fsinfo.nextfree); + union raw_dirent *ent = cache_sector(fat_bpb, filestr->lastsector); - rc = storage_write_sectors(IF_MD(fat_bpb->drive,) - fat_bpb->startsector + fat_bpb->bpb_fsinfo,1,fsinfo); - fat_release_sector_buffer(); - if (rc < 0) + if (ent) + ent += entry % DIR_ENTRIES_PER_SECTOR; + + return ent; +} + +static long next_write_cluster(struct bpb *fat_bpb, long oldcluster) +{ + DEBUGF("%s(old:%lx)\n", __func__, oldcluster); + + long cluster = 0; + + /* cluster already allocated? */ + if (oldcluster) + cluster = get_next_cluster(fat_bpb, oldcluster); + + if (!cluster) { - DEBUGF( "update_fsinfo() - Couldn't write FSInfo (error code %d)", rc); - return rc * 10 - 2; + /* passed end of existing entries and now need to append */ + #ifdef HAVE_FAT16SUPPORT + if (UNLIKELY(oldcluster < 0)) + return 0; /* negative, pseudo-cluster of the root dir */ + /* impossible to append something to the root */ + #endif /* HAVE_FAT16SUPPORT */ + + dc_lock_cache(); + + long findstart = oldcluster > 0 ? + oldcluster + 1 : (long)fat_bpb->fsinfo.nextfree; + + cluster = find_free_cluster(fat_bpb, findstart); + + if (cluster) + { + /* create the cluster chain */ + if (oldcluster) + update_fat_entry(fat_bpb, oldcluster, cluster); + + update_fat_entry(fat_bpb, cluster, FAT_EOF_MARK); + } + else + { + #ifdef TEST_FAT + if (fat_bpb->fsinfo.freecount > 0) + panicf("There is free space, but find_free_cluster() " + "didn't find it!\n"); + #endif + DEBUGF("Disk full!\n"); + } + + dc_unlock_cache(); } - return 0; + return cluster; } -static int flush_fat(IF_MV_NONVOID(struct bpb* fat_bpb)) +/* extend dir file by one cluster and clear it; file position should be at the + current last cluster before calling and size of dir checked */ +static int fat_extend_dir(struct bpb *fat_bpb, struct fat_filestr *dirstr) { - int i; + DEBUGF("%s()\n", __func__); + int rc; - unsigned char *sec; - LDEBUGF("flush_fat()\n"); - mutex_lock(&cache_mutex); - for(i = 0;i < FAT_CACHE_SIZE;i++) + long cluster = dirstr->lastcluster; + long newcluster = next_write_cluster(fat_bpb, cluster); + + if (!newcluster) { - struct fat_cache_entry *fce = &fat_cache[i]; - if(fce->inuse -#ifdef HAVE_MULTIVOLUME - && fce->fat_vol == fat_bpb -#endif - && fce->dirty) + /* no more room or something went wrong */ + DEBUGF("Out of space\n"); + FAT_ERROR(FAT_RC_ENOSPC); + } + + /* we must clear whole clusters */ + unsigned long startsector = cluster2sec(fat_bpb, newcluster); + unsigned long sector = startsector - 1; + + if (startsector == 0) + FAT_ERROR(-1); + + for (unsigned int i = 0; i < fat_bpb->bpb_secperclus; i++) + { + dc_lock_cache(); + + void *sec = cache_sector_buffer(IF_MV(fat_bpb,) ++sector); + if (!sec) { - sec = fat_cache_sectors[i]; - flush_fat_sector(fce, sec); + dc_unlock_cache(); + DEBUGF("Cannot clear cluster %ld\n", newcluster); + update_fat_entry(fat_bpb, newcluster, 0); + FAT_ERROR(-2); } + + memset(sec, 0, SECTOR_SIZE); + dc_dirty_buf(sec); + dc_unlock_cache(); } - mutex_unlock(&cache_mutex); - rc = update_fsinfo(IF_MV(fat_bpb)); - if (rc < 0) - return rc * 10 - 3; + if (!dirstr->fatfilep->firstcluster) + dirstr->fatfilep->firstcluster = newcluster; - return 0; + dirstr->lastcluster = newcluster; + dirstr->lastsector = sector; + dirstr->clusternum++; + dirstr->sectornum = sector - startsector; + dirstr->eof = false; + + rc = 0; +fat_error: + return rc; } -static void fat_time(unsigned short* date, - unsigned short* time, - unsigned short* tenth ) +static void fat_open_internal(IF_MV(int volume,) long startcluster, + struct fat_file *file) { +#ifdef HAVE_MULTIVOLUME + file->volume = volume; +#endif + file->firstcluster = startcluster; + file->dircluster = 0; + file->e.entry = 0; + file->e.entries = 0; +} + #if CONFIG_RTC - struct tm* tm = get_time(); +static void fat_time(uint16_t *date, uint16_t *time, int16_t *tenth) +{ + struct tm *tm = get_time(); if (date) + { *date = ((tm->tm_year - 80) << 9) | - ((tm->tm_mon + 1) << 5) | - tm->tm_mday; + ((tm->tm_mon + 1) << 5) | + tm->tm_mday; + } if (time) + { *time = (tm->tm_hour << 11) | - (tm->tm_min << 5) | - (tm->tm_sec >> 1); + (tm->tm_min << 5) | + (tm->tm_sec >> 1); + } if (tenth) *tenth = (tm->tm_sec & 1) * 100; -#else +} + +#else /* !CONFIG_RTC */ + +static void fat_time(uint16_t *date, uint16_t *time, int16_t *tenth) +{ /* non-RTC version returns an increment from the supplied time, or a * fixed standard time/date if no time given as input */ -/* Macros to convert a 2-digit string to a decimal constant. - (YEAR), MONTH and DAY are set by the date command, which outputs - DAY as 00..31 and MONTH as 01..12. The leading zero would lead to - misinterpretation as an octal constant. */ -#define S100(x) 1 ## x -#define C2DIG2DEC(x) (S100(x)-100) -/* The actual build date, as FAT date constant */ -#define BUILD_DATE_FAT (((YEAR - 1980) << 9) \ - | (C2DIG2DEC(MONTH) << 5) \ - | C2DIG2DEC(DAY)) + /* Macros to convert a 2-digit string to a decimal constant. + (YEAR), MONTH and DAY are set by the date command, which outputs + DAY as 00..31 and MONTH as 01..12. The leading zero would lead to + misinterpretation as an octal constant. */ + #define S100(x) 1 ## x + #define C2DIG2DEC(x) (S100(x)-100) + /* The actual build date, as FAT date constant */ + #define BUILD_DATE_FAT (((YEAR - 1980) << 9) \ + | (C2DIG2DEC(MONTH) << 5) \ + | C2DIG2DEC(DAY)) bool date_forced = false; bool next_day = false; @@ -1107,7 +1482,7 @@ static void fat_time(unsigned short* date, *date = BUILD_DATE_FAT; date_forced = true; } - + if (time) { time2 = *time << 1; @@ -1119,8 +1494,8 @@ static void fat_time(unsigned short* date, { unsigned mins = (time2 >> 6) & 0x3f; unsigned hours = (time2 >> 12) & 0x1f; - - mins = 11 * ((mins/11) + 1); /* advance to next multiple of 11 */ + + mins = 11 * ((mins / 11) + 1); /* advance to next multiple of 11 */ if (mins > 59) { mins = 11; /* 00 would be a bad marker */ @@ -1134,14 +1509,14 @@ static void fat_time(unsigned short* date, } *time = time2 >> 1; } - + if (tenth) *tenth = (time2 & 1) * 100; if (date && next_day) { static const unsigned char daysinmonth[] = - {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; unsigned day = *date & 0x1f; unsigned month = (*date >> 5) & 0x0f; unsigned year = (*date >> 9) & 0x7f; @@ -1156,1514 +1531,1393 @@ static void fat_time(unsigned short* date, year++; } } + *date = (year << 9) | (month << 5) | day; } - -#endif /* CONFIG_RTC */ } +#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) +static int write_longname(struct bpb *fat_bpb, struct fat_filestr *parentstr, + struct fat_file *file, const unsigned char *name, + unsigned long ucslen, const unsigned char *shortname, + union raw_dirent *srcent, uint8_t attr, + unsigned int flags) { - 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 = utf8length(name); + DEBUGF("%s(file:%lx, first:%d, num:%d, name:%s)\n", __func__, + parent->info->firstcluster, firstentry, numentries, name); + int rc; - unsigned short name_utf16[namelen + 1]; + union raw_dirent *ent; + + uint16_t date = 0, time = 0, tenth = 0; + fat_time(&date, &time, &tenth); + time = htole16(time); + date = htole16(date); - LDEBUGF("write_long_name(file:%lx, first:%d, num:%d, name:%s)\n", - file->firstcluster, firstentry, numentries, name); + /* shortname checksum saved in each longname entry */ + uint8_t chksum = shortname_checksum(shortname); - rc = fat_seek(file, sector); - if (rc<0) - return rc * 10 - 1; + /* we need to convert the name first since the entries are written in + reverse order */ + unsigned long ucspadlen = ALIGN_UP(ucslen, FATLONG_NAME_CHARS); + uint16_t ucsname[ucspadlen]; - unsigned char* buf = fat_get_sector_buffer(); - rc = fat_readwrite(file, 1, buf, false); - if (rc<1) + for (unsigned long i = 0; i < ucspadlen; i++) { - fat_release_sector_buffer(); - return rc * 10 - 2; + if (i < ucslen) + name = utf8decode(name, &ucsname[i]); + else if (i == ucslen) + ucsname[i] = 0x0000; /* name doesn't fill last block */ + else /* i > ucslen */ + ucsname[i] = 0xffff; /* pad-out to end */ } - /* 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 = utf8decode(name, &name_utf16[i]); - - 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(); - LDEBUGF("Failed writing new sector: %d\n",rc); - return rc * 10 - 5; - } - if (rc==0) - /* end of dir */ - memset(buf, 0, SECTOR_SIZE); + dc_lock_cache(); - sector++; - idx = 0; - } + const unsigned int longentries = file->e.entries - 1; + const unsigned int firstentry = file->e.entry - longentries; - entry = buf + idx * DIR_ENTRY_SIZE; + /* longame entries */ + for (unsigned int i = 0; i < longentries; i++) + { + ent = cache_direntry(fat_bpb, parentstr, firstentry + i); + if (!ent) + FAT_ERROR(-2); /* verify this entry is free */ - if (entry[0] && entry[0] != 0xe5 ) + if (ent->name[0] && ent->name[0] != 0xe5) { - fat_release_sector_buffer(); panicf("Dir entry %d in sector %x is not free! " "%02x %02x %02x %02x", - idx, sector, - entry[0], entry[1], entry[2], entry[3]); + i + firstentry, (unsigned)parentstr->lastsector, + (unsigned)ent->data[0], (unsigned)ent->data[1], + (unsigned)ent->data[2], (unsigned)ent->data[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); - } + memset(ent->data, 0, DIR_ENTRY_SIZE); - entry[FATDIR_ATTR] = FAT_ATTR_LONG_NAME; - entry[FATDIR_FSTCLUSLO] = 0; - entry[FATLONG_TYPE] = 0; - entry[FATLONG_CHKSUM] = chksum; - LDEBUGF("Longname entry %d: %s\n", idx, name+nameidx); - } - else { - /* shortname entry */ - unsigned short date=0, time=0, tenth=0; - LDEBUGF("Shortname entry: %s\n", 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); + unsigned int ord = longentries - i; + + ent->ldir_ord = ord | (i == 0 ? FATLONG_ORD_F_LAST : 0); + ent->ldir_attr = ATTR_LONG_NAME; + ent->ldir_chksum = chksum; + + /* set name */ + uint16_t *ucsptr = &ucsname[(ord - 1) * FATLONG_NAME_CHARS]; + for (unsigned j = longent_char_first(); j; j = longent_char_next(j)) + { + uint16_t ucs = *ucsptr++; + INT162BYTES(ent->data, j, ucs); } - idx++; - nameidx -= NAME_BYTES_PER_ENTRY; + + dc_dirty_buf(ent); + DEBUGF("Longname entry %d\n", ord); } - /* update last sector */ - rc = fat_seek(file, sector); - if (rc<0) + /* shortname entry */ + DEBUGF("Shortname '%s'\n", shortname); + + ent = cache_direntry(fat_bpb, parentstr, file->e.entry); + if (!ent) + FAT_ERROR(-2); + + if (srcent && (flags & DIRENT_TEMPL)) { - fat_release_sector_buffer(); - return rc * 10 - 6; + /* srcent points to short entry template */ + *ent = *srcent; + } + else + { + /* make our own short entry */ + memset(ent->data, 0, DIR_ENTRY_SIZE); + ent->attr = attr; } - rc = fat_readwrite(file, 1, buf, true); - fat_release_sector_buffer(); - if (rc<1) - return rc * 10 - 7; + /* short name may change even if just moving */ + memcpy(ent->name, shortname, 11); + raw_dirent_set_fstclus(ent, file->firstcluster); - 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] == '.') + if (!(flags & DIRENT_TEMPL_CRT)) { - return -1; + ent->crttimetenth = tenth; + ent->crttime = time; + ent->crtdate = date; } - while (*newname) + + if (!(flags & DIRENT_TEMPL_WRT)) { - if (*newname < ' ' || strchr(invalid_chars, *newname) != NULL) - return -1; - newname++; + ent->wrttime = time; + ent->wrtdate = date; } - /* check trailing space(s) */ - if(*(--newname) == ' ') - return -1; - return 0; + if (!(flags & DIRENT_TEMPL_ACC)) + ent->lstaccdate = date; + + if (srcent && (flags & DIRENT_RETURN)) + *srcent = *ent; /* caller wants */ + + dc_dirty_buf(ent); + + rc = 0; +fat_error: + dc_unlock_cache(); + return rc; } -static int add_dir_entry(struct fat_dir* dir, - struct fat_file* file, - const char* name, - bool is_directory, - bool dotdir) +static int add_dir_entry(struct bpb *fat_bpb, struct fat_filestr *parentstr, + struct fat_file *file, const char *name, + union raw_dirent *srcent, uint8_t attr, + unsigned int flags) { -#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; - - LDEBUGF( "add_dir_entry(%s,%lx)\n", - 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; - } - } + DEBUGF("%s(name:\"%s\",first:%lx)\n", __func__, name, + file->firstcluster); -#ifdef HAVE_MULTIVOLUME - file->volume = dir->file.volume; /* inherit the volume, to make sure */ -#endif + int rc; - /* 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] = ' '; + unsigned char basisname[11], shortname[11]; + int n; + int entries_needed; + unsigned long ucslen = 0; + if (is_dotdir_name(name) && (attr & ATTR_DIRECTORY)) + { + /* The "." and ".." directory entries must not be long names */ + int dots = strlcpy(shortname, name, 11); + memset(&shortname[dots], ' ', 11 - dots); entries_needed = 1; - } else { - create_dos_name(name, shortname); + } + else + { + rc = check_longname(name); + if (rc < 0) + FAT_ERROR(rc * 10 - 1); /* filename is invalid */ + + create_dos_name(basisname, name, &n); + randomize_dos_name(shortname, basisname, &n); - /* one dir entry needed for every 13 bytes of filename, + /* one dir entry needed for every 13 characters of filename, plus one entry for the short name */ - entries_needed = (utf8length(name) + (NAME_BYTES_PER_ENTRY-1)) - / NAME_BYTES_PER_ENTRY + 1; + ucslen = utf8length(name); + if (ucslen > 255) + FAT_ERROR(-2); /* name is too long */ + + entries_needed = (ucslen + FATLONG_NAME_CHARS - 1) + / FATLONG_NAME_CHARS + 1; } - unsigned char* buf = fat_get_sector_buffer(); - restart: - firstentry = -1; + int entry = 0, entries_found = 0, firstentry = -1; + const int entperclus = DIR_ENTRIES_PER_SECTOR*fat_bpb->bpb_secperclus; - rc = fat_seek(&dir->file, 0); - if (rc < 0) - { - fat_release_sector_buffer(); - return rc * 10 - 2; - } + /* step 1: search for a sufficiently-long run of free entries and check + for duplicate shortname */ + dc_lock_cache(); - /* step 1: search for free entries and check for duplicate shortname */ - for (sector = 0; !done; sector++) + for (bool done = false; !done;) { - unsigned int i; + union raw_dirent *ent = cache_direntry(fat_bpb, parentstr, entry); - 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)\n", rc); - return rc * 10 - 3; - } + if (!ent) + { + if (parentstr->eof) + { + DEBUGF("End of dir (entry %d)\n", entry); + break; + } - if (rc == 0) { /* current end of dir reached */ - LDEBUGF("End of dir on cluster boundary\n"); - break; + DEBUGF("Couldn't read dir (entry %d)\n", entry); + dc_unlock_cache(); + FAT_ERROR(-3); } - /* look for free slots */ - for (i = 0; i < DIR_ENTRIES_PER_SECTOR; i++) + switch (ent->name[0]) { - switch (buf[i * DIR_ENTRY_SIZE]) { - case 0: - entries_found += DIR_ENTRIES_PER_SECTOR - i; - LDEBUGF("Found end of dir %d\n", - sector * DIR_ENTRIES_PER_SECTOR + i); - i = DIR_ENTRIES_PER_SECTOR - 1; - done = true; - break; + case 0: /* all remaining entries in cluster are free */ + DEBUGF("Found end of dir %d\n", entry); + int found = entperclus - (entry % entperclus); + entries_found += found; + entry += found; /* move entry passed end of cluster */ + done = true; + break; - case 0xe5: - entries_found++; - LDEBUGF("Found free entry %d (%d/%d)\n", - sector * DIR_ENTRIES_PER_SECTOR + i, - entries_found, entries_needed); - break; + case 0xe5: /* individual free entry */ + entries_found++; + entry++; + DEBUGF("Found free entry %d (%d/%d)\n", + entry, entries_found, entries_needed); + break; - default: - entries_found = 0; + default: /* occupied */ + entries_found = 0; + entry++; - /* 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); - LDEBUGF("Duplicate shortname, changing to %s\n", - shortname); + if ((ent->ldir_attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME) + break; /* ignore long name entry */ - /* name has changed, we need to restart search */ - goto restart; - } - break; + /* check that our intended shortname doesn't already exist */ + if (!strncmp(shortname, ent->name, 11)) + { + /* shortname exists already, make a new one */ + DEBUGF("Duplicate shortname '%.11s'", shortname); + randomize_dos_name(shortname, basisname, &n); + DEBUGF(", changing to '%.11s'\n", shortname); + + /* name has changed, we need to restart search */ + entry = 0; + firstentry = -1; } - if (firstentry < 0 && (entries_found >= entries_needed)) - firstentry = sector * DIR_ENTRIES_PER_SECTOR + i + 1 - - entries_found; + break; + } + + if (firstentry < 0 && entries_found >= entries_needed) + { + /* found adequate space; point to initial free entry */ + firstentry = entry - entries_found; } } + dc_unlock_cache(); + /* step 2: extend the dir if necessary */ if (firstentry < 0) { - LDEBUGF("Adding new sector(s) to dir\n"); - rc = fat_seek(&dir->file, sector); - if (rc < 0) + DEBUGF("Adding new cluster(s) to dir\n"); + + if (entry + entries_needed - entries_found > MAX_DIRENTRIES) { - fat_release_sector_buffer(); - return rc * 10 - 4; + /* FAT specification allows no more than 65536 entries (2MB) + per directory */ + DEBUGF("Directory would be too large.\n"); + FAT_ERROR(-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++) + while (entries_found < entries_needed) { - 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; + rc = fat_extend_dir(fat_bpb, parentstr); + if (rc == FAT_RC_ENOSPC) + FAT_ERROR(RC); + else if (rc < 0) + FAT_ERROR(rc * 10 - 5); + + entries_found += entperclus; + entry += entperclus; } - firstentry = sector * DIR_ENTRIES_PER_SECTOR - entries_found; + firstentry = entry - entries_found; } - fat_release_sector_buffer(); - /* step 3: add entry */ - sector = firstentry / DIR_ENTRIES_PER_SECTOR; - LDEBUGF("Adding longname to entry %d in sector %d\n", - firstentry, sector); + /* remember the parent directory entry information */ +#ifdef HAVE_MULTIVOLUME + file->volume = parentstr->fatfilep->volume; +#endif + file->dircluster = parentstr->fatfilep->firstcluster; + file->e.entry = firstentry + entries_needed - 1; + file->e.entries = entries_needed; - rc = write_long_name(&dir->file, firstentry, - entries_needed, name, - shortname, is_directory); + /* step 3: add entry */ + DEBUGF("Adding longname to entry %d\n", firstentry); + rc = write_longname(fat_bpb, parentstr, file, name, ucslen, + shortname, srcent, attr, flags); if (rc < 0) - return rc * 10 - 7; + FAT_ERROR(rc * 10 - 6); - /* remember where the shortname dir entry is located */ - file->direntry = firstentry + entries_needed - 1; - file->direntries = entries_needed; - file->dircluster = dir->file.firstcluster; - LDEBUGF("Added new dir entry %d, using %d slots.\n", - file->direntry, file->direntries); + DEBUGF("Added new dir entry %u; using %u entries\n", + file->e.entry, file->e.entries); - return 0; + rc = 0; +fat_error: + return rc; } -static unsigned char char2dos(unsigned char c, int* randomize) +static int update_short_entry(struct bpb *fat_bpb, struct fat_file *file, + uint32_t size, struct fat_direntry *fatent) { - 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); + DEBUGF("%s(cluster:%lx entry:%d size:%ld)\n", + __func__, file->firstcluster, file->e.entry, size); - return c; -} - -static void create_dos_name(const unsigned char *name, unsigned char *newname) -{ - int i; - unsigned char *ext; - int randomize = 0; + int rc; - /* Find extension part */ - ext = strrchr(name, '.'); - if (ext == name) /* handle .dotnames */ - ext = NULL; +#if CONFIG_RTC + uint16_t time = 0; + uint16_t date = 0; +#else + /* get old time to increment from */ + uint16_t time = letoh16(fatent->wrttime); + uint16_t date = letoh16(fatent->wrtdate); +#endif + fat_time(&date, &time, NULL); + date = htole16(date); + time = htole16(time); - /* needs to randomize? */ - if((ext && (strlen(ext) > 4)) || - ((ext ? (unsigned int)(ext-name) : strlen(name)) > 8) ) - randomize = 1; + /* open the parent directory */ + struct fat_file parent; + fat_open_internal(IF_MV(file->volume,) file->dircluster, &parent); - /* Name part */ - for (i = 0; *name && (!ext || name < ext) && (i < 8); name++) - { - unsigned char c = char2dos(*name, &randomize); - if (c) - newname[i++] = c; - } + struct fat_filestr parentstr; + fat_filestr_init(&parentstr, &parent); - /* Pad both name and extension */ - while (i < 11) - newname[i++] = ' '; + dc_lock_cache(); - if (newname[0] == 0xe5) /* Special kanji character */ - newname[0] = 0x05; + union raw_dirent *ent = cache_direntry(fat_bpb, &parentstr, file->e.entry); + if (!ent) + FAT_ERROR(-1); - if (ext) - { /* Extension part */ - ext++; - for (i = 8; *ext && (i < 11); ext++) - { - unsigned char c = char2dos(*ext, &randomize); - if (c) - newname[i++] = c; - } - } + if (!ent->name[0] || ent->name[0] == 0xe5) + panicf("Updating size on empty dir entry %d\n", file->e.entry); - if(randomize) - randomize_dos_name(newname); -} + /* basic file data */ + raw_dirent_set_fstclus(ent, file->firstcluster); + ent->filesize = htole32(size); -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; + /* time and date info */ + ent->wrttime = time; + ent->wrtdate = date; + ent->lstaccdate = date; - while(i++ < 8) + if (fatent) { - /* hunt for ~ and where to put it */ - if(!tilde && *nameptr == '~') - tilde = nameptr; - if(!lastpt && (*nameptr == ' ' || *nameptr == '~')) - lastpt = nameptr; - nameptr++; + fatent->name[0] = '\0'; /* not gonna bother here */ + parse_short_direntry(ent, fatent); } - 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); + dc_dirty_buf(ent); - /* in special case of counter overflow: pad with spaces */ - for(offset = offset+numlen; offset < 8; offset++) - name[offset] = ' '; + rc = 0; +fat_error: + dc_unlock_cache(); + return rc; } -static int update_short_entry( struct fat_file* file, long size, int attr ) +static int free_direntries(struct bpb *fat_bpb, struct fat_file *file) { - int sector = file->direntry / DIR_ENTRIES_PER_SECTOR; - uint32_t* sizeptr; - uint16_t* clusptr; - struct fat_file dir; - int rc; + /* open the parent directory */ + struct fat_file parent; + fat_open_internal(IF_MV(file->volume,) file->dircluster, &parent); - LDEBUGF("update_file_size(cluster:%lx entry:%d size:%ld)\n", - file->firstcluster, file->direntry, size); + struct fat_filestr parentstr; + fat_filestr_init(&parentstr, &parent); - /* create a temporary file handle for the dir holding this file */ - rc = fat_open(IF_MV(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) + for (unsigned int entries = file->e.entries, + entry = file->e.entry - entries + 1; + entries; entries--, entry++) { - fat_release_sector_buffer(); - return rc * 10 - 3; - } + DEBUGF("Clearing dir entry %d (%d/%d)\n", + entry, entry - numentries + 1, numentries); - if (!entry[0] || entry[0] == 0xe5) - { - fat_release_sector_buffer(); - panicf("Updating size on empty dir entry %d\n", file->direntry); - } + dc_lock_cache(); - entry[FATDIR_ATTR] = attr & 0xFF; + union raw_dirent *ent = cache_direntry(fat_bpb, &parentstr, entry); + if (!ent) + { + dc_unlock_cache(); - clusptr = (uint16_t*)(entry + FATDIR_FSTCLUSHI); - *clusptr = htole16(file->firstcluster >> 16); + if (entries == file->e.entries) + return -1; /* nothing at all freed */ - clusptr = (uint16_t*)(entry + FATDIR_FSTCLUSLO); - *clusptr = htole16(file->firstcluster & 0xffff); + /* longname already destroyed; revert to shortname */ + file->e.entries = 1; + return 0; + } - sizeptr = (uint32_t*)(entry + FATDIR_FILESIZE); - *sizeptr = htole32(size); + ent->data[0] = 0xe5; - { -#if CONFIG_RTC - uint16_t time = 0; - uint16_t date = 0; -#else - /* get old time to increment from */ - uint16_t time = htole16(*(uint16_t*)(entry+FATDIR_WRTTIME)); - uint16_t date = htole16(*(uint16_t*)(entry+FATDIR_WRTDATE)); -#endif - fat_time(&date, &time, NULL); - *(uint16_t*)(entry + FATDIR_WRTTIME) = htole16(time); - *(uint16_t*)(entry + FATDIR_WRTDATE) = htole16(date); - *(uint16_t*)(entry + FATDIR_LSTACCDATE) = htole16(date); + dc_dirty_buf(ent); + dc_unlock_cache(); } - 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; + /* directory entry info is now gone */ + file->dircluster = 0; + file->e.entry = FAT_RW_VAL; + file->e.entries = 0; - return 0; + return 1; } -static int parse_direntry(struct fat_direntry *de, const unsigned char *buf) +static int free_cluster_chain(struct bpb *fat_bpb, long startcluster) { - int i=0,j=0; - unsigned char c; - bool lowercase; + for (long last = startcluster, next; last; last = next) + { + next = get_next_cluster(fat_bpb, last); + int rc = update_fat_entry(fat_bpb, last, 0); + if (LIKELY(rc >= 0 && !startcluster)) + continue; - 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) */ + if (rc < 0) + return startcluster ? -1 : 0; - /* 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; + startcluster = 0; } + return 1; } -int fat_open(IF_MV(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) - { - LDEBUGF("fat_open() illegal volume %d\n", volume); - return -1; - } -#endif - LDEBUGF("fat_open(%lx), entry %d\n",startcluster,file->direntry); - return 0; -} +/** File entity functions **/ -int fat_create_file(const char* name, - struct fat_file* file, - struct fat_dir* dir) +int fat_create_file(struct fat_file *parent, const char *name, + uint8_t attr, struct fat_file *file, + struct fat_direntry *fatent) { - int rc; - - LDEBUGF("fat_create_file(\"%s\",%lx,%lx)\n",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; - } + DEBUGF("%s(\"%s\",%lx,%lx)\n", __func__, name, (long)file, (long)dir); + struct bpb * const fat_bpb = FAT_BPB(parent->volume); + if (!fat_bpb) + return -1; - return rc; -} + int rc; -/* noinline because this is only split out of fat_create_dir to make sure - * the sector buffer doesn't remain on the stack, to avoid nasty stack - * overflows later on (when flush_fat() is called) */ -static __attribute__((noinline)) int fat_clear_cluster(int sector, - struct bpb *fat_bpb) -{ - unsigned char* buf = fat_get_sector_buffer(); - int i,rc; - memset(buf, 0, SECTOR_SIZE); - for(i = 0;i < (int)fat_bpb->bpb_secperclus;i++) { - rc = transfer(IF_MV(fat_bpb,) sector + i, 1, buf, true ); - if (rc < 0) - { - fat_release_sector_buffer(); - return rc * 10 - 2; - } - } - fat_release_sector_buffer(); - return 0; -} + fat_open_internal(IF_MV(parent->volume,) 0, file); -int fat_create_dir(const char* name, - struct fat_dir* newdir, - 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 - long sector; - int rc; - struct fat_file dummyfile; + struct fat_filestr parentstr; + fat_filestr_init(&parentstr, parent); - LDEBUGF("fat_create_dir(\"%s\",%lx,%lx)\n",name,(long)newdir,(long)dir); + const bool isdir = attr & ATTR_DIRECTORY; + unsigned int addflags = fatent ? DIRENT_RETURN : 0; + union raw_dirent *newentp = (isdir || fatent) ? + alloca(sizeof (union raw_dirent)) : NULL; - memset(newdir, 0, sizeof(struct fat_dir)); - memset(&dummyfile, 0, sizeof(struct fat_file)); + if (isdir) + { + struct fat_filestr dirstr; + fat_filestr_init(&dirstr, file); - /* First, add the entry in the parent directory */ - rc = add_dir_entry(dir, &newdir->file, name, true, false); - if (rc < 0) - return rc * 10 - 1; + /* create the first cluster */ + rc = fat_extend_dir(fat_bpb, &dirstr); + if (rc == FAT_RC_ENOSPC) + FAT_ERROR(RC); + else if (rc < 0) + FAT_ERROR(rc * 10 - 2); - /* Allocate a new cluster for the directory */ - newdir->file.firstcluster = find_free_cluster(IF_MV(fat_bpb,) - fat_bpb->fsinfo.nextfree); - if(newdir->file.firstcluster == 0) - return -1; + struct fat_file dummyfile; - update_fat_entry(IF_MV(fat_bpb,) newdir->file.firstcluster, FAT_EOF_MARK); + /* add the "." entry */ + fat_open_internal(IF_MV(0,) file->firstcluster, &dummyfile); - /* Clear the entire cluster */ - sector = cluster2sec(IF_MV(fat_bpb,) newdir->file.firstcluster); - rc = fat_clear_cluster(sector,fat_bpb); - if (rc < 0) - return rc; + /* this returns the short entry template for the remaining entries */ + rc = add_dir_entry(fat_bpb, &dirstr, &dummyfile, ".", newentp, + attr, DIRENT_RETURN); + if (rc < 0) + FAT_ERROR(rc * 10 - 3); + /* and the ".." entry */ + /* the root cluster is cluster 0 in the ".." entry */ + fat_open_internal(IF_MV(0,) + parent->firstcluster == fat_bpb->bpb_rootclus ? + 0 : parent->firstcluster, &dummyfile); - /* Then add the "." entry */ - rc = add_dir_entry(newdir, &dummyfile, ".", true, true); - if (rc < 0) - return rc * 10 - 3; - dummyfile.firstcluster = newdir->file.firstcluster; - update_short_entry(&dummyfile, 0, FAT_ATTR_DIRECTORY); + rc = add_dir_entry(fat_bpb, &dirstr, &dummyfile, "..", newentp, + attr, DIRENT_TEMPL_TIMES); + if (rc < 0) + FAT_ERROR(rc * 10 - 4); - /* and the ".." entry */ - rc = add_dir_entry(newdir, &dummyfile, "..", true, true); - if (rc < 0) - return rc * 10 - 4; + addflags |= DIRENT_TEMPL_TIMES; + } - /* The root cluster is cluster 0 in the ".." entry */ - if(dir->file.firstcluster == fat_bpb->bpb_rootclus) - dummyfile.firstcluster = 0; - else - dummyfile.firstcluster = dir->file.firstcluster; - update_short_entry(&dummyfile, 0, FAT_ATTR_DIRECTORY); + /* lastly, add the entry in the parent directory */ + rc = add_dir_entry(fat_bpb, &parentstr, file, name, newentp, + attr, addflags); + if (rc == FAT_RC_ENOSPC) + FAT_ERROR(RC); + else if (rc < 0) + FAT_ERROR(rc * 10 - 5); - /* Set the firstcluster field in the direntry */ - update_short_entry(&newdir->file, 0, FAT_ATTR_DIRECTORY); + if (fatent) + { + strcpy(fatent->name, name); + parse_short_direntry(newentp, fatent); + } - rc = flush_fat(IF_MV(fat_bpb)); + rc = 0; +fat_error: if (rc < 0) - return rc * 10 - 5; + free_cluster_chain(fat_bpb, file->firstcluster); + cache_commit(fat_bpb); return rc; } -int fat_truncate(const struct fat_file *file) +bool fat_dir_is_parent(const struct fat_file *dir, const struct fat_file *file) +{ + /* if the directory file's first cluster is the same as the file's + directory cluster and they're on the same volume, 'dir' is its parent + directory; the file must also have a dircluster (ie. not removed) */ + long dircluster = file->dircluster; + return dircluster && dircluster == dir->firstcluster + IF_MV( && dir->volume == file->volume ); +} + +bool fat_file_is_same(const struct fat_file *file1, + const struct fat_file *file2) +{ + /* first, triviality */ + if (file1 == file2) + return true; + + /* if the directory info matches and the volumes are the same, file1 and + file2 refer to the same file/directory */ + return file1->dircluster == file2->dircluster + && file1->e.entry == file2->e.entry + IF_MV( && file1->volume == file2->volume ); +} + +int fat_open(const struct fat_file *parent, long startcluster, + struct fat_file *file) { - /* truncate trailing clusters */ - long next; - long last = file->lastcluster; + if (!parent) + return -2; /* this does _not_ open any root */ + + struct bpb * const fat_bpb = FAT_BPB(parent->volume); + if (!fat_bpb) + return -1; + + /* inherit basic parent information; dirscan info is expected to have been + initialized beforehand (usually via scanning for the entry ;) */ #ifdef HAVE_MULTIVOLUME - struct bpb* fat_bpb = &fat_bpbs[file->volume]; + file->volume = parent->volume; #endif + file->firstcluster = startcluster; + file->dircluster = parent->firstcluster; - LDEBUGF("fat_truncate(%lx, %lx)\n", file->firstcluster, last); + return 0; +} - for ( last = get_next_cluster(IF_MV(fat_bpb,) last); last; last = next ) { - next = get_next_cluster(IF_MV(fat_bpb,) last); - update_fat_entry(IF_MV(fat_bpb,) last,0); - } - if (file->lastcluster) - update_fat_entry(IF_MV(fat_bpb,) file->lastcluster,FAT_EOF_MARK); +int fat_open_rootdir(IF_MV(int volume,) struct fat_file *dir) +{ + struct bpb * const fat_bpb = FAT_BPB(volume); + if (!fat_bpb) + return -1; + fat_open_internal(IF_MV(volume,) fat_bpb->bpb_rootclus, dir); return 0; } -int fat_closewrite(struct fat_file *file, long size, int attr) +int fat_remove(struct fat_file *file, enum fat_remove_op what) { + struct bpb * const fat_bpb = FAT_BPB(file->volume); + if (!fat_bpb) + return -1; + int rc; -#ifdef HAVE_MULTIVOLUME - struct bpb* fat_bpb = &fat_bpbs[file->volume]; -#endif - LDEBUGF("fat_closewrite(size=%ld)\n",size); - if (!size) { - /* empty file */ - if ( file->firstcluster ) { - update_fat_entry(IF_MV(fat_bpb,) file->firstcluster, 0); - file->firstcluster = 0; - } + if (file->firstcluster == fat_bpb->bpb_rootclus) + { + DEBUGF("Trying to remove root of volume %d\n", + IF_MV_VOL(info->volume)); + FAT_ERROR(-2); } - if (file->dircluster) { - rc = update_short_entry(file, size, attr); - if (rc < 0) - return rc * 10 - 1; + if (file->dircluster && (what & FAT_RM_DIRENTRIES)) + { + /* free everything in the parent directory */ + DEBUGF("Removing dir entries: %lX\n", info->dircluster); + rc = free_direntries(fat_bpb, file); + if (rc <= 0) + FAT_ERROR(rc * 10 - 3); } - flush_fat(IF_MV(fat_bpb)); + if (file->firstcluster && (what & FAT_RM_DATA)) + { + /* mark all clusters in the chain as free */ + DEBUGF("Removing cluster chain: %lX\n", file->firstcluster); + rc = free_cluster_chain(fat_bpb, file->firstcluster); + if (rc < 0) + FAT_ERROR(rc * 10 - 4); -#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_MV(fat_bpb,) next) ) { - LDEBUGF("cluster %ld: %lx\n", count, next); - count++; - } - len = count * fat_bpb->bpb_secperclus * SECTOR_SIZE; - LDEBUGF("File is %ld clusters (chainlen=%ld, size=%ld)\n", - count, len, size ); - if ( len > size + fat_bpb->bpb_secperclus * SECTOR_SIZE) - panicf("Cluster chain is too long\n"); - if ( len < size ) - panicf("Cluster chain is too short\n"); + /* at least the first cluster was freed */ + file->firstcluster = 0; + + if (rc == 0) + FAT_ERROR(-5); } -#endif - return 0; + rc = 0; +fat_error: + cache_commit(fat_bpb); + return rc; } -static int free_direntries(struct fat_file* file) +int fat_rename(struct fat_file *parent, struct fat_file *file, + const unsigned char *newname) { - 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; + struct bpb * const fat_bpb = FAT_BPB(parent->volume); + if (!fat_bpb) + return -1; + int rc; + /* save old file; don't change it unless everything succeeds */ + struct fat_file newfile = *file; - /* create a temporary file handle for the dir holding this file */ - rc = fat_open(IF_MV(file->volume,) file->dircluster, &dir, NULL); - if (rc < 0) - return rc * 10 - 1; +#ifdef HAVE_MULTIVOLUME + /* rename only works on the same volume */ + if (file->volume != parent->volume) + { + DEBUGF("No rename across volumes!\n"); + FAT_ERROR(-2); + } +#endif - rc = fat_seek( &dir, sector ); - if (rc < 0) - return rc * 10 - 2; + /* root directories can't be renamed */ + if (file->firstcluster == fat_bpb->bpb_rootclus) + { + DEBUGF("Trying to rename root of volume %d\n", + IF_MV_VOL(file->volume)); + FAT_ERROR(-3); + } - unsigned char* buf = fat_get_sector_buffer(); - rc = fat_readwrite(&dir, 1, buf, false); - if (rc < 1) + if (!file->dircluster) { - fat_release_sector_buffer(); - return rc * 10 - 3; + /* file was removed but is still open */ + DEBUGF("File has no dir cluster!\n"); + FAT_ERROR(-4); } - for (i=0; i < numentries; i++) { - LDEBUGF("Clearing dir entry %d (%d/%d)\n", - entry, i+1, numentries); - buf[(entry % DIR_ENTRIES_PER_SECTOR) * DIR_ENTRY_SIZE] = 0xe5; - entry++; + struct fat_file dir; + struct fat_filestr dirstr; - 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; - } + /* open old parent */ + fat_open_internal(IF_MV(file->volume,) file->dircluster, &dir); + fat_filestr_init(&dirstr, &dir); - rc = fat_readwrite(&dir, 1, buf, true); - if (rc < 1) - { - fat_release_sector_buffer(); - return rc * 10 - 5; - } + /* fetch a copy of the existing short entry */ + dc_lock_cache(); - 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++; - } + union raw_dirent *ent = cache_direntry(fat_bpb, &dirstr, file->e.entry); + if (!ent) + { + dc_unlock_cache(); + FAT_ERROR(-5); } - if ( entry % DIR_ENTRIES_PER_SECTOR ) { - /* flush this sector */ - rc = fat_seek(&dir, sector); - if (rc < 0) + union raw_dirent rawent = *ent; + + dc_unlock_cache(); + + /* create new name in new parent directory */ + fat_filestr_init(&dirstr, parent); + rc = add_dir_entry(fat_bpb, &dirstr, &newfile, newname, &rawent, + 0, DIRENT_TEMPL_CRT | DIRENT_TEMPL_WRT); + if (rc == FAT_RC_ENOSPC) + FAT_ERROR(RC); + else if (rc < 0) + FAT_ERROR(rc * 10 - 6); + + /* if renaming a directory and it was a move, update the '..' entry to + keep it pointing to its parent directory */ + if ((rawent.attr & ATTR_DIRECTORY) && newfile.dircluster != file->dircluster) + { + /* open the dir that was renamed */ + fat_open_internal(IF_MV(newfile.volume,) newfile.firstcluster, &dir); + fat_filestr_init(&dirstr, &dir); + + /* obtain dot-dot directory entry */ + dc_lock_cache(); + ent = cache_direntry(fat_bpb, &dirstr, 1); + if (!ent) { - fat_release_sector_buffer(); - return rc * 10 - 7; + dc_unlock_cache(); + FAT_ERROR(-7); } - rc = fat_readwrite(&dir, 1, buf, true); - if (rc < 1) + if (strncmp(".. ", ent->name, 11)) { - fat_release_sector_buffer(); - return rc * 10 - 8; + /* .. entry must be second entry according to FAT spec (p.29) */ + DEBUGF("Second dir entry is not double-dot!\n"); + dc_unlock_cache(); + FAT_ERROR(-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 + /* parent cluster is 0 if parent dir is the root - FAT spec (p.29) */ + long parentcluster = 0; + if (parent->firstcluster != fat_bpb->bpb_rootclus) + parentcluster = parent->firstcluster; - LDEBUGF("fat_remove(%lx)\n",last); + raw_dirent_set_fstclus(ent, parentcluster); - while ( last ) { - next = get_next_cluster(IF_MV(fat_bpb,) last); - update_fat_entry(IF_MV(fat_bpb,) last,0); - last = next; + dc_dirty_buf(ent); + dc_unlock_cache(); } - if ( file->dircluster ) { - rc = free_direntries(file); - if (rc < 0) - return rc * 10 - 1; - } + /* remove old name */ + rc = free_direntries(fat_bpb, file); + if (rc <= 0) + FAT_ERROR(rc * 10 - 9); - file->firstcluster = 0; - file->dircluster = 0; + /* finally, update old file with new directory entry info */ + *file = newfile; - rc = flush_fat(IF_MV(fat_bpb)); - if (rc < 0) - return rc * 10 - 2; + rc = 0; +fat_error: + if (rc < 0 && !fat_file_is_same(&newfile, file)) + free_direntries(fat_bpb, &newfile); - return 0; + cache_commit(fat_bpb); + return rc; } -int fat_rename(struct fat_file* file, - struct fat_dir* dir, - const unsigned char* newname, - long size, - int attr) -{ - int rc; - struct fat_file olddir_file; - 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!\n"); - return -1; - } -#else - struct bpb* fat_bpb = &fat_bpbs[0]; -#endif - - if ( !file->dircluster ) { - DEBUGF("File has no dir cluster!\n"); - 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; +/** File stream functions **/ - /* remove old name */ - rc = free_direntries(file); - if (rc < 0) - return rc * 10 - 4; +int fat_closewrite(struct fat_filestr *filestr, uint32_t size, + struct fat_direntry *fatentp) +{ + DEBUGF("%s(size=%ld)\n", __func__, size); + struct fat_file * const file = filestr->fatfilep; + struct bpb * const fat_bpb = FAT_BPB(file->volume); + if (!fat_bpb) + return -1; - rc = flush_fat(IF_MV(fat_bpb)); - if (rc < 0) - return rc * 10 - 5; + int rc; - /* 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 olddir_file struct */ - rc = fat_open(IF_MV(file->volume,) newfile.firstcluster, &olddir_file, NULL); + if (!size && file->firstcluster) + { + /* empty file */ + rc = update_fat_entry(fat_bpb, file->firstcluster, 0); if (rc < 0) - return rc * 10 - 6; + FAT_ERROR(rc * 10 - 2); - /* get the first sector of the dir */ - rc = fat_seek(&olddir_file, 0); - if (rc < 0) - return rc * 10 - 7; + file->firstcluster = 0; + fat_rewind(filestr); + } - unsigned char* buf = fat_get_sector_buffer(); - rc = fat_readwrite(&olddir_file, 1, buf, false); + if (file->dircluster) + { + rc = update_short_entry(fat_bpb, file, size, fatentp); 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; + FAT_ERROR(rc * 10 - 3); + } + else if (fatentp) + { + fat_empty_fat_direntry(fatentp); + } - entry = buf + DIR_ENTRY_SIZE; - if(strncmp(".. ", entry, 11)) +#ifdef TEST_FAT + if (file->firstcluster) + { + unsigned long count = 0; + for (long next = file->firstcluster; next; + next = get_next_cluster(fat_bpb, next)) { - fat_release_sector_buffer(); - /* .. entry must be second entry according to FAT spec (p.29) */ - DEBUGF("Second dir entry is not double-dot!\n"); - return rc * 10 - 9; + DEBUGF("cluster %ld: %lx\n", count, next); + count++; } - clusptr = (short*)(entry + FATDIR_FSTCLUSHI); - *clusptr = htole16(parentcluster >> 16); - clusptr = (short*)(entry + FATDIR_FSTCLUSLO); - *clusptr = htole16(parentcluster & 0xffff); + uint32_t len = count * fat_bpb->bpb_secperclus * SECTOR_SIZE; + DEBUGF("File is %lu clusters (chainlen=%lu, size=%lu)\n", + count, len, size ); - /* write back this sector */ - rc = fat_seek(&olddir_file, 0); - if (rc < 0) - { - fat_release_sector_buffer(); - return rc * 10 - 7; - } + if (len > size + fat_bpb->bpb_secperclus * SECTOR_SIZE) + panicf("Cluster chain is too long\n"); - rc = fat_readwrite(&olddir_file, 1, buf, true); - fat_release_sector_buffer(); - if (rc < 1) - return rc * 10 - 8; + if (len < size) + panicf("Cluster chain is too short\n"); } +#endif /* TEST_FAT */ - return 0; + rc = 0; +fat_error: + cache_commit(fat_bpb); + return rc; } -static long next_write_cluster(struct fat_file* file, - long oldcluster, - long* newsector) +void fat_filestr_init(struct fat_filestr *fatstr, struct fat_file *file) { -#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; - - LDEBUGF("next_write_cluster(%lx,%lx)\n",file->firstcluster, oldcluster); - - if (oldcluster) - cluster = get_next_cluster(IF_MV(fat_bpb,) oldcluster); - - if (!cluster) { - if (oldcluster > 0) - cluster = find_free_cluster(IF_MV(fat_bpb,) oldcluster+1); - else if (oldcluster == 0) - cluster = find_free_cluster(IF_MV(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 + fatstr->fatfilep = file; + fat_rewind(fatstr); +} - if (cluster) { - if (oldcluster) - update_fat_entry(IF_MV(fat_bpb,) oldcluster, cluster); - else - file->firstcluster = cluster; - update_fat_entry(IF_MV(fat_bpb,) cluster, FAT_EOF_MARK); - } - else { -#ifdef TEST_FAT - if (fat_bpb->fsinfo.freecount>0) - panicf("There is free space, but find_free_cluster() " - "didn't find it!\n"); -#endif - DEBUGF("next_write_cluster(): Disk full!\n"); - return 0; - } - } - sector = cluster2sec(IF_MV(fat_bpb,) cluster); - if (sector<0) - return 0; +unsigned long fat_query_sectornum(const struct fat_filestr *filestr) +{ + /* return next sector number to be transferred */ + struct bpb * const fat_bpb = FAT_BPB(filestr->fatfilep->volume); + if (!fat_bpb) + return INVALID_SECNUM; - *newsector = sector; - return cluster; + return fat_bpb->bpb_secperclus*filestr->clusternum + filestr->sectornum + 1; } -static int transfer(IF_MV(struct bpb* fat_bpb,) - unsigned long start, long count, char* buf, bool write ) +/* helper for fat_readwrite */ +static long transfer(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; + long rc = 0; - LDEBUGF("transfer(s=%lx, c=%lx, %s)\n", - start+ fat_bpb->startsector, count, write?"write":"read"); - if (write) { + DEBUGF("%s(s=%lx, c=%lx, wr=%u)\n", __func__, + start + fat_bpb->startsector, count, write ? 1 : 0); + + if (write) + { unsigned long firstallowed; #ifdef HAVE_FAT16SUPPORT if (fat_bpb->is_fat16) firstallowed = fat_bpb->rootdirsector; else -#endif +#endif /* HAVE_FAT16SUPPORT */ firstallowed = fat_bpb->firstdatasector; if (start < firstallowed) panicf("Write %ld before data\n", firstallowed - start); + if (start + count > fat_bpb->totalsectors) + { panicf("Write %ld after data\n", - start + count - fat_bpb->totalsectors); - rc = storage_write_sectors(IF_MD(fat_bpb->drive,) - start + fat_bpb->startsector, count, buf); + start + count - fat_bpb->totalsectors); + } + else + { + rc = storage_write_sectors(IF_MD(fat_bpb->drive,) + start + fat_bpb->startsector, count, buf); + } } else + { rc = storage_read_sectors(IF_MD(fat_bpb->drive,) - start + fat_bpb->startsector, count, buf); - if (rc < 0) { - DEBUGF( "transfer() - Couldn't %s sector %lx" - " (error code %d)\n", - write ? "write":"read", start, rc); + start + fat_bpb->startsector, count, buf); + } + + if (rc < 0) + { + DEBUGF("Couldn't %s sector %lx (err %d)\n", + write ? "write":"read", start, rc); return rc; } + return 0; } - -long fat_readwrite( struct fat_file *file, long sectorcount, - void* buf, bool write ) +long fat_readwrite(struct fat_filestr *filestr, unsigned 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; + struct fat_file * const file = filestr->fatfilep; + struct bpb * const fat_bpb = FAT_BPB(file->volume); + if (!fat_bpb) + return -1; - LDEBUGF( "fat_readwrite(file:%lx,count:0x%lx,buf:%lx,%s)\n", - file->firstcluster,sectorcount,(long)buf,write?"write":"read"); - LDEBUGF( "fat_readwrite: sec=%lx numsec=%ld eof=%d\n", - sector,numsec, eof?1:0); + bool eof = filestr->eof; - if ( eof && !write) + if ((eof && !write) || !sectorcount) 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, §or); - else { - cluster = get_next_cluster(IF_MV(fat_bpb,) cluster); - sector = cluster2sec(IF_MV(fat_bpb,) cluster); - } + long rc; - clusternum++; - numsec=1; + long cluster = filestr->lastcluster; + unsigned long sector = filestr->lastsector; + long clusternum = filestr->clusternum; + unsigned long sectornum = filestr->sectornum; - 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; + DEBUGF("%s(file:%lx,count:0x%lx,buf:%lx,%s)\n", __func__, + file->firstcluster, sectorcount, (long)buf, + write ? "write":"read"); + DEBUGF("%s: sec:%lx numsec:%ld eof:%d\n", __func__, + sector, (long)sectornum, eof ? 1 : 0); + + eof = false; + + if (!sector) + { + /* look up first sector of file */ + long newcluster = file->firstcluster; + + if (write && !newcluster) + { + /* file is empty; try to allocate its first cluster */ + newcluster = next_write_cluster(fat_bpb, 0); + file->firstcluster = newcluster; } - else { - if (sector) - sector++; - else { - /* look up first sector of file */ - sector = cluster2sec(IF_MV(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 (newcluster) + { + cluster = newcluster; + sector = cluster2sec(fat_bpb, cluster) - 1; + + #ifdef HAVE_FAT16SUPPORT + if (fat_bpb->is_fat16 && file->firstcluster < 0) + { + sector += fat_bpb->rootdirsectornum; + sectornum = fat_bpb->rootdirsectornum; } + #endif /* HAVE_FAT16SUPPORT */ } + } - if (!first) - first = sector; + if (!sector) + { + sectorcount = 0; + eof = true; + } - 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_MV(fat_bpb,) first, count, buf, write ); - if (rc < 0) - return rc * 10 - 1; + unsigned long transferred = 0; + unsigned long count = 0; + unsigned long last = sector; - buf = (char *)buf + count * SECTOR_SIZE; - first = sector; + while (transferred + count < sectorcount) + { + if (++sectornum >= fat_bpb->bpb_secperclus) + { + /* out of sectors in this cluster; get the next cluster */ + long newcluster = write ? next_write_cluster(fat_bpb, cluster) : + get_next_cluster(fat_bpb, cluster); + if (newcluster) + { + cluster = newcluster; + sector = cluster2sec(fat_bpb, cluster) - 1; + clusternum++; + sectornum = 0; + + /* jumped clusters right at start? */ + if (!count) + last = sector; + } + else + { + sectornum--; /* remain in previous position */ + eof = true; + break; + } } - if ((i == sectorcount-1) && /* last sector requested */ - (!eof)) + /* find sequential sectors and transfer them all at once */ + if (sector != last || count >= FAT_MAX_TRANSFER_SIZE) { - long count = sector - first + 1; - rc = transfer(IF_MV(fat_bpb,) first, count, buf, write ); + /* not sequential/over limit */ + rc = transfer(fat_bpb, last - count + 1, count, buf, write); if (rc < 0) - return rc * 10 - 2; + FAT_ERROR(rc * 10 - 2); + + transferred += count; + buf += count * SECTOR_SIZE; + count = 0; } - last = sector; + count++; + last = ++sector; } - file->lastcluster = cluster; - file->lastsector = sector; - file->clusternum = clusternum; - file->sectornum = numsec; - file->eof = eof; + if (count) + { + /* transfer any remainder */ + rc = transfer(fat_bpb, last - count + 1, count, buf, write); + if (rc < 0) + FAT_ERROR(rc * 10 - 3); + + transferred += count; + } + + rc = (eof && write) ? FAT_RC_ENOSPC : (long)transferred; +fat_error: + filestr->lastcluster = cluster; + filestr->lastsector = sector; + filestr->clusternum = clusternum; + filestr->sectornum = sectornum; + filestr->eof = eof; + + if (rc >= 0) + DEBUGF("Sectors transferred: %ld\n", rc); - /* if eof, don't report last block as read/written */ - if (eof) - i--; + return rc; +} - DEBUGF("Sectors written: %ld\n", i); - return i; +void fat_rewind(struct fat_filestr *filestr) +{ + /* rewind the file position */ + filestr->lastcluster = filestr->fatfilep->firstcluster; + filestr->lastsector = 0; + filestr->clusternum = 0; + filestr->sectornum = FAT_RW_VAL; + filestr->eof = false; } -int fat_seek(struct fat_file *file, unsigned long seeksector ) +int fat_seek(struct fat_filestr *filestr, 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; + const struct fat_file * const file = filestr->fatfilep; + struct bpb * const fat_bpb = FAT_BPB(file->volume); + if (!fat_bpb) + return -1; + + int rc; + long cluster = file->firstcluster; + unsigned long sector = 0; + long clusternum = 0; + unsigned long sectornum = FAT_RW_VAL; #ifdef HAVE_FAT16SUPPORT - if (cluster < 0) /* FAT16 root dir */ - seeksector += fat_bpb->rootdiroffset; -#endif + if (fat_bpb->is_fat16 && cluster < 0) /* FAT16 root dir */ + seeksector += fat_bpb->rootdirsectornum; +#endif /* HAVE_FAT16SUPPORT */ + + filestr->eof = false; + if (seeksector) + { + if (cluster == 0) + { + DEBUGF("Seeking beyond the end of empty file! " + "(sector %lu, cluster %ld)\n", seeksector, + seeksector / fat_bpb->bpb_secperclus); + FAT_ERROR(FAT_SEEK_EOF); + } - 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; + clusternum = seeksector / fat_bpb->bpb_secperclus; sectornum = seeksector % fat_bpb->bpb_secperclus; - if (file->clusternum && clusternum >= file->clusternum) + long numclusters = clusternum; + + if (filestr->clusternum && clusternum >= filestr->clusternum) { - cluster = file->lastcluster; - numclusters -= file->clusternum; + /* seek forward from current position */ + cluster = filestr->lastcluster; + numclusters -= filestr->clusternum; } - for (i=0; i<numclusters; i++) { - cluster = get_next_cluster(IF_MV(fat_bpb,) cluster); - if (!cluster) { + for (long i = 0; i < numclusters; i++) + { + cluster = get_next_cluster(fat_bpb, cluster); + + if (!cluster) + { DEBUGF("Seeking beyond the end of the file! " - "(sector %ld, cluster %ld)\n", seeksector, i); - return -1; + "(sector %lu, cluster %ld)\n", seeksector, i); + FAT_ERROR(FAT_SEEK_EOF); } } - sector = cluster2sec(IF_MV(fat_bpb,) cluster) + sectornum; - } - else { - sectornum = -1; + sector = cluster2sec(fat_bpb, cluster) + sectornum; } - LDEBUGF("fat_seek(%lx, %lx) == %lx, %lx, %lx\n", - file->firstcluster, seeksector, cluster, sector, sectornum); + DEBUGF("%s(%lx, %lx) == %lx, %lx, %lx\n", __func__, + file->firstcluster, seeksector, cluster, sector, sectornum); - file->lastcluster = cluster; - file->lastsector = sector; - file->clusternum = clusternum; - file->sectornum = sectornum + 1; - return 0; + filestr->lastcluster = cluster; + filestr->lastsector = sector; + filestr->clusternum = clusternum; + filestr->sectornum = sectornum; + + rc = 0; +fat_error: + return rc; } -int fat_opendir(IF_MV(int volume,) - struct fat_dir *dir, unsigned long startcluster, - const struct fat_dir *parent_dir) +int fat_truncate(const struct fat_filestr *filestr) { -#ifdef HAVE_MULTIVOLUME - struct bpb* fat_bpb = &fat_bpbs[volume]; - /* fixme: remove error check when done */ - if (volume >= NUM_VOLUMES || !fat_bpbs[volume].mounted) - { - LDEBUGF("fat_open() illegal volume %d\n", volume); + DEBUGF("%s(): %lX\n", __func__, filestr->lastcluster); + + struct bpb * const fat_bpb = FAT_BPB(filestr->fatfilep->volume); + if (!fat_bpb) return -1; - } -#else - struct bpb* fat_bpb = &fat_bpbs[0]; -#endif - int rc; - if (startcluster == 0) - startcluster = fat_bpb->bpb_rootclus; + int rc = 1; - rc = fat_open(IF_MV(volume,) startcluster, &dir->file, parent_dir); - if(rc) + long last = filestr->lastcluster; + long next = 0; + + /* truncate trailing clusters after the current position */ + if (last) { - DEBUGF( "fat_opendir() - Couldn't open dir" - " (error code %d)\n", rc); - return rc * 10 - 1; + next = get_next_cluster(fat_bpb, last); + int rc2 = update_fat_entry(fat_bpb, last, FAT_EOF_MARK); + if (rc2 < 0) + FAT_ERROR(rc2 * 10 - 2); } - - /* 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 rc2 = free_cluster_chain(fat_bpb, next); + if (rc2 <= 0) + { + DEBUGF("Failed freeing cluster chain\n"); + rc = 0; /* still partial success */ + } + +fat_error: + return rc; } -int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) + +/** Directory stream functions **/ + +int fat_readdir(struct fat_filestr *dirstr, struct fat_dirscan_info *scan, + struct filestr_cache *cachep, 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; + int rc = 0; + + /* long file names are stored in special entries; each entry holds up to + 13 UTF-16 characters' thus, UTF-8 converted names can be max 255 chars + (1020 bytes) long, not including the trailing '\0'. */ + struct fatlong_parse_state lnparse; + fatlong_parse_start(&lnparse); - dir->entrycount = 0; + scan->entries = 0; - while(!done) + while (1) { - if ( !(dir->entry % DIR_ENTRIES_PER_SECTOR) || !dir->sector ) + unsigned int direntry = ++scan->entry; + if (direntry >= MAX_DIRENTRIES) { - rc = fat_readwrite(&dir->file, 1, dir->sectorcache, false); - if (rc == 0) { - /* eof */ - entry->name[0] = 0; - break; + DEBUGF("%s() - Dir is too large (entry %u)\n", __func__, + direntry); + FAT_ERROR(-1); + } + + unsigned long sector = direntry / DIR_ENTRIES_PER_SECTOR; + if (cachep->sector != sector) + { + if (cachep->sector + 1 != sector) + { + /* Nothing cached or sector isn't contiguous */ + int rc2 = fat_seek(dirstr, sector); + if (rc2 < 0) + FAT_ERROR(rc2 * 10 - 2); } - if (rc < 0) { - DEBUGF( "fat_getnext() - Couldn't read dir" - " (error code %d)\n", rc); - return rc * 10 - 1; + + int rc2 = fat_readwrite(dirstr, 1, cachep->buffer, false); + if (rc2 <= 0) + { + if (rc2 == 0) + break; /* eof */ + + DEBUGF("%s() - Couldn't read dir (err %d)\n", __func__, rc); + FAT_ERROR(rc2 * 10 - 3); } - dir->sector = dir->file.lastsector; + + cachep->sector = sector; } - for (i = dir->entry % DIR_ENTRIES_PER_SECTOR; - i < DIR_ENTRIES_PER_SECTOR; i++) { - unsigned int entrypos = i * DIR_ENTRY_SIZE; + unsigned int index = direntry % DIR_ENTRIES_PER_SECTOR; + union raw_dirent *ent = &((union raw_dirent *)cachep->buffer)[index]; - firstbyte = dir->sectorcache[entrypos]; - dir->entry++; + if (ent->name[0] == 0) + break; /* last entry */ - if (firstbyte == 0xe5) { - /* free entry */ - dir->entrycount = 0; - continue; - } + if (ent->name[0] == 0xe5) + { + scan->entries = 0; + continue; /* free entry */ + } - if (firstbyte == 0) { - /* last entry */ - entry->name[0] = 0; - dir->entrycount = 0; - return 0; - } + ++scan->entries; - 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) { - logf("fat warning: orphan LFN entry"); - /* ignore */ - continue; - } - - /* check order */ - if (order != (last_long_ord - 1)) { - logf("fat warning: wrong LFN ordinal"); - /* ignore the whole LFN, will trigger lots of warnings */ - nb_longs = 0; - } - - last_long_ord = order; - } + if (IS_LDIR_ATTR(ent->ldir_attr)) + { + /* LFN entry */ + if (UNLIKELY(!fatlong_parse_entry(&lnparse, ent, entry))) + { + /* resync so we don't return just the short name if what we + landed in the middle of is valid (directory changes + between calls likely created the situation; ignoring this + case can be harmful elsewhere and is destructive to the + entry series itself) */ + struct bpb *fat_bpb = FAT_BPB(dirstr->fatfilep->volume); + if (!fat_bpb) + FAT_ERROR(-4); + + dc_lock_cache(); + + while (--scan->entry != FAT_RW_VAL) /* at beginning? */ + { + ent = cache_direntry(fat_bpb, dirstr, scan->entry); - /* 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]; + /* name[0] == 0 shouldn't happen here but... */ + if (!ent || ent->name[0] == 0 || ent->name[0] == 0xe5 || + !IS_LDIR_ATTR(ent->ldir_attr)) + break; } + + dc_unlock_cache(); + + /* retry it once from the new position */ + scan->entries = 0; + continue; } - 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 = utf8encode(ucs, longname_utf8segm) - longname_utf8segm; - - /* 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 { - 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. */ - logf("SN-DOS: %s", shortname); - unsigned char *utf8; - utf8 = iso_decode(shortname, entry->name, -1, - strlen(shortname)); - *utf8 = 0; - logf("SN: %s", entry->name); - } else { - logf("LN: %s", entry->name); - logf("LNLen: %d", longname_utf8len); - } - } - done = true; - i++; - break; - } + } + else if (!IS_VOL_ID_ATTR(ent->attr)) /* ignore volume id entry */ + { + rc = 1; + + if (!fatlong_parse_finish(&lnparse, ent, entry)) + { + /* the long entries failed to pass all checks or there is + just a short entry. */ + DEBUGF("SN-DOS:'%s'", entry->shortname); + strcpy(entry->name, entry->shortname); + scan->entries = 1; + rc = 2; /* name is OEM */ } + + DEBUGF("LN:\"%s\"", entry->name); + break; } + } /* end while */ + +fat_error: + if (rc <= 0) + { + /* error or eod; stay on last good position */ + fat_empty_fat_direntry(entry); + scan->entry--; + scan->entries = 0; } + + return rc; +} + +void fat_rewinddir(struct fat_dirscan_info *scan) +{ + /* rewind the directory scan counter to the beginning */ + scan->entry = FAT_RW_VAL; + scan->entries = 0; +} + + +/** Mounting and unmounting functions **/ + +bool fat_ismounted(IF_MV_NONVOID(int volume)) +{ + return !!FAT_BPB(volume); +} + +int fat_mount(IF_MV(int volume,) IF_MD(int drive,) unsigned long startsector) +{ + int rc; + + struct bpb * const fat_bpb = &fat_bpbs[IF_MV_VOL(volume)]; + if (fat_bpb->mounted) + FAT_ERROR(-1); /* double mount */ + + /* fill-in basic info first */ + fat_bpb->startsector = startsector; +#ifdef HAVE_MULTIVOLUME + fat_bpb->volume = volume; +#endif +#ifdef HAVE_MULTIDRIVE + fat_bpb->drive = drive; +#endif + + rc = fat_mount_internal(fat_bpb); + if (rc < 0) + FAT_ERROR(rc * 10 - 2); + + /* it worked */ + fat_bpb->mounted = true; + + /* calculate freecount if unset */ + if (fat_bpb->fsinfo.freecount == 0xffffffff) + fat_recalc_free(IF_MV(fat_bpb->volume)); + + DEBUGF("Freecount: %ld\n", (unsigned long)fat_bpb->fsinfo.freecount); + DEBUGF("Nextfree: 0x%lx\n", (unsigned long)fat_bpb->fsinfo.nextfree); + DEBUGF("Cluster count: 0x%lx\n", fat_bpb->dataclusters); + DEBUGF("Sectors per cluster: %d\n", fat_bpb->bpb_secperclus); + DEBUGF("FAT sectors: 0x%lx\n", fat_bpb->fatsize); + + rc = 0; +fat_error: + return rc; +} + +int fat_unmount(IF_MV_NONVOID(int volume)) +{ + struct bpb * const fat_bpb = FAT_BPB(volume); + if (!fat_bpb) + return -1; /* not mounted */ + + /* free the entries for this volume */ + cache_discard(IF_MV(fat_bpb)); + fat_bpb->mounted = false; + return 0; } + +/** Debug screen stuff **/ + +#ifdef MAX_LOG_SECTOR_SIZE +int fat_get_bytes_per_sector(IF_MV_NONVOID(int volume)) +{ + int bytes = 0; + + struct bpb * const fat_bpb = FAT_BPB(volume); + if (fat_bpb) + bytes = fat_bpb->bpb_bytspersec; + + return bytes; +} +#endif /* MAX_LOG_SECTOR_SIZE */ + 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; + unsigned int size = 0; + + struct bpb * const fat_bpb = FAT_BPB(volume); + if (fat_bpb) + size = fat_bpb->bpb_secperclus * SECTOR_SIZE; + + return size; } -#ifdef HAVE_MULTIVOLUME -bool fat_ismounted(int volume) +void fat_recalc_free(IF_MV_NONVOID(int volume)) { - return (volume<NUM_VOLUMES && fat_bpbs[volume].mounted); + struct bpb * const fat_bpb = FAT_BPB(volume); + if (!fat_bpb) + return; + + dc_lock_cache(); + fat_recalc_free_internal(fat_bpb); + dc_unlock_cache(); +} + +bool fat_size(IF_MV(int volume,) unsigned long *size, unsigned long *free) +{ + struct bpb * const fat_bpb = FAT_BPB(volume); + if (!fat_bpb) + return false; + + unsigned long factor = fat_bpb->bpb_secperclus * SECTOR_SIZE / 1024; + + if (size) *size = fat_bpb->dataclusters * factor; + if (free) *free = fat_bpb->fsinfo.freecount * factor; + + return true; +} + + +/** Misc. **/ + +void fat_empty_fat_direntry(struct fat_direntry *entry) +{ + entry->name[0] = 0; + entry->shortname[0] = 0; + entry->attr = 0; + entry->crttimetenth = 0; + entry->crttime = 0; + entry->crtdate = 0; + entry->lstaccdate = 0; + entry->wrttime = 0; + entry->wrtdate = 0; + entry->filesize = 0; + entry->firstcluster = 0; +} + +time_t fattime_mktime(uint16_t fatdate, uint16_t fattime) +{ + /* this knows our mktime() only uses these struct tm fields */ + struct tm tm; + tm.tm_sec = ((fattime ) & 0x1f) * 2; + tm.tm_min = ((fattime >> 5) & 0x3f); + tm.tm_hour = ((fattime >> 11) ); + tm.tm_mday = ((fatdate ) & 0x1f); + tm.tm_mon = ((fatdate >> 5) & 0x0f) - 1; + tm.tm_year = ((fatdate >> 9) ) + 80; + + return mktime(&tm); +} + +void fat_init(void) +{ + dc_lock_cache(); + + /* mark the possible volumes as not mounted */ + for (unsigned int i = 0; i < NUM_VOLUMES; i++) + { + dc_discard_all(IF_MV(i)); + fat_bpbs[i].mounted = false; + } + + dc_unlock_cache(); } -#endif |