summaryrefslogtreecommitdiffstats
path: root/firmware/drivers/fat.c
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2013-08-05 22:02:45 -0400
committerMichael Sevakis <jethead71@rockbox.org>2014-08-30 03:48:23 +0200
commit7d1a47cf13726c95ac46027156cc12dd9da5b855 (patch)
treeeb20d07656806479a8e1fea25887a490ea30d1d8 /firmware/drivers/fat.c
parent95a4c3afcd53a1f8b835dec33de51f9c304de4d9 (diff)
downloadrockbox-7d1a47cf13726c95ac46027156cc12dd9da5b855.tar.gz
rockbox-7d1a47cf13726c95ac46027156cc12dd9da5b855.zip
Rewrite filesystem code (WIP)
This patch redoes the filesystem code from the FAT driver up to the clipboard code in onplay.c. Not every aspect of this is finished therefore it is still "WIP". I don't wish to do too much at once (haha!). What is left to do is get dircache back in the sim and find an implementation for the dircache indicies in the tagcache and playlist code or do something else that has the same benefit. Leaving these out for now does not make anything unusable. All the basics are done. Phone app code should probably get vetted (and app path handling just plain rewritten as environment expansions); the SDL app and Android run well. Main things addressed: 1) Thread safety: There is none right now in the trunk code. Most of what currently works is luck when multiple threads are involved or multiple descriptors to the same file are open. 2) POSIX compliance: Many of the functions behave nothing like their counterparts on a host system. This leads to inconsistent code or very different behavior from native to hosted. One huge offender was rename(). Going point by point would fill a book. 3) Actual running RAM usage: Many targets will use less RAM and less stack space (some more RAM because I upped the number of cache buffers for large memory). There's very little memory lying fallow in rarely-used areas (see 'Key core changes' below). Also, all targets may open the same number of directory streams whereas before those with less than 8MB RAM were limited to 8, not 12 implying those targets will save slightly less. 4) Performance: The test_disk plugin shows markedly improved performance, particularly in the area of (uncached) directory scanning, due partly to more optimal directory reading and to a better sector cache algorithm. Uncached times tend to be better while there is a bit of a slowdown in dircache due to it being a bit heavier of an implementation. It's not noticeable by a human as far as I can say. Key core changes: 1) Files and directories share core code and data structures. 2) The filesystem code knows which descriptors refer to same file. This ensures that changes from one stream are appropriately reflected in every open descriptor for that file (fileobj_mgr.c). 3) File and directory cache buffers are borrowed from the main sector cache. This means that when they are not in use by a file, they are not wasted, but used for the cache. Most of the time, only a few of them are needed. It also means that adding more file and directory handles is less expensive. All one must do in ensure a large enough cache to borrow from. 4) Relative path components are supported and the namespace is unified. It does not support full relative paths to an implied current directory; what is does support is use of "." and "..". Adding the former would not be very difficult. The namespace is unified in the sense that volumes may be specified several times along with relative parts, e.g.: "/<0>/foo/../../<1>/bar" :<=> "/<1>/bar". 5) Stack usage is down due to sharing of data, static allocation and less duplication of strings on the stack. This requires more serialization than I would like but since the number of threads is limited to a low number, the tradoff in favor of the stack seems reasonable. 6) Separates and heirarchicalizes (sic) the SIM and APP filesystem code. SIM path and volume handling is just like the target. Some aspects of the APP file code get more straightforward (e.g. no path hashing is needed). Dircache: Deserves its own section. Dircache is new but pays homage to the old. The old one was not compatible and so it, since it got redone, does all the stuff it always should have done such as: 1) It may be update and used at any time during the build process. No longer has one to wait for it to finish building to do basic file management (create, remove, rename, etc.). 2) It does not need to be either fully scanned or completely disabled; it can be incomplete (i.e. overfilled, missing paths), still be of benefit and be correct. 3) Handles mounting and dismounting of individual volumes which means a full rebuild is not needed just because you pop a new SD card in the slot. Now, because it reuses its freed entry data, may rebuild only that volume. 4) Much more fundamental to the file code. When it is built, it is the keeper of the master file list whether enabled or not ("disabled" is just a state of the cache). Its must always to ready to be started and bind all streams opened prior to being enabled. 5) Maintains any short filenames in OEM format which means that it does not need to be rebuilt when changing the default codepage. Miscellaneous Compatibility: 1) Update any other code that would otherwise not work such as the hotswap mounting code in various card drivers. 2) File management: Clipboard needed updating because of the behavioral changes. Still needs a little more work on some finer points. 3) Remove now-obsolete functionality such as the mutex's "no preempt" flag (which was only for the prior FAT driver). 4) struct dirinfo uses time_t rather than raw FAT directory entry time fields. I plan to follow up on genericizing everything there (i.e. no FAT attributes). 5) unicode.c needed some redoing so that the file code does not try try to load codepages during a scan, which is actually a problem with the current code. The default codepage, if any is required, is now kept in RAM separarately (bufalloced) from codepages specified to iso_decode() (which must not be bufalloced because the conversion may be done by playback threads). Brings with it some additional reusable core code: 1) Revised file functions: Reusable code that does things such as safe path concatenation and parsing without buffer limitations or data duplication. Variants that copy or alter the input path may be based off these. To do: 1) Put dircache functionality back in the sim. Treating it internally as a different kind of file system seems the best approach at this time. 2) Restore use of dircache indexes in the playlist and database or something effectively the same. Since the cache doesn't have to be complete in order to be used, not getting a hit on the cache doesn't unambiguously say if the path exists or not. Change-Id: Ia30f3082a136253e3a0eae0784e3091d138915c8 Reviewed-on: http://gerrit.rockbox.org/566 Reviewed-by: Michael Sevakis <jethead71@rockbox.org> Tested: Michael Sevakis <jethead71@rockbox.org>
Diffstat (limited to 'firmware/drivers/fat.c')
-rw-r--r--firmware/drivers/fat.c4206
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, &sector);
- 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