summaryrefslogtreecommitdiffstats
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
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>
-rw-r--r--apps/bookmark.c8
-rw-r--r--apps/codecs.c2
-rw-r--r--apps/debug_menu.c86
-rw-r--r--apps/filetree.c4
-rw-r--r--apps/main.c102
-rw-r--r--apps/menus/display_menu.c20
-rw-r--r--apps/menus/main_menu.c18
-rw-r--r--apps/menus/settings_menu.c6
-rw-r--r--apps/misc.c16
-rw-r--r--apps/onplay.c844
-rwxr-xr-xapps/playlist.c237
-rw-r--r--apps/playlist_catalog.c2
-rw-r--r--apps/plugin.c264
-rw-r--r--apps/plugin.h45
-rw-r--r--apps/plugins/properties.c14
-rw-r--r--apps/radio/presets.c2
-rw-r--r--apps/radio/radioart.c2
-rw-r--r--apps/recorder/albumart.c2
-rw-r--r--apps/recorder/recording.c2
-rw-r--r--apps/root_menu.c5
-rw-r--r--apps/scrobbler.c2
-rw-r--r--apps/settings.c8
-rw-r--r--apps/settings_list.c6
-rw-r--r--apps/shortcuts.c2
-rw-r--r--apps/tagcache.c21
-rw-r--r--apps/tree.c55
-rw-r--r--bootloader/creativezvm.c5
-rw-r--r--bootloader/gigabeat-s.c16
-rw-r--r--bootloader/gigabeat.c3
-rw-r--r--bootloader/iaudio_coldfire.c2
-rw-r--r--bootloader/imx233.c3
-rw-r--r--bootloader/ipod.c10
-rw-r--r--bootloader/ipodnano2g.c4
-rw-r--r--bootloader/iriver_h1x0.c3
-rw-r--r--bootloader/iriver_h300.c4
-rw-r--r--bootloader/main-e200r-installer.c19
-rw-r--r--bootloader/main-pp.c15
-rw-r--r--bootloader/mini2440.c3
-rw-r--r--bootloader/mpio_hd200_hd300.c3
-rw-r--r--bootloader/mrobe500.c5
-rw-r--r--bootloader/ondavx747.c3
-rw-r--r--bootloader/rk27xx.c4
-rw-r--r--bootloader/sansa_as3525.c10
-rw-r--r--bootloader/sansaconnect.c3
-rw-r--r--bootloader/telechips.c3
-rw-r--r--bootloader/tpj1022.c3
-rw-r--r--firmware/SOURCES45
-rw-r--r--firmware/asm/mips/memcpy.S22
-rw-r--r--firmware/asm/sh/memcpy.S10
-rw-r--r--firmware/common/dir.c413
-rw-r--r--firmware/common/dir_uncached.c312
-rw-r--r--firmware/common/dircache.c3981
-rw-r--r--firmware/common/disk.c451
-rw-r--r--firmware/common/disk_cache.c343
-rw-r--r--firmware/common/file.c1637
-rw-r--r--firmware/common/file_internal.c776
-rw-r--r--firmware/common/filefuncs.c102
-rw-r--r--firmware/common/fileobj_mgr.c396
-rw-r--r--firmware/common/pathfuncs.c421
-rw-r--r--firmware/common/rbpaths.c432
-rw-r--r--firmware/common/unicode.c451
-rw-r--r--firmware/drivers/fat.c4206
-rw-r--r--firmware/export/config.h29
-rw-r--r--firmware/export/config/gigabeats.h3
-rw-r--r--firmware/export/disk.h13
-rw-r--r--firmware/export/fat.h225
-rw-r--r--firmware/export/hostfs.h25
-rw-r--r--firmware/export/load_code.h10
-rw-r--r--firmware/export/mv.h77
-rw-r--r--firmware/export/pathfuncs.h100
-rw-r--r--firmware/export/rbpaths.h21
-rw-r--r--firmware/export/storage.h7
-rw-r--r--firmware/export/system.h10
-rw-r--r--firmware/include/dir.h91
-rw-r--r--firmware/include/dir_uncached.h107
-rw-r--r--firmware/include/dircache.h209
-rw-r--r--firmware/include/dircache_redirect.h198
-rw-r--r--firmware/include/disk_cache.h83
-rw-r--r--firmware/include/file.h125
-rw-r--r--firmware/include/file_internal.h371
-rw-r--r--firmware/include/fileobj_mgr.h56
-rw-r--r--firmware/include/filesystem-native.h107
-rw-r--r--firmware/include/fs_attr.h39
-rw-r--r--firmware/include/rb-loader.h1
-rw-r--r--firmware/include/rbunicode.h18
-rw-r--r--firmware/kernel/include/mutex.h11
-rw-r--r--firmware/kernel/mutex.c5
-rw-r--r--firmware/libc/include/errno.h1
-rw-r--r--firmware/libc/include/fcntl.h20
-rw-r--r--firmware/libc/mktime.c2
-rw-r--r--firmware/storage.c9
-rw-r--r--firmware/target/arm/as3525/sd-as3525.c44
-rw-r--r--firmware/target/arm/as3525/sd-as3525v2.c41
-rw-r--r--firmware/target/arm/imx233/sdmmc-imx233.c45
-rw-r--r--firmware/target/arm/pp/ata-sd-pp.c30
-rw-r--r--firmware/target/arm/rk27xx/sd-rk27xx.c52
-rw-r--r--firmware/target/arm/s3c2440/sd-s3c2440.c34
-rw-r--r--firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c27
-rw-r--r--firmware/target/arm/tcc780x/sd-tcc780x.c32
-rw-r--r--firmware/target/arm/tms320dm320/sdmmc-dm320.c35
-rw-r--r--firmware/target/hosted/filesystem-app.c562
-rw-r--r--firmware/target/hosted/filesystem-app.h117
-rw-r--r--firmware/target/hosted/filesystem-hosted.h74
-rw-r--r--firmware/target/hosted/filesystem-unix.c202
-rw-r--r--firmware/target/hosted/filesystem-unix.h82
-rw-r--r--firmware/target/hosted/filesystem-win32.c479
-rw-r--r--firmware/target/hosted/filesystem-win32.h111
-rw-r--r--firmware/target/hosted/lc-unix.c11
-rw-r--r--firmware/target/hosted/sdl/app/load_code-sdl-app.c (renamed from firmware/export/filefuncs.h)31
-rw-r--r--firmware/target/hosted/sdl/filesystem-sdl.c55
-rw-r--r--firmware/target/hosted/sdl/filesystem-sdl.h37
-rw-r--r--firmware/target/hosted/sdl/load_code-sdl.c52
-rw-r--r--firmware/target/hosted/sdl/system-sdl.c4
-rw-r--r--firmware/target/hosted/sdl/system-sim.h32
-rw-r--r--firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c29
-rw-r--r--firmware/usbstack/usb_storage.c5
-rw-r--r--tools/checkwps/SOURCES14
-rw-r--r--tools/checkwps/checkwps.c7
-rw-r--r--tools/checkwps/file.h16
-rw-r--r--tools/database/SOURCES10
-rw-r--r--tools/root.make8
-rw-r--r--uisimulator/common/SOURCES17
-rw-r--r--uisimulator/common/dummylib.c1
-rw-r--r--uisimulator/common/filesystem-sim.c833
-rw-r--r--uisimulator/common/filesystem-sim.h108
-rw-r--r--uisimulator/common/io.c729
-rw-r--r--uisimulator/common/load_code-sim.c72
-rw-r--r--uisimulator/common/sim_tasks.c34
-rw-r--r--uisimulator/common/stubs.c5
-rw-r--r--uisimulator/common/time-win32.c61
130 files changed, 14689 insertions, 7316 deletions
diff --git a/apps/bookmark.c b/apps/bookmark.c
index 3234e77d9b..567f98ac29 100644
--- a/apps/bookmark.c
+++ b/apps/bookmark.c
@@ -41,7 +41,7 @@
#include "list.h"
#include "plugin.h"
#include "file.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#define MAX_BOOKMARKS 10
#define MAX_BOOKMARK_SIZE 350
@@ -1090,10 +1090,10 @@ static bool generate_bookmark_file_name(const char *in)
{
#ifdef HAVE_MULTIVOLUME
/* The "root" of an extra volume need special handling too. */
- bool volume_root = (strip_volume(in, global_bookmark_file_name) &&
- !strcmp("/", global_bookmark_file_name));
+ const char *filename;
+ path_strip_volume(in, &filename, true);
+ bool volume_root = *filename == '\0';
#endif
-
strcpy(global_bookmark_file_name, in);
if(global_bookmark_file_name[len-1] == '/')
len--;
diff --git a/apps/codecs.c b/apps/codecs.c
index e84cdf88aa..cabc9ba993 100644
--- a/apps/codecs.c
+++ b/apps/codecs.c
@@ -128,7 +128,7 @@ struct codec_api ci = {
logf,
#endif
- (qsort_func)qsort,
+ (void *)qsort,
#ifdef RB_PROFILE
profile_thread,
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index 61698f5025..75e23b3945 100644
--- a/apps/debug_menu.c
+++ b/apps/debug_menu.c
@@ -528,14 +528,19 @@ static const char* dbg_partitions_getname(int selected_item, void *data,
{
(void)data;
int partition = selected_item/2;
- struct partinfo* p = disk_partinfo(partition);
+
+ struct partinfo p;
+ if (!disk_partinfo(partition, &p))
+ return buffer;
+
if (selected_item%2)
{
- snprintf(buffer, buffer_len, " T:%x %ld MB", p->type, p->size / ( 2048 / ( SECTOR_SIZE / 512 )));
+ snprintf(buffer, buffer_len, " T:%x %ld MB", p.type,
+ p.size / ( 2048 / ( SECTOR_SIZE / 512 )));
}
else
{
- snprintf(buffer, buffer_len, "P%d: S:%lx", partition, p->start);
+ snprintf(buffer, buffer_len, "P%d: S:%lx", partition, p.start);
}
return buffer;
}
@@ -1377,7 +1382,7 @@ static int disk_callback(int btn, struct gui_synclist *lists)
simplelist_addline(
"Size: %s", buf);
unsigned long free;
- fat_size( IF_MV(0,) NULL, &free );
+ volume_size( IF_MV(0,) NULL, &free );
simplelist_addline(
"Free: %ld MB", free / 1024);
simplelist_addline(
@@ -1469,7 +1474,7 @@ static int disk_callback(int btn, struct gui_synclist *lists)
"No timing info");
}
simplelist_addline(
- "Cluster size: %d bytes", fat_get_cluster_size(IF_MV(0)));
+ "Cluster size: %d bytes", volume_get_cluster_size(IF_MV(0)));
#ifdef HAVE_ATA_DMA
i = ata_get_dma_mode();
if (i == 0) {
@@ -1496,11 +1501,11 @@ static int disk_callback(int btn, struct gui_synclist *lists)
simplelist_addline(
"Size: %ld MB", info.num_sectors*(info.sector_size/512)/2024);
unsigned long free;
- fat_size( IF_MV(0,) NULL, &free );
+ volume_size( IF_MV(0,) NULL, &free );
simplelist_addline(
"Free: %ld MB", free / 1024);
simplelist_addline(
- "Cluster size: %d bytes", fat_get_cluster_size(IF_MV(0)));
+ "Cluster size: %d bytes", volume_get_cluster_size(IF_MV(0)));
return btn;
}
#endif
@@ -1542,29 +1547,64 @@ static bool dbg_disk_info(void)
#ifdef HAVE_DIRCACHE
static int dircache_callback(int btn, struct gui_synclist *lists)
{
- (void)lists;
+ struct dircache_info info;
+ dircache_get_info(&info);
+
+ if (global_settings.dircache)
+ {
+ switch (btn)
+ {
+ case ACTION_STD_CONTEXT:
+ splash(HZ/2, "Rebuilding cache");
+ dircache_suspend();
+ *(int *)lists->data = dircache_resume();
+ case ACTION_UNKNOWN:
+ btn = ACTION_NONE;
+ break;
+ #ifdef DIRCACHE_DUMPSTER
+ case ACTION_STD_OK:
+ splash(0, "Dumping cache");
+ dircache_dump();
+ btn = ACTION_NONE;
+ break;
+ #endif /* DIRCACHE_DUMPSTER */
+ case ACTION_STD_CANCEL:
+ if (*(int *)lists->data > 0 && info.status == DIRCACHE_SCANNING)
+ {
+ splash(HZ, str(LANG_SCANNING_DISK));
+ btn = ACTION_NONE;
+ }
+ break;
+ }
+ }
+
simplelist_set_line_count(0);
- simplelist_addline("Cache initialized: %s",
- dircache_is_enabled() ? "Yes" : "No");
- simplelist_addline("Cache size: %d B",
- dircache_get_cache_size());
- simplelist_addline("Last size: %d B",
- global_status.dircache_size);
- simplelist_addline("Limit: %d B",
- DIRCACHE_LIMIT);
- simplelist_addline("Reserve: %d/%d B",
- dircache_get_reserve_used(), DIRCACHE_RESERVE);
- simplelist_addline("Scanning took: %d s",
- dircache_get_build_ticks() / HZ);
- simplelist_addline("Entry count: %d",
- dircache_get_entry_count());
+
+ simplelist_addline("Cache status: %s", info.statusdesc);
+ simplelist_addline("Last size: %lu B", info.last_size);
+ simplelist_addline("Size: %lu B", info.size);
+ unsigned int utilized = info.size ? 1000ull*info.sizeused / info.size : 0;
+ simplelist_addline("Used: %lu B (%u.%u%%)", info.sizeused,
+ utilized / 10, utilized % 10);
+ simplelist_addline("Limit: %lu B", info.size_limit);
+ simplelist_addline("Reserve: %lu/%lu B", info.reserve_used, info.reserve);
+ long ticks = ALIGN_UP(info.build_ticks, HZ / 10);
+ simplelist_addline("Scanning took: %ld.%ld s",
+ ticks / HZ, (ticks*10 / HZ) % 10);
+ simplelist_addline("Entry count: %u", info.entry_count);
+
+ if (btn == ACTION_NONE)
+ btn = ACTION_REDRAW;
+
return btn;
+ (void)lists;
}
static bool dbg_dircache_info(void)
{
struct simplelist_info info;
- simplelist_info_init(&info, "Dircache Info", 7, NULL);
+ int syncbuild = 0;
+ simplelist_info_init(&info, "Dircache Info", 8, &syncbuild);
info.action_callback = dircache_callback;
info.hide_selection = true;
info.scroll_all = true;
diff --git a/apps/filetree.c b/apps/filetree.c
index 319b5f4a77..64283b274b 100644
--- a/apps/filetree.c
+++ b/apps/filetree.c
@@ -376,9 +376,7 @@ int ft_load(struct tree_context* c, const char* tempdir)
++files_in_dir;
dptr->name = core_get_data(c->cache.name_buffer_handle)+name_buffer_used;
- dptr->time_write =
- (long)info.wrtdate<<16 |
- (long)info.wrttime; /* in one # */
+ dptr->time_write = info.mtime;
strcpy(dptr->name, (char *)entry->d_name);
name_buffer_used += len + 1;
diff --git a/apps/main.c b/apps/main.c
index 6c6f0d6aba..9098180fb8 100644
--- a/apps/main.c
+++ b/apps/main.c
@@ -19,11 +19,12 @@
*
****************************************************************************/
#include "config.h"
+#include "system.h"
#include "gcc_extensions.h"
#include "storage.h"
#include "disk.h"
-#include "fat.h"
+#include "file_internal.h"
#include "lcd.h"
#include "rtc.h"
#include "debug.h"
@@ -34,7 +35,6 @@
#include "filetypes.h"
#include "panic.h"
#include "menu.h"
-#include "system.h"
#include "usb.h"
#include "powermgmt.h"
#include "adc.h"
@@ -203,80 +203,53 @@ int main(void)
root_menu();
}
-static int init_dircache(bool preinit) INIT_ATTR;
-static int init_dircache(bool preinit)
-{
#ifdef HAVE_DIRCACHE
- int result = 0;
- bool clear = false;
-
+static int INIT_ATTR init_dircache(bool preinit)
+{
if (preinit)
- dircache_init();
+ dircache_init(MAX(global_status.dircache_size, 0));
if (!global_settings.dircache)
- return 0;
+ return -1;
+
+ int result = -1;
-# ifdef HAVE_EEPROM_SETTINGS
- if (firmware_settings.initialized && firmware_settings.disk_clean
- && preinit)
+#ifdef HAVE_EEPROM_SETTINGS
+ if (firmware_settings.initialized &&
+ firmware_settings.disk_clean &&
+ preinit)
{
result = dircache_load();
-
if (result < 0)
- {
firmware_settings.disk_clean = false;
- if (global_status.dircache_size <= 0)
- {
- /* This will be in default language, settings are not
- applied yet. Not really any easy way to fix that. */
- splash(0, str(LANG_SCANNING_DISK));
- clear = true;
- }
-
- dircache_build(global_status.dircache_size);
- }
}
else
-# endif
+#endif /* HAVE_EEPROM_SETTINGS */
+ if (!preinit)
{
- if (preinit)
- return -1;
-
- if (!dircache_is_enabled()
- && !dircache_is_initializing())
+ result = dircache_enable();
+ if (result != 0)
{
- if (global_status.dircache_size <= 0)
+ if (result > 0)
{
+ /* Print "Scanning disk..." to the display. */
splash(0, str(LANG_SCANNING_DISK));
- clear = true;
+ dircache_wait();
+ backlight_on();
+ show_logo();
}
- result = dircache_build(global_status.dircache_size);
- }
- if (result < 0)
- {
- /* Initialization of dircache failed. Manual action is
- * necessary to enable dircache again.
- */
- splashf(0, "Dircache failed, disabled. Result: %d", result);
- global_settings.dircache = false;
+ struct dircache_info info;
+ dircache_get_info(&info);
+ global_status.dircache_size = info.size;
+ status_save();
}
- }
-
- if (clear)
- {
- backlight_on();
- show_logo();
- global_status.dircache_size = dircache_get_cache_size();
- status_save();
+ /* else don't wait or already enabled by load */
}
return result;
-#else
- (void)preinit;
- return 0;
-#endif
}
+#endif /* HAVE_DIRCACHE */
#ifdef HAVE_TAGCACHE
static void init_tagcache(void) INIT_ATTR;
@@ -363,6 +336,7 @@ static void init(void)
button_init();
powermgmt_init();
backlight_init();
+ unicode_init();
#ifdef SIMULATOR
sim_tasks_init();
#endif
@@ -392,8 +366,10 @@ static void init(void)
settings_reset();
settings_load(SETTINGS_ALL);
settings_apply(true);
+#ifdef HAVE_DIRCACHE
init_dircache(true);
init_dircache(false);
+#endif
#ifdef HAVE_TAGCACHE
init_tagcache();
#endif
@@ -429,6 +405,8 @@ static void init(void)
#else
+#include "errno.h"
+
static void init(void) INIT_ATTR;
static void init(void)
{
@@ -443,6 +421,9 @@ static void init(void)
core_allocator_init();
kernel_init();
+ /* early early early! */
+ filesystem_init();
+
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
set_cpu_frequency(CPUFREQ_NORMAL);
#ifdef CPU_COLDFIRE
@@ -462,6 +443,7 @@ static void init(void)
/* current_tick should be ticking by now */
CHART("ticking");
+ unicode_init();
lcd_init();
#ifdef HAVE_REMOTE_LCD
lcd_remote_init();
@@ -558,8 +540,6 @@ static void init(void)
}
#endif
-
- disk_init_subsystem();
CHART(">storage_init");
rc = storage_init();
CHART("<storage_init");
@@ -661,22 +641,24 @@ static void init(void)
CHART("<settings_load(ALL)");
}
+#ifdef HAVE_DIRCACHE
CHART(">init_dircache(true)");
rc = init_dircache(true);
CHART("<init_dircache(true)");
- if (rc < 0)
- {
#ifdef HAVE_TAGCACHE
+ if (rc < 0)
remove(TAGCACHE_STATEFILE);
-#endif
- }
+#endif /* HAVE_TAGCACHE */
+#endif /* HAVE_DIRCACHE */
CHART(">settings_apply(true)");
settings_apply(true);
CHART("<settings_apply(true)");
+#ifdef HAVE_DIRCACHE
CHART(">init_dircache(false)");
init_dircache(false);
CHART("<init_dircache(false)");
+#endif
#ifdef HAVE_TAGCACHE
CHART(">init_tagcache");
init_tagcache();
diff --git a/apps/menus/display_menu.c b/apps/menus/display_menu.c
index 3e1443d02e..948dcede00 100644
--- a/apps/menus/display_menu.c
+++ b/apps/menus/display_menu.c
@@ -43,6 +43,7 @@
#endif
#include "viewport.h"
#include "statusbar.h" /* statusbar_vals enum*/
+#include "rbunicode.h"
#ifdef HAVE_BACKLIGHT
static int filterfirstkeypress_callback(int action,const struct menu_item_ex *this_item)
@@ -524,8 +525,25 @@ MAKE_MENU(touchscreen_menu, ID2P(LANG_TOUCHSCREEN_SETTINGS), NULL, Icon_NOICON,
&touchscreen_menu_calibrate, &touchscreen_menu_reset_calibration);
#endif
+static int codepage_callback(int action, const struct menu_item_ex *this_item)
+{
+ static int old_codepage;
+ int new_codepage = global_settings.default_codepage;
+ (void)this_item;
+ switch (action)
+ {
+ case ACTION_ENTER_MENUITEM:
+ old_codepage = new_codepage;
+ break;
+ case ACTION_EXIT_MENUITEM:
+ if (new_codepage != old_codepage)
+ set_codepage(new_codepage);
+ break;
+ }
+ return action;
+}
-MENUITEM_SETTING(codepage_setting, &global_settings.default_codepage, NULL);
+MENUITEM_SETTING(codepage_setting, &global_settings.default_codepage, codepage_callback);
MAKE_MENU(display_menu, ID2P(LANG_DISPLAY),
diff --git a/apps/menus/main_menu.c b/apps/menus/main_menu.c
index 6a1295996c..8764101f73 100644
--- a/apps/menus/main_menu.c
+++ b/apps/menus/main_menu.c
@@ -48,6 +48,7 @@
#include "time.h"
#include "wps.h"
#include "skin_buffer.h"
+#include "disk.h"
static const struct browse_folder_info config = {ROCKBOX_DIR, SHOW_CFG};
@@ -160,14 +161,14 @@ static const char* info_getname(int selected_item, void *data,
#endif
if (info->new_data)
{
- fat_size(IF_MV(0,) &info->size, &info->free);
+ volume_size(IF_MV(0,) &info->size, &info->free);
#ifdef HAVE_MULTIVOLUME
#ifndef APPLICATION
- if (fat_ismounted(1))
- fat_size(1, &info->size2, &info->free2);
- else
+ volume_size(1, &info->size2, &info->free2);
+#else
+ info->size2 = 0;
#endif
- info->size2 = 0;
+
#endif
info->new_data = false;
}
@@ -347,12 +348,7 @@ static int info_action_callback(int action, struct gui_synclist *lists)
info->new_data = true;
splash(0, ID2P(LANG_SCANNING_DISK));
for (i = 0; i < NUM_VOLUMES; i++)
- {
-#ifdef HAVE_HOTSWAP
- if (fat_ismounted(i))
-#endif
- fat_recalc_free(IF_MV(i));
- }
+ volume_recalc_free(IF_MV(i));
#else
(void) lists;
#endif
diff --git a/apps/menus/settings_menu.c b/apps/menus/settings_menu.c
index 95423a20fa..0d2a7febf1 100644
--- a/apps/menus/settings_menu.c
+++ b/apps/menus/settings_menu.c
@@ -209,12 +209,12 @@ static int dircache_callback(int action,const struct menu_item_ex *this_item)
switch (action)
{
case ACTION_EXIT_MENUITEM: /* on exit */
- if (global_settings.dircache && !dircache_is_enabled())
+ if (global_settings.dircache)
{
- if (dircache_build(0) < 0)
+ if (dircache_enable() < 0)
splash(HZ*2, ID2P(LANG_PLEASE_REBOOT));
}
- else if (!global_settings.dircache && dircache_is_enabled())
+ else
{
dircache_disable();
}
diff --git a/apps/misc.c b/apps/misc.c
index f847023c31..b6eaafb599 100644
--- a/apps/misc.c
+++ b/apps/misc.c
@@ -28,9 +28,12 @@
#include "misc.h"
#include "system.h"
#include "lcd.h"
+#ifdef HAVE_DIRCACHE
+#include "dircache.h"
+#endif
#include "file.h"
#ifndef __PCTOOL__
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "lang.h"
#include "dir.h"
#ifdef HAVE_REMOTE_LCD
@@ -744,8 +747,7 @@ int show_logo( void )
*/
void check_bootfile(bool do_rolo)
{
- static unsigned short wrtdate = 0;
- static unsigned short wrttime = 0;
+ static time_t mtime = 0;
DIR* dir = NULL;
struct dirent* entry = NULL;
@@ -761,10 +763,9 @@ void check_bootfile(bool do_rolo)
{
struct dirinfo info = dir_get_info(dir, entry);
/* found the bootfile */
- if(wrtdate && do_rolo)
+ if(mtime && do_rolo)
{
- if((info.wrtdate != wrtdate) ||
- (info.wrttime != wrttime))
+ if(info.mtime != mtime)
{
static const char *lines[] = { ID2P(LANG_BOOT_CHANGED),
ID2P(LANG_REBOOT_NOW) };
@@ -777,8 +778,7 @@ void check_bootfile(bool do_rolo)
}
}
}
- wrtdate = info.wrtdate;
- wrttime = info.wrttime;
+ mtime = info.mtime;
}
}
closedir(dir);
diff --git a/apps/onplay.c b/apps/onplay.c
index 7c5f517090..091680e949 100644
--- a/apps/onplay.c
+++ b/apps/onplay.c
@@ -62,16 +62,13 @@
#include "statusbar-skinned.h"
#include "pitchscreen.h"
#include "viewport.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "shortcuts.h"
static int context;
-static const char* selected_file = NULL;
+static const char *selected_file = NULL;
static int selected_file_attr = 0;
static int onplay_result = ONPLAY_OK;
-static char clipboard_selection[MAX_PATH];
-static int clipboard_selection_attr = 0;
-static bool clipboard_is_copy = false;
/* redefine MAKE_MENU so the MENU_EXITAFTERTHISMENU flag can be added easily */
#define MAKE_ONPLAYMENU( name, str, callback, icon, ... ) \
@@ -82,6 +79,63 @@ static bool clipboard_is_copy = false;
MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \
{ (void*)name##_},{.callback_and_desc = & name##__}};
+/* Used for directory move, copy and delete */
+struct dirrecurse_params
+{
+ char path[MAX_PATH]; /* Buffer for full path */
+ size_t append; /* Append position in 'path' for stack push */
+};
+
+enum clipboard_op_flags
+{
+ PASTE_CUT = 0x00, /* Is a move (cut) operation (default) */
+ PASTE_COPY = 0x01, /* Is a copy operation */
+ PASTE_OVERWRITE = 0x02, /* Overwrite destination */
+ PASTE_EXDEV = 0x04, /* Actually copy/move across volumes */
+};
+
+/* result codec of various onplay operations */
+enum onplay_result_code
+{
+ /* Anything < 0 is failure */
+ OPRC_SUCCESS = 0, /* All operations completed successfully */
+ OPRC_NOOP = 1, /* Operation didn't need to do anything */
+ OPRC_CANCELLED = 2, /* Operation was cancelled by user */
+ OPRC_NOOVERWRT = 3,
+};
+
+static struct clipboard
+{
+ char path[MAX_PATH]; /* Clipped file's path */
+ unsigned int attr; /* Clipped file's attributes */
+ unsigned int flags; /* Operation type flags */
+} clipboard;
+
+/* Empty the clipboard */
+static void clipboard_clear_selection(struct clipboard *clip)
+{
+ clip->path[0] = '\0';
+ clip->attr = 0;
+ clip->flags = 0;
+}
+
+/* Store the selection in the clipboard */
+static bool clipboard_clip(struct clipboard *clip, const char *path,
+ unsigned int attr, unsigned int flags)
+{
+ /* if it fits it clips */
+ if (strlcpy(clip->path, path, sizeof (clip->path))
+ < sizeof (clip->path)) {
+ clip->attr = attr;
+ clip->flags = flags;
+ return true;
+ }
+ else {
+ clipboard_clear_selection(clip);
+ return false;
+ }
+}
+
/* ----------------------------------------------------------------------- */
/* Displays the bookmark menu options for the user to decide. This is an */
/* interface function. */
@@ -492,438 +546,578 @@ static void draw_slider(void)
#define draw_slider()
#endif
-/* helper function to remove a non-empty directory */
-static int remove_dir(char* dirname, int len)
+static void clear_display(bool update)
{
- int result = 0;
- DIR* dir;
- int dirlen = strlen(dirname);
+ struct viewport vp;
- dir = opendir(dirname);
- if (!dir)
+ FOR_NB_SCREENS(i)
+ {
+ struct screen * screen = &screens[i];
+ viewport_set_defaults(&vp, screen->screen_type);
+ screen->set_viewport(&vp);
+ screen->clear_viewport();
+ if (update) {
+ screen->update_viewport();
+ }
+ screen->set_viewport(NULL);
+ }
+}
+
+static void splash_path(const char *path)
+{
+ clear_display(false);
+ path_basename(path, &path);
+ splash(0, path);
+ draw_slider();
+}
+
+/* Splashes the path and checks the keys */
+static bool poll_cancel_action(const char *path)
+{
+ splash_path(path);
+ return ACTION_STD_CANCEL == get_action(CONTEXT_STD, TIMEOUT_NOBLOCK);
+}
+
+static int confirm_overwrite(void)
+{
+ static const char *lines[] = { ID2P(LANG_REALLY_OVERWRITE) };
+ static const struct text_message message = { lines, 1 };
+ return gui_syncyesno_run(&message, NULL, NULL);
+}
+
+static int confirm_delete(const char *file)
+{
+ const char *lines[] = { ID2P(LANG_REALLY_DELETE), file };
+ const char *yes_lines[] = { ID2P(LANG_DELETING), file };
+ const struct text_message message = { lines, 2 };
+ const struct text_message yes_message = { yes_lines, 2 };
+ return gui_syncyesno_run(&message, &yes_message, NULL);
+}
+
+static bool check_new_name(const char *basename)
+{
+ /* at least prevent escapes out of the base directory from keyboard-
+ entered filenames; the file code should reject other invalidities */
+ return *basename != '\0' && !strchr(basename, PATH_SEPCH) &&
+ !is_dotdir_name(basename);
+}
+
+static void splash_cancelled(void)
+{
+ clear_display(true);
+ splash(HZ, ID2P(LANG_CANCEL));
+}
+
+static void splash_failed(int lang_what)
+{
+ cond_talk_ids_fq(lang_what, LANG_FAILED);
+ clear_display(true);
+ splashf(HZ*2, "%s %s", str(lang_what), str(LANG_FAILED));
+}
+
+/* helper function to remove a non-empty directory */
+static int remove_dir(struct dirrecurse_params *parm)
+{
+ DIR *dir = opendir(parm->path);
+ if (!dir) {
return -1; /* open error */
+ }
- while(true)
- {
- struct dirent* entry;
- /* walk through the directory content */
- entry = readdir(dir);
- if (!entry)
+ size_t append = parm->append;
+ int rc = OPRC_SUCCESS;
+
+ /* walk through the directory content */
+ while (rc == OPRC_SUCCESS) {
+ errno = 0; /* distinguish failure from eod */
+ struct dirent *entry = readdir(dir);
+ if (!entry) {
+ if (errno) {
+ rc = -1;
+ }
break;
+ }
+
struct dirinfo info = dir_get_info(dir, entry);
- dirname[dirlen] ='\0';
- /* inform the user which dir we're deleting */
- splash(0, dirname);
+ if ((info.attribute & ATTR_DIRECTORY) &&
+ is_dotdir_name(entry->d_name)) {
+ continue; /* skip these */
+ }
/* append name to current directory */
- snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name);
- if (info.attribute & ATTR_DIRECTORY)
- { /* remove a subdirectory */
- if (!strcmp((char *)entry->d_name, ".") ||
- !strcmp((char *)entry->d_name, ".."))
- continue; /* skip these */
-
- result = remove_dir(dirname, len); /* recursion */
- if (result)
- break; /* or better continue, delete what we can? */
- }
- else
- { /* remove a file */
- draw_slider();
- result = remove(dirname);
+ parm->append = append + path_append(&parm->path[append],
+ PA_SEP_HARD, entry->d_name,
+ sizeof (parm->path) - append);
+ if (parm->append >= sizeof (parm->path)) {
+ rc = -1;
+ break; /* no space left in buffer */
}
- if(ACTION_STD_CANCEL == get_action(CONTEXT_STD,TIMEOUT_NOBLOCK))
- {
- splash(HZ, ID2P(LANG_CANCEL));
- result = -1;
- break;
+
+ if (info.attribute & ATTR_DIRECTORY) {
+ /* remove a subdirectory */
+ rc = remove_dir(parm);
+ } else {
+ /* remove a file */
+ if (poll_cancel_action(parm->path)) {
+ rc = OPRC_CANCELLED;
+ break;
+ }
+
+ rc = remove(parm->path);
}
+
+ /* Remove basename we added above */
+ parm->path[append] = '\0';
}
- closedir(dir);
- if (!result)
- { /* remove the now empty directory */
- dirname[dirlen] = '\0'; /* terminate to original length */
+ closedir(dir);
- result = rmdir(dirname);
+ if (rc == 0) {
+ /* remove the now empty directory */
+ if (poll_cancel_action(parm->path)) {
+ rc = OPRC_CANCELLED;
+ } else {
+ rc = rmdir(parm->path);
+ }
}
- return result;
+ return rc;
}
-
/* share code for file and directory deletion, saves space */
-static bool delete_file_dir(void)
+static int delete_file_dir(void)
{
- char file_to_delete[MAX_PATH];
- strcpy(file_to_delete, selected_file);
+ if (confirm_delete(selected_file) != YESNO_YES) {
+ return 1;
+ }
- const char *lines[]={
- ID2P(LANG_REALLY_DELETE),
- file_to_delete
- };
- const char *yes_lines[]={
- ID2P(LANG_DELETING),
- file_to_delete
- };
+ clear_display(true);
+ splash(HZ/2, str(LANG_DELETING));
- const struct text_message message={lines, 2};
- const struct text_message yes_message={yes_lines, 2};
+ int rc = -1;
- if(gui_syncyesno_run(&message, &yes_message, NULL)!=YESNO_YES)
- return false;
+ if (selected_file_attr & ATTR_DIRECTORY) { /* true if directory */
+ struct dirrecurse_params parm;
+ parm.append = strlcpy(parm.path, selected_file, sizeof (parm.path));
- splash(0, str(LANG_DELETING));
+ if (parm.append < sizeof (parm.path)) {
+ cpu_boost(true);
+ rc = remove_dir(&parm);
+ cpu_boost(false);
+ }
+ } else {
+ rc = remove(selected_file);
+ }
- int res;
- if (selected_file_attr & ATTR_DIRECTORY) /* true if directory */
- {
- char pathname[MAX_PATH]; /* space to go deep */
- cpu_boost(true);
- strlcpy(pathname, file_to_delete, sizeof(pathname));
- res = remove_dir(pathname, sizeof(pathname));
- cpu_boost(false);
+ if (rc < OPRC_SUCCESS) {
+ splash_failed(LANG_DELETE);
+ } else if (rc == OPRC_CANCELLED) {
+ splash_cancelled();
}
- else
- res = remove(file_to_delete);
- if (!res)
+ if (rc != OPRC_NOOP) {
+ /* Could have failed after some but not all needed changes; reload */
onplay_result = ONPLAY_RELOAD_DIR;
+ }
- return (res == 0);
+ return 1;
}
-static bool rename_file(void)
+static int rename_file(void)
{
+ int rc = -1;
char newname[MAX_PATH];
- char* ptr = strrchr(selected_file, '/') + 1;
- int pathlen = (ptr - selected_file);
- strlcpy(newname, selected_file, sizeof(newname));
- if (!kbd_input(newname + pathlen, (sizeof newname)-pathlen)) {
- if (!strlen(newname + pathlen) ||
- (rename(selected_file, newname) < 0)) {
- cond_talk_ids_fq(LANG_RENAME, LANG_FAILED);
- splashf(HZ*2, "%s %s", str(LANG_RENAME), str(LANG_FAILED));
+ const char *oldbase, *selection = selected_file;
+
+ path_basename(selection, &oldbase);
+ size_t pathlen = oldbase - selection;
+ char *newbase = newname + pathlen;
+
+ if (strlcpy(newname, selection, sizeof (newname)) >= sizeof (newname)) {
+ /* Too long */
+ } else if (kbd_input(newbase, sizeof (newname) - pathlen) < 0) {
+ rc = OPRC_CANCELLED;
+ } else if (!strcmp(oldbase, newbase)) {
+ rc = OPRC_NOOP; /* No change at all */
+ } else if (check_new_name(newbase)) {
+ switch (relate(selection, newname))
+ {
+ case RELATE_DIFFERENT:
+ if (file_exists(newname)) {
+ break; /* don't overwrite */
+ }
+ /* Fall-through */
+ case RELATE_SAME:
+ rc = rename(selection, newname);
+ break;
+ case RELATE_PREFIX:
+ default:
+ break;
}
- else
- onplay_result = ONPLAY_RELOAD_DIR;
}
- return false;
+ if (rc < OPRC_SUCCESS) {
+ splash_failed(LANG_RENAME);
+ } else if (rc == OPRC_CANCELLED) {
+ /* splash_cancelled(); kbd_input() splashes it */
+ } else if (rc == OPRC_SUCCESS) {
+ onplay_result = ONPLAY_RELOAD_DIR;
+ }
+
+ return 1;
}
-static bool create_dir(void)
+static int create_dir(void)
{
+ int rc = -1;
char dirname[MAX_PATH];
- char *cwd;
- int rc;
- int pathlen;
-
- cwd = getcwd(NULL, 0);
- memset(dirname, 0, sizeof dirname);
-
- snprintf(dirname, sizeof dirname, "%s/", cwd[1] ? cwd : "");
-
- pathlen = strlen(dirname);
- rc = kbd_input(dirname + pathlen, (sizeof dirname)-pathlen);
- if (rc < 0)
- return false;
+ size_t pathlen = path_append(dirname, getcwd(NULL, 0), PA_SEP_HARD,
+ sizeof (dirname));
+ char *basename = dirname + pathlen;
+
+ if (pathlen >= sizeof (dirname)) {
+ /* Too long */
+ } else if (kbd_input(basename, sizeof (dirname) - pathlen) < 0) {
+ rc = OPRC_CANCELLED;
+ } else if (check_new_name(basename)) {
+ rc = mkdir(dirname);
+ }
- rc = mkdir(dirname);
- if (rc < 0) {
- cond_talk_ids_fq(LANG_CREATE_DIR, LANG_FAILED);
- splashf(HZ, (unsigned char *)"%s %s", str(LANG_CREATE_DIR),
- str(LANG_FAILED));
- } else {
+ if (rc < OPRC_SUCCESS) {
+ splash_failed(LANG_CREATE_DIR);
+ } else if (rc == OPRC_CANCELLED) {
+ /* splash_cancelled(); kbd_input() splashes it */
+ } else if (rc == OPRC_SUCCESS) {
onplay_result = ONPLAY_RELOAD_DIR;
}
- return true;
+ return 1;
}
-/* Store the current selection in the clipboard */
-static bool clipboard_clip(bool copy)
+/* Paste a file */
+static int clipboard_pastefile(const char *src, const char *target,
+ unsigned int flags)
{
- clipboard_selection[0] = 0;
- strlcpy(clipboard_selection, selected_file, sizeof(clipboard_selection));
- clipboard_selection_attr = selected_file_attr;
- clipboard_is_copy = copy;
-
- return true;
-}
+ int rc = -1;
+
+ while (!(flags & (PASTE_COPY | PASTE_EXDEV))) {
+ if ((flags & PASTE_OVERWRITE) || !file_exists(target)) {
+ /* Rename and possibly overwrite the file */
+ if (poll_cancel_action(src)) {
+ rc = OPRC_CANCELLED;
+ } else {
+ rc = rename(src, target);
+ }
-static bool clipboard_cut(void)
-{
- return clipboard_clip(false);
-}
+ #ifdef HAVE_MULTIVOLUME
+ if (rc < 0 && errno == EXDEV) {
+ /* Failed because cross volume rename doesn't work; force
+ a move instead */
+ flags |= PASTE_EXDEV;
+ break;
+ }
+ #endif /* HAVE_MULTIVOLUME */
+ }
-static bool clipboard_copy(void)
-{
- return clipboard_clip(true);
-}
+ return rc;
+ }
-/* Paste a file to a new directory. Will overwrite always. */
-static bool clipboard_pastefile(const char *src, const char *target, bool copy)
-{
- int src_fd, target_fd;
+ /* See if we can get the plugin buffer for the file copy buffer */
size_t buffersize;
- ssize_t size, bytesread, byteswritten;
- char *buffer;
- bool result = false;
-
- if (copy) {
- /* See if we can get the plugin buffer for the file copy buffer */
- buffer = (char *) plugin_get_buffer(&buffersize);
- if (buffer == NULL || buffersize < 512) {
- /* Not large enough, try for a disk sector worth of stack
- instead */
- buffersize = 512;
- buffer = (char *) __builtin_alloca(buffersize);
- }
+ char *buffer = (char *) plugin_get_buffer(&buffersize);
+ if (buffer == NULL || buffersize < 512) {
+ /* Not large enough, try for a disk sector worth of stack
+ instead */
+ buffersize = 512;
+ buffer = (char *)alloca(buffersize);
+ }
- if (buffer == NULL) {
- return false;
- }
+ if (buffer == NULL) {
+ return -1;
+ }
- buffersize &= ~0x1ff; /* Round buffer size to multiple of sector
- size */
+ buffersize &= ~0x1ff; /* Round buffer size to multiple of sector
+ size */
- src_fd = open(src, O_RDONLY);
+ int src_fd = open(src, O_RDONLY);
+ if (src_fd >= 0) {
+ int oflag = O_WRONLY|O_CREAT;
- if (src_fd >= 0) {
- target_fd = creat(target, 0666);
+ if (!(flags & PASTE_OVERWRITE)) {
+ oflag |= O_EXCL;
+ }
- if (target_fd >= 0) {
- result = true;
+ int target_fd = open(target, oflag, 0666);
+ if (target_fd >= 0) {
+ off_t total_size = 0;
+ off_t next_cancel_test = 0; /* No excessive button polling */
- size = filesize(src_fd);
+ rc = OPRC_SUCCESS;
- if (size == -1) {
- result = false;
+ while (rc == OPRC_SUCCESS) {
+ if (total_size >= next_cancel_test) {
+ next_cancel_test = total_size + 0x10000;
+ if (poll_cancel_action(src)) {
+ rc = OPRC_CANCELLED;
+ break;
+ }
}
- while(size > 0) {
- bytesread = read(src_fd, buffer, buffersize);
-
- if (bytesread == -1) {
- result = false;
- break;
+ ssize_t bytesread = read(src_fd, buffer, buffersize);
+ if (bytesread <= 0) {
+ if (bytesread < 0) {
+ rc = -1;
}
+ /* else eof on buffer boundary; nothing to write */
+ break;
+ }
- size -= bytesread;
-
- while(bytesread > 0) {
- byteswritten = write(target_fd, buffer, bytesread);
-
- if (byteswritten < 0) {
- result = false;
- size = 0;
- break;
- }
-
- bytesread -= byteswritten;
- draw_slider();
- }
+ ssize_t byteswritten = write(target_fd, buffer, bytesread);
+ if (byteswritten < bytesread) {
+ /* Some I/O error */
+ rc = -1;
+ break;
}
- close(target_fd);
+ total_size += byteswritten;
- /* Copy failed. Cleanup. */
- if (!result) {
- remove(target);
+ if (bytesread < (ssize_t)buffersize) {
+ /* EOF with trailing bytes */
+ break;
}
}
- close(src_fd);
- }
- } else {
- result = rename(src, target) == 0;
-#ifdef HAVE_MULTIVOLUME
- if (!result) {
- if (errno == EXDEV) {
- /* Failed because cross volume rename doesn't work. Copy
- instead */
- result = clipboard_pastefile(src, target, true);
-
- if (result) {
- result = remove(src) == 0;
- }
+ if (rc == OPRC_SUCCESS) {
+ /* If overwriting, set the correct length if original was
+ longer */
+ rc = ftruncate(target_fd, total_size);
+ }
+
+ close(target_fd);
+
+ if (rc != OPRC_SUCCESS) {
+ /* Copy failed. Cleanup. */
+ remove(target);
}
}
-#endif
+
+ close(src_fd);
}
- return result;
+ if (rc == OPRC_SUCCESS && !(flags & PASTE_COPY)) {
+ /* Remove the source file */
+ rc = remove(src);
+ }
+
+ return rc;
}
-/* Paste a directory to a new location. Designed to be called by
- clipboard_paste */
-static bool clipboard_pastedirectory(char *src, int srclen, char *target,
- int targetlen, bool copy)
+/* Paste a directory */
+static int clipboard_pastedirectory(struct dirrecurse_params *src,
+ struct dirrecurse_params *target,
+ unsigned int flags)
{
- DIR *srcdir;
- int srcdirlen = strlen(src);
- int targetdirlen = strlen(target);
- bool result = true;
-
- if (!file_exists(target)) {
- if (!copy) {
- /* Just move the directory */
- result = rename(src, target) == 0;
+ int rc = -1;
+
+ while (!(flags & (PASTE_COPY | PASTE_EXDEV))) {
+ if ((flags & PASTE_OVERWRITE) || !file_exists(target->path)) {
+ /* Just try to move the directory */
+ if (poll_cancel_action(src->path)) {
+ rc = OPRC_CANCELLED;
+ } else {
+ rc = rename(src->path, target->path);
+ }
-#ifdef HAVE_MULTIVOLUME
- if (!result && errno == EXDEV) {
- /* Try a copy as we're going across devices */
- result = clipboard_pastedirectory(src, srclen, target,
- targetlen, true);
-
- /* If it worked, remove the source directory */
- if (result) {
- remove_dir(src, srclen);
+ if (rc < 0) {
+ int errnum = errno;
+ if (errnum == ENOTEMPTY && (flags & PASTE_OVERWRITE)) {
+ /* Directory is not empty thus rename() will not do a quick
+ overwrite */
+ break;
}
+ #ifdef HAVE_MULTIVOLUME
+ else if (errnum == EXDEV) {
+ /* Failed because cross volume rename doesn't work; force
+ a move instead */
+ flags |= PASTE_EXDEV;
+ break;
+ }
+ #endif /* HAVE_MULTIVOLUME */
}
-#endif
- return result;
- } else {
- /* Make a directory to copy things to */
- result = mkdir(target) == 0;
}
- }
- /* Check if something went wrong already */
- if (!result) {
- return result;
+ return rc;
}
- srcdir = opendir(src);
- if (!srcdir) {
- return false;
+ DIR *srcdir = opendir(src->path);
+
+ if (srcdir) {
+ /* Make a directory to copy things to */
+ rc = mkdir(target->path);
+ if (rc < 0 && errno == EEXIST && (flags & PASTE_OVERWRITE)) {
+ /* Exists and overwrite was approved */
+ rc = OPRC_SUCCESS;
+ }
}
- /* This loop will exit as soon as there's a problem */
- while(result)
- {
- struct dirent* entry;
- /* walk through the directory content */
- entry = readdir(srcdir);
- if (!entry)
+ size_t srcap = src->append, targetap = target->append;
+
+ /* Walk through the directory content; this loop will exit as soon as
+ there's a problem */
+ while (rc == OPRC_SUCCESS) {
+ errno = 0; /* Distinguish failure from eod */
+ struct dirent *entry = readdir(srcdir);
+ if (!entry) {
+ if (errno) {
+ rc = -1;
+ }
break;
+ }
struct dirinfo info = dir_get_info(srcdir, entry);
- /* append name to current directory */
- snprintf(src+srcdirlen, srclen-srcdirlen, "/%s", entry->d_name);
- snprintf(target+targetdirlen, targetlen-targetdirlen, "/%s",
- entry->d_name);
+ if ((info.attribute & ATTR_DIRECTORY) &&
+ is_dotdir_name(entry->d_name)) {
+ continue; /* Skip these */
+ }
- DEBUGF("Copy %s to %s\n", src, target);
+ /* Append names to current directories */
+ src->append = srcap +
+ path_append(&src->path[srcap], PA_SEP_HARD, entry->d_name,
+ sizeof(src->path) - srcap);
- if (info.attribute & ATTR_DIRECTORY)
- { /* copy/move a subdirectory */
- if (!strcmp((char *)entry->d_name, ".") ||
- !strcmp((char *)entry->d_name, ".."))
- continue; /* skip these */
+ target->append = targetap +
+ path_append(&target->path[targetap], PA_SEP_HARD, entry->d_name,
+ sizeof (target->path) - targetap);
- result = clipboard_pastedirectory(src, srclen, target, targetlen,
- copy); /* recursion */
+ if (src->append >= sizeof (src->path) ||
+ target->append >= sizeof (target->path)) {
+ rc = -1; /* No space left in buffer */
+ break;
}
- else
- { /* copy/move a file */
- draw_slider();
- result = clipboard_pastefile(src, target, copy);
+
+ if (poll_cancel_action(src->path)) {
+ rc = OPRC_CANCELLED;
+ break;
+ }
+
+ DEBUGF("Copy %s to %s\n", src->path, target->path);
+
+ if (info.attribute & ATTR_DIRECTORY) {
+ /* Copy/move a subdirectory */
+ rc = clipboard_pastedirectory(src, target, flags); /* recursion */
+ } else {
+ /* Copy/move a file */
+ rc = clipboard_pastefile(src->path, target->path, flags);
}
+
+ /* Remove basenames we added above */
+ src->path[srcap] = target->path[targetap] = '\0';
+ }
+
+ if (rc == OPRC_SUCCESS && !(flags & PASTE_COPY)) {
+ /* Remove the now empty directory */
+ rc = rmdir(src->path);
}
closedir(srcdir);
+ return rc;
+}
- if (result) {
- src[srcdirlen] = '\0'; /* terminate to original length */
- target[targetdirlen] = '\0'; /* terminate to original length */
- }
+static bool clipboard_cut(void)
+{
+ return clipboard_clip(&clipboard, selected_file, selected_file_attr,
+ PASTE_CUT);
+}
- return result;
+static bool clipboard_copy(void)
+{
+ return clipboard_clip(&clipboard, selected_file, selected_file_attr,
+ PASTE_COPY);
}
/* Paste the clipboard to the current directory */
-static bool clipboard_paste(void)
+static int clipboard_paste(void)
{
- char target[MAX_PATH];
- char *cwd, *nameptr;
- bool success;
+ if (!clipboard.path[0])
+ return 1;
- static const char *lines[]={ID2P(LANG_REALLY_OVERWRITE)};
- static const struct text_message message={lines, 1};
+ int rc = -1;
- /* Get the name of the current directory */
- cwd = getcwd(NULL, 0);
+ struct dirrecurse_params src, target;
+ unsigned int flags = clipboard.flags;
/* Figure out the name of the selection */
- nameptr = strrchr(clipboard_selection, '/');
+ const char *nameptr;
+ path_basename(clipboard.path, &nameptr);
/* Final target is current directory plus name of selection */
- snprintf(target, sizeof(target), "%s%s", cwd[1] ? cwd : "", nameptr);
-
- /* If the target existed but they choose not to overwite, exit */
- if (file_exists(target) &&
- (gui_syncyesno_run(&message, NULL, NULL) == YESNO_NO)) {
- return false;
- }
+ target.append = path_append(target.path, getcwd(NULL, 0),
+ nameptr, sizeof (target.path));
- if (clipboard_is_copy) {
- splash(0, ID2P(LANG_COPYING));
- }
- else
+ switch (target.append < sizeof (target.path) ?
+ relate(clipboard.path, target.path) : -1)
{
- splash(0, ID2P(LANG_MOVING));
- }
+ case RELATE_SAME:
+ rc = OPRC_NOOP;
+ break;
+
+ case RELATE_DIFFERENT:
+ if (file_exists(target.path)) {
+ /* If user chooses not to overwrite, cancel */
+ if (confirm_overwrite() == YESNO_NO) {
+ rc = OPRC_NOOVERWRT;
+ break;
+ }
- /* Now figure out what we're doing */
- cpu_boost(true);
- if (clipboard_selection_attr & ATTR_DIRECTORY) {
- /* Recursion. Set up external stack */
- char srcpath[MAX_PATH];
- char targetpath[MAX_PATH];
- if (!strncmp(clipboard_selection, target, strlen(clipboard_selection)))
- {
- /* Do not allow the user to paste a directory into a dir they are
- copying */
- success = 0;
+ flags |= PASTE_OVERWRITE;
}
- else
- {
- strlcpy(srcpath, clipboard_selection, sizeof(srcpath));
- strlcpy(targetpath, target, sizeof(targetpath));
- success = clipboard_pastedirectory(srcpath, sizeof(srcpath),
- target, sizeof(targetpath), clipboard_is_copy);
+ clear_display(true);
+ splash(HZ/2, (flags & PASTE_COPY) ? ID2P(LANG_COPYING) :
+ ID2P(LANG_MOVING));
- if (success && !clipboard_is_copy)
- {
- strlcpy(srcpath, clipboard_selection, sizeof(srcpath));
- remove_dir(srcpath, sizeof(srcpath));
+ /* Now figure out what we're doing */
+ cpu_boost(true);
+
+ if (clipboard.attr & ATTR_DIRECTORY) {
+ /* Copy or move a subdirectory */
+ src.append = strlcpy(src.path, clipboard.path,
+ sizeof (src.path));
+ if (src.append < sizeof (src.path)) {
+ rc = clipboard_pastedirectory(&src, &target, flags);
}
+ } else {
+ /* Copy or move a file */
+ rc = clipboard_pastefile(clipboard.path, target.path, flags);
}
- } else {
- success = clipboard_pastefile(clipboard_selection, target,
- clipboard_is_copy);
+
+ cpu_boost(false);
+ break;
+
+ case RELATE_PREFIX:
+ default: /* Some other relation / failure */
+ break;
}
- cpu_boost(false);
- /* Did it work? */
- if (success) {
- /* Reset everything */
- clipboard_selection[0] = 0;
- clipboard_selection_attr = 0;
- clipboard_is_copy = false;
+ clear_display(true);
- /* Force reload of the current directory */
+ switch (rc)
+ {
+ case OPRC_CANCELLED:
+ splash_cancelled();
+ case OPRC_SUCCESS:
onplay_result = ONPLAY_RELOAD_DIR;
- } else {
- cond_talk_ids_fq(LANG_PASTE, LANG_FAILED);
- splashf(HZ, (unsigned char *)"%s %s", str(LANG_PASTE),
- str(LANG_FAILED));
+ case OPRC_NOOP:
+ clipboard_clear_selection(&clipboard);
+ case OPRC_NOOVERWRT:
+ break;
+ default:
+ if (rc < OPRC_SUCCESS) {
+ splash_failed(LANG_PASTE);
+ onplay_result = ONPLAY_RELOAD_DIR;
+ }
}
- return true;
+ return 1;
}
#ifdef HAVE_TAGCACHE
@@ -1094,15 +1288,12 @@ static int clipboard_callback(int action,const struct menu_item_ex *this_item)
{
case ACTION_REQUEST_MENUITEM:
#ifdef HAVE_MULTIVOLUME
- if ((selected_file_attr & FAT_ATTR_VOLUME) &&
- (this_item == &rename_file_item ||
- this_item == &delete_dir_item ||
- this_item == &clipboard_cut_item) )
- return ACTION_EXIT_MENUITEM;
/* no rename+delete for volumes */
if ((selected_file_attr & ATTR_VOLUME) &&
- (this_item == &delete_file_item ||
- this_item == &list_viewers_item))
+ (this_item == &rename_file_item ||
+ this_item == &delete_dir_item ||
+ this_item == &clipboard_cut_item ||
+ this_item == &list_viewers_item))
return ACTION_EXIT_MENUITEM;
#endif
#ifdef HAVE_TAGCACHE
@@ -1117,7 +1308,7 @@ static int clipboard_callback(int action,const struct menu_item_ex *this_item)
#endif
if (this_item == &clipboard_paste_item)
{ /* visible if there is something to paste */
- return (clipboard_selection[0] != 0) ?
+ return (clipboard.path[0] != 0) ?
action : ACTION_EXIT_MENUITEM;
}
else if (this_item == &create_dir_item)
@@ -1232,8 +1423,7 @@ static bool delete_item(void)
{
#ifdef HAVE_MULTIVOLUME
/* no delete for volumes */
- if ((selected_file_attr & FAT_ATTR_VOLUME) ||
- (selected_file_attr & ATTR_VOLUME))
+ if (selected_file_attr & ATTR_VOLUME)
return false;
#endif
return delete_file_dir();
diff --git a/apps/playlist.c b/apps/playlist.c
index 43aa97790b..db93344ef1 100755
--- a/apps/playlist.c
+++ b/apps/playlist.c
@@ -86,7 +86,7 @@
#include "screens.h"
#include "core_alloc.h"
#include "misc.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "button.h"
#include "filetree.h"
#include "abrepeat.h"
@@ -107,6 +107,8 @@
#include "panic.h"
#include "logdiskf.h"
+#undef HAVE_DIRCACHE
+
#define PLAYLIST_CONTROL_FILE_VERSION 2
/*
@@ -180,8 +182,8 @@ static int get_next_directory(char *dir);
static int get_next_dir(char *dir, bool is_forward);
static int get_previous_directory(char *dir);
static int check_subdir_for_music(char *dir, const char *subdir, bool recurse);
-static int format_track_path(char *dest, char *src, int buf_length, int max,
- const char *dir);
+static ssize_t format_track_path(char *dest, char *src, int buf_length,
+ const char *dir);
static void display_playlist_count(int count, const unsigned char *fmt,
bool final);
static void display_buffer_full(void);
@@ -526,7 +528,7 @@ static int add_indices_to_playlist(struct playlist_info* playlist,
int result = 0;
/* get emergency buffer so we don't fail horribly */
if (!buflen)
- buffer = __builtin_alloca((buflen = 64));
+ buffer = alloca((buflen = 64));
if(-1 == playlist->fd)
playlist->fd = open_utf8(playlist->filename, O_RDONLY);
@@ -1429,7 +1431,7 @@ static int get_filename(struct playlist_info* playlist, int index, int seek,
strlcpy(dir_buf, playlist->filename, playlist->dirlen);
- return (format_track_path(buf, tmp_buf, buf_length, max, dir_buf));
+ return format_track_path(buf, tmp_buf, buf_length, dir_buf);
}
static int get_next_directory(char *dir){
@@ -1629,7 +1631,7 @@ static int get_next_dir(char *dir, bool is_forward)
static int check_subdir_for_music(char *dir, const char *subdir, bool recurse)
{
int result = -1;
- int dirlen = strlen(dir);
+ size_t dirlen = strlen(dir);
int num_files = 0;
int i;
struct entry *files;
@@ -1637,12 +1639,11 @@ static int check_subdir_for_music(char *dir, const char *subdir, bool recurse)
bool has_subdir = false;
struct tree_context* tc = tree_get_context();
- snprintf(
- dir + dirlen, MAX_PATH - dirlen,
- /* only add a trailing slash if we need one */
- dirlen && dir[dirlen - 1] == '/' ? "%s" : "/%s",
- subdir
- );
+ if (path_append(dir + dirlen, PA_SEP_HARD, subdir, MAX_PATH - dirlen) >=
+ MAX_PATH - dirlen)
+ {
+ return 0;
+ }
if (ft_load(tc, dir) < 0)
{
@@ -1695,7 +1696,7 @@ static int check_subdir_for_music(char *dir, const char *subdir, bool recurse)
}
else
{
- strcpy(dir, "/");
+ strcpy(dir, PATH_ROOTSTR);
}
/* we now need to reload our current directory */
@@ -1708,79 +1709,31 @@ static int check_subdir_for_music(char *dir, const char *subdir, bool recurse)
/*
* Returns absolute path of track
*/
-static int format_track_path(char *dest, char *src, int buf_length, int max,
- const char *dir)
+static ssize_t format_track_path(char *dest, char *src, int buf_length,
+ const char *dir)
{
- int i = 0;
- int j;
- char *temp_ptr;
-
- /* Look for the end of the string */
- while((i < max) &&
- (src[i] != '\n') &&
- (src[i] != '\r') &&
- (src[i] != '\0'))
- i++;
-
- /* Now work back killing white space */
- while((i > 0) &&
- ((src[i-1] == ' ') ||
- (src[i-1] == '\t')))
- i--;
+ size_t len;
- /* Zero-terminate the file name */
- src[i]=0;
+ /* strip whitespace at beginning and end */
+ len = path_trim_whitespace(src, (const char **)&src);
+ src[len] = '\0';
/* replace backslashes with forward slashes */
- for ( j=0; j<i; j++ )
- if ( src[j] == '\\' )
- src[j] = '/';
+ path_correct_separators(src, src);
- if('/' == src[0])
- {
- strlcpy(dest, src, buf_length);
- }
- else
- {
- /* handle dos style drive letter */
- if (':' == src[1])
- strlcpy(dest, &src[2], buf_length);
- else if (!strncmp(src, "../", 3))
- {
- /* handle relative paths */
- i=3;
- while(!strncmp(&src[i], "../", 3))
- i += 3;
- for (j=0; j<i/3; j++) {
- temp_ptr = strrchr(dir, '/');
- if (temp_ptr)
- *temp_ptr = '\0';
- else
- break;
- }
- snprintf(dest, buf_length, "%s/%s", dir, &src[i]);
- }
- else if ( '.' == src[0] && '/' == src[1] ) {
- snprintf(dest, buf_length, "%s/%s", dir, &src[2]);
- }
- else {
- snprintf(dest, buf_length, "%s/%s", dir, src);
- }
- }
-#ifdef HAVE_MULTIVOLUME
+ /* handle DOS style drive letter and parse non-greedily so that:
+ * 1) "c:/foo" becomes "/foo" and the result is absolute
+ * 2) "c:foo becomes "foo" and the result is relative
+ * This is how Windows seems to handle it except drive letters are of no
+ * meaning here. */
+ path_strip_drive(src, (const char **)&src, false);
- char vol_string[VOL_ENUM_POS + 8];
- snprintf(vol_string, sizeof(vol_string), "/"VOL_NAMES, 1);
+ /* prepends directory only if src is relative */
+ len = path_append(dest, *dir ? dir : PATH_ROOTSTR, src, buf_length);
+ if (len >= (size_t)buf_length)
+ return -1; /* buffer too small */
- /*check if the playlist is on a external card, and correct path if needed */
- if(strstr(dir, vol_string) && (strstr(dest, vol_string) == NULL)){
- char temp[buf_length];
- strlcpy(temp, dest, buf_length);
- snprintf(dest, buf_length, "%s%s", vol_string, temp);
- }
-#endif
-
- return 0;
+ return len;
}
/*
@@ -3113,8 +3066,7 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam
{
int fd;
int max;
- char *temp_ptr;
- const char *dir;
+ char *dir;
unsigned char *count_str;
char temp_buf[MAX_PATH+1];
char trackname[MAX_PATH+1];
@@ -3139,13 +3091,8 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam
}
/* we need the directory name for formatting purposes */
- dir = filename;
-
- temp_ptr = strrchr(filename+1,'/');
- if (temp_ptr)
- *temp_ptr = 0;
- else
- dir = "/";
+ size_t dirlen = path_dirname(filename, (const char **)&dir);
+ dir = strmemdupa(dir, dirlen);
if (queue)
count_str = ID2P(LANG_PLAYLIST_QUEUE_COUNT);
@@ -3183,8 +3130,8 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam
/* we need to format so that relative paths are correctly
handled */
- if (format_track_path(trackname, temp_buf, sizeof(trackname), max,
- dir) < 0)
+ if (format_track_path(trackname, temp_buf, sizeof(trackname),
+ dir) < 0)
{
result = -1;
break;
@@ -3223,9 +3170,6 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam
close(fd);
- if (temp_ptr)
- *temp_ptr = '/';
-
sync_control(playlist, false);
cpu_boost(false);
@@ -3537,9 +3481,9 @@ int playlist_save(struct playlist_info* playlist, char *filename,
char path[MAX_PATH+1];
char tmp_buf[MAX_PATH+1];
int result = 0;
- bool overwrite_current = false;
int *seek_buf;
bool reparse;
+ ssize_t pathlen;
ALIGN_BUFFER(temp_buffer, temp_buffer_size, sizeof(int));
seek_buf = temp_buffer;
@@ -3557,22 +3501,18 @@ int playlist_save(struct playlist_info* playlist, char *filename,
return -1;
/* use current working directory as base for pathname */
- if (format_track_path(path, filename, sizeof(tmp_buf),
- strlen(filename)+1, "/") < 0)
+ pathlen = format_track_path(path, filename, sizeof(path), PATH_ROOTSTR);
+ if (pathlen < 0)
+ return -1;
+
+ /* Use temporary pathname and overwrite/rename later */
+ if (strlcat(path, "_temp", sizeof(path)) >= sizeof (path))
return -1;
/* can ignore volatile here, because core_get_data() is called later */
char* old_buffer = (char*)playlist->buffer;
size_t old_buffer_size = playlist->buffer_size;
- if (!strncmp(playlist->filename, path, strlen(path)))
- {
- /* Attempting to overwrite current playlist file.
- * use temporary pathname and overwrite later */
- strlcat(path, "_temp", sizeof(path));
- overwrite_current = true;
- }
-
if (is_m3u8(path))
{
fd = open_utf8(path, O_CREAT|O_WRONLY|O_TRUNC);
@@ -3621,8 +3561,8 @@ int playlist_save(struct playlist_info* playlist, char *filename,
break;
}
- if (overwrite_current && !reparse)
- seek_buf[count] = lseek(fd, 0, SEEK_CUR);
+ if (!reparse)
+ seek_buf[count] = filesize(fd);
if (fdprintf(fd, "%s\n", tmp_buf) < 0)
{
@@ -3647,57 +3587,61 @@ int playlist_save(struct playlist_info* playlist, char *filename,
index = (index+1)%playlist->amount;
}
- display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT), true);
-
close(fd);
+ fd = -1;
- if (overwrite_current && result >= 0)
+ display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT), true);
+
+ if (result >= 0)
{
- result = -1;
+ strmemcpy(tmp_buf, path, pathlen); /* remove "_temp" */
mutex_lock(playlist->control_mutex);
- /* Replace the current playlist with the new one and update indices */
- close(playlist->fd);
- playlist->fd = -1;
- if (remove(playlist->filename) >= 0)
+ if (!rename(path, tmp_buf))
{
- if (rename(path, playlist->filename) >= 0)
+ fd = open_utf8(tmp_buf, O_RDONLY);
+ if (fsamefile(fd, playlist->fd) > 0)
{
- playlist->fd = open_utf8(playlist->filename, O_RDONLY);
- if (playlist->fd >= 0)
+ /* Replace the current playlist with the new one and update
+ indices */
+ close(playlist->fd);
+ playlist->fd = fd;
+ fd = -1;
+
+ if (!reparse)
{
- if (!reparse)
+ index = playlist->first_index;
+ for (i=0, count=0; i<playlist->amount; i++)
{
- index = playlist->first_index;
- for (i=0, count=0; i<playlist->amount; i++)
+ if (!(playlist->indices[index] & PLAYLIST_QUEUE_MASK))
{
- if (!(playlist->indices[index] & PLAYLIST_QUEUE_MASK))
- {
- playlist->indices[index] = seek_buf[count];
- count++;
- }
- index = (index+1)%playlist->amount;
+ playlist->indices[index] = seek_buf[count];
+ count++;
}
+ index = (index+1)%playlist->amount;
}
- else
- {
- NOTEF("reparsing current playlist (slow)");
- playlist->amount = 0;
- add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size);
- }
- /* we need to recreate control because inserted tracks are
- now part of the playlist and shuffle has been
- invalidated */
- result = recreate_control(playlist);
}
- }
- }
+ else
+ {
+ NOTEF("reparsing current playlist (slow)");
+ playlist->amount = 0;
+ add_indices_to_playlist(playlist, temp_buffer,
+ temp_buffer_size);
+ }
- mutex_unlock(playlist->control_mutex);
+ /* we need to recreate control because inserted tracks are
+ now part of the playlist and shuffle has been invalidated */
+ result = recreate_control(playlist);
+ }
+ }
+ mutex_unlock(playlist->control_mutex);
}
+ if (fd >= 0)
+ close(fd);
+
cpu_boost(false);
reset_old_buffer:
@@ -3759,8 +3703,12 @@ int playlist_directory_tracksearch(const char* dirname, bool recurse,
if (recurse)
{
/* recursively add directories */
- snprintf(buf, sizeof(buf), "%s/%s",
- dirname[1]? dirname: "", files[i].name);
+ if (path_append(buf, dirname, files[i].name, sizeof(buf))
+ >= sizeof(buf))
+ {
+ continue;
+ }
+
result = playlist_directory_tracksearch(buf, recurse,
callback, context);
if (result < 0)
@@ -3785,8 +3733,11 @@ int playlist_directory_tracksearch(const char* dirname, bool recurse,
}
else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
{
- snprintf(buf, sizeof(buf), "%s/%s",
- dirname[1]? dirname: "", files[i].name);
+ if (path_append(buf, dirname, files[i].name, sizeof(buf))
+ >= sizeof(buf))
+ {
+ continue;
+ }
if (callback(buf, context) != 0)
{
diff --git a/apps/playlist_catalog.c b/apps/playlist_catalog.c
index 3687681b66..5741d11258 100644
--- a/apps/playlist_catalog.c
+++ b/apps/playlist_catalog.c
@@ -32,7 +32,7 @@
#include "lang.h"
#include "list.h"
#include "misc.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "onplay.h"
#include "playlist.h"
#include "settings.h"
diff --git a/apps/plugin.c b/apps/plugin.c
index 8edc773239..8a6c577f69 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -18,6 +18,8 @@
* KIND, either express or implied.
*
****************************************************************************/
+#define DIRFUNCTIONS_DEFINED
+#define FILEFUNCTIONS_DEFINED
#include "plugin.h"
#include <ctype.h>
#include <string.h>
@@ -40,8 +42,9 @@
#include "pcmbuf.h"
#include "errno.h"
#include "diacritic.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "load_code.h"
+#include "file.h"
#if CONFIG_CHARGING
#include "power.h"
@@ -58,80 +61,119 @@
#include "usbstack/usb_hid.h"
#endif
-#if defined (SIMULATOR)
-#define PREFIX(_x_) sim_ ## _x_
-#elif defined (APPLICATION)
-#define PREFIX(_x_) app_ ## _x_
+#define WRAPPER(_x_) _x_ ## _wrapper
+
+#if (CONFIG_PLATFORM & PLATFORM_HOSTED)
+static unsigned char pluginbuf[PLUGIN_BUFFER_SIZE];
+void sim_lcd_ex_init(unsigned long (*getpixel)(int, int));
+void sim_lcd_ex_update_rect(int x, int y, int width, int height);
#else
-#define PREFIX(_x_) _x_
+extern unsigned char pluginbuf[];
+#include "bitswap.h"
#endif
-#if defined (APPLICATION)
-/* For symmetry reasons (we want app_ and sim_ to behave similarly), some
- * wrappers are needed */
-static int app_close(int fd)
+/* for actual plugins only, not for codecs */
+static int plugin_size = 0;
+static bool (*pfn_tsr_exit)(bool reenter) = NULL; /* TSR exit callback */
+static char current_plugin[MAX_PATH];
+/* NULL if no plugin is loaded, otherwise the handle that lc_open() returned */
+static void *current_plugin_handle;
+
+char *plugin_get_current_filename(void);
+
+static void* plugin_get_audio_buffer(size_t *buffer_size);
+static void plugin_release_audio_buffer(void);
+static void plugin_tsr(bool (*exit_callback)(bool));
+
+
+#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
+/* File handle leak prophylaxis */
+#include "bitarray.h"
+#include "file_internal.h" /* for MAX_OPEN_FILES */
+
+#define PCOC_WRAPPER(_x_) WRAPPER(_x_)
+
+BITARRAY_TYPE_DECLARE(plugin_check_open_close_bitmap_t, open_files_bitmap,
+ MAX_OPEN_FILES)
+
+static plugin_check_open_close_bitmap_t open_files_bitmap;
+
+static void plugin_check_open_close__enter(void)
{
- return close(fd);
+ if (!current_plugin_handle)
+ open_files_bitmap_clear(&open_files_bitmap);
}
-static ssize_t app_read(int fd, void *buf, size_t count)
+static void plugin_check_open_close__open(int fildes)
{
- return read(fd,buf,count);
+ if (fildes >= 0)
+ open_files_bitmap_set_bit(&open_files_bitmap, fildes);
}
-static off_t app_lseek(int fd, off_t offset, int whence)
+static void plugin_check_open_close__close(int fildes)
{
- return lseek(fd,offset,whence);
+ if (fildes < 0)
+ return;
+
+ if (!open_files_bitmap_test_bit(&open_files_bitmap, fildes))
+ {
+ logf("double close from plugin");
+ }
+
+ open_files_bitmap_clear_bit(&open_files_bitmap, fildes);
}
-static ssize_t app_write(int fd, const void *buf, size_t count)
+static int WRAPPER(open)(const char *path, int oflag, ...)
{
- return write(fd,buf,count);
+ int fildes = FS_PREFIX(open)(path, oflag __OPEN_MODE_ARG);
+ plugin_check_open_close__open(fildes);
+ return fildes;
}
-static int app_ftruncate(int fd, off_t length)
+static int WRAPPER(creat)(const char *path, mode_t mode)
{
- return ftruncate(fd,length);
+ int fildes = FS_PREFIX(creat)(path __CREAT_MODE_ARG);
+ plugin_check_open_close__open(fildes);
+ return fildes;
+ (void)mode;
}
-#endif
-#if defined(HAVE_PLUGIN_CHECK_OPEN_CLOSE) && (MAX_OPEN_FILES>32)
-#warning "MAX_OPEN_FILES>32, disabling plugin file open/close checking"
-#undef HAVE_PLUGIN_CHECK_OPEN_CLOSE
-#endif
+static int WRAPPER(close)(int fildes)
+{
+ int rc = FS_PREFIX(close)(fildes);
+ if (rc >= 0)
+ plugin_check_open_close__close(fildes);
-#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
-static unsigned int open_files;
-#endif
+ return rc;
+}
-#if (CONFIG_PLATFORM & PLATFORM_HOSTED)
-static unsigned char pluginbuf[PLUGIN_BUFFER_SIZE];
-void sim_lcd_ex_init(unsigned long (*getpixel)(int, int));
-void sim_lcd_ex_update_rect(int x, int y, int width, int height);
-#else
-extern unsigned char pluginbuf[];
-#include "bitswap.h"
-#endif
+static void plugin_check_open_close__exit(void)
+{
+ if (current_plugin_handle)
+ return;
-/* for actual plugins only, not for codecs */
-static int plugin_size = 0;
-static bool (*pfn_tsr_exit)(bool reenter) = NULL; /* TSR exit callback */
-static char current_plugin[MAX_PATH];
-/* NULL if no plugin is loaded, otherwise the handle that lc_open() returned */
-static void *current_plugin_handle;
+ if (open_files_bitmap_is_clear(&open_files_bitmap))
+ return;
-char *plugin_get_current_filename(void);
+ logf("Plugin '%s' leaks file handles", plugin);
-/* Some wrappers used to monitor open and close and detect leaks*/
-static int open_wrapper(const char* pathname, int flags, ...);
-#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
-static int close_wrapper(int fd);
-static int creat_wrapper(const char *pathname, mode_t mode);
-#endif
+ static const char *lines[] =
+ { ID2P(LANG_PLUGIN_ERROR), "#leak-file-handles" };
+ static const struct text_message message = { lines, 2 };
+ button_clear_queue(); /* Empty the keyboard buffer */
+ gui_syncyesno_run(&message, NULL, NULL);
-static void* plugin_get_audio_buffer(size_t *buffer_size);
-static void plugin_release_audio_buffer(void);
-static void plugin_tsr(bool (*exit_callback)(bool));
+ FOR_EACH_BITARRAY_SET_BIT(&open_files_bitmap, fildes)
+ WRAPPER(close)(fildes);
+}
+
+#else /* !HAVE_PLUGIN_CHECK_OPEN_CLOSE */
+
+#define PCOC_WRAPPER(_x_) FS_PREFIX(_x_)
+#define plugin_check_open_close__enter()
+#define plugin_check_open_close__exit()
+
+#endif /* HAVE_PLUGIN_CHECK_OPEN_CLOSE */
static const struct plugin_api rockbox_api = {
@@ -339,24 +381,16 @@ static const struct plugin_api rockbox_api = {
/* file */
open_utf8,
- (open_func)open_wrapper,
-#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
- close_wrapper,
-#else
- PREFIX(close),
-#endif
- (read_func)PREFIX(read),
- PREFIX(lseek),
-#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
- (creat_func)creat_wrapper,
-#else
- PREFIX(creat),
-#endif
- (write_func)PREFIX(write),
- PREFIX(remove),
- PREFIX(rename),
- PREFIX(ftruncate),
- filesize,
+ PCOC_WRAPPER(open),
+ PCOC_WRAPPER(creat),
+ PCOC_WRAPPER(close),
+ FS_PREFIX(read),
+ FS_PREFIX(lseek),
+ FS_PREFIX(write),
+ FS_PREFIX(remove),
+ FS_PREFIX(rename),
+ FS_PREFIX(ftruncate),
+ FS_PREFIX(filesize),
fdprintf,
read_line,
settings_parseline,
@@ -369,18 +403,18 @@ static const struct plugin_api rockbox_api = {
#endif /* USING_STORAGE_CALLBACK */
reload_directory,
create_numbered_filename,
- file_exists,
+ FS_PREFIX(file_exists),
strip_extension,
crc_32,
filetype_get_attr,
/* dir */
- (opendir_func)opendir,
- (closedir_func)closedir,
- (readdir_func)readdir,
- mkdir,
- rmdir,
- dir_exists,
+ FS_PREFIX(opendir),
+ FS_PREFIX(closedir),
+ FS_PREFIX(readdir),
+ FS_PREFIX(mkdir),
+ FS_PREFIX(rmdir),
+ FS_PREFIX(dir_exists),
dir_get_info,
/* browsing */
@@ -688,10 +722,11 @@ static const struct plugin_api rockbox_api = {
#endif
srand,
rand,
- (qsort_func)qsort,
+ (void *)qsort,
kbd_input,
get_time,
set_time,
+ gmtime_r,
#if CONFIG_RTC
mktime,
#endif
@@ -891,9 +926,7 @@ int plugin_load(const char* plugin, const void* parameter)
/* allow voice to back off if the plugin needs lots of memory */
talk_buffer_set_policy(TALK_BUFFER_LOOSE);
-#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
- open_files = 0;
-#endif
+ plugin_check_open_close__enter();
int rc = p_hdr->entry_point(parameter);
@@ -947,24 +980,7 @@ int plugin_load(const char* plugin, const void* parameter)
FOR_NB_SCREENS(i)
viewportmanager_theme_undo(i, true);
-#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
- if(open_files != 0 && !current_plugin_handle)
- {
- int fd;
- logf("Plugin '%s' leaks file handles", plugin);
-
- static const char *lines[] =
- { ID2P(LANG_PLUGIN_ERROR),
- "#leak-file-handles" };
- static const struct text_message message={ lines, 2 };
- button_clear_queue(); /* Empty the keyboard buffer */
- gui_syncyesno_run(&message, NULL, NULL);
-
- for(fd=0; fd < MAX_OPEN_FILES; fd++)
- if(open_files & (1<<fd))
- close_wrapper(fd);
- }
-#endif
+ plugin_check_open_close__exit();
if (rc == PLUGIN_ERROR)
splash(HZ*2, str(LANG_PLUGIN_ERROR));
@@ -1027,55 +1043,3 @@ char *plugin_get_current_filename(void)
{
return current_plugin;
}
-
-static int open_wrapper(const char* pathname, int flags, ...)
-{
-/* we don't have an 'open' function. it's a define. and we need
- * the real file_open, hence PREFIX() doesn't work here */
- int fd;
-#if (CONFIG_PLATFORM & PLATFORM_HOSTED)
- if (flags & O_CREAT)
- {
- va_list ap;
- va_start(ap, flags);
- fd = open(pathname, flags, va_arg(ap, unsigned int));
- va_end(ap);
- }
- else
- fd = open(pathname, flags);
-#else
- fd = file_open(pathname,flags);
-#endif
-
-#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
- if(fd >= 0)
- open_files |= 1<<fd;
-#endif
- return fd;
-}
-
-#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
-static int close_wrapper(int fd)
-{
- if((~open_files) & (1<<fd))
- {
- logf("double close from plugin");
- }
- if(fd >= 0)
- open_files &= (~(1<<fd));
-
- return PREFIX(close)(fd);
-}
-
-static int creat_wrapper(const char *pathname, mode_t mode)
-{
- (void)mode;
-
- int fd = PREFIX(creat)(pathname, mode);
-
- if(fd >= 0)
- open_files |= (1<<fd);
-
- return fd;
-}
-#endif /* HAVE_PLUGIN_CHECK_OPEN_CLOSE */
diff --git a/apps/plugin.h b/apps/plugin.h
index 8b8481b6ac..e55dcf13cb 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -75,7 +75,7 @@ void* plugin_get_buffer(size_t *buffer_size);
#include "profile.h"
#endif
#include "misc.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#if (CONFIG_CODEC == SWCODEC)
#include "pcm_mixer.h"
#include "dsp-util.h"
@@ -160,12 +160,12 @@ void* plugin_get_buffer(size_t *buffer_size);
#define PLUGIN_MAGIC 0x526F634B /* RocK */
/* increase this every time the api struct changes */
-#define PLUGIN_API_VERSION 231
+#define PLUGIN_API_VERSION 232
/* update this to latest version if a change to the api struct breaks
backwards compatibility (and please take the opportunity to sort in any
new function which are "waiting" at the end of the function table) */
-#define PLUGIN_MIN_API_VERSION 231
+#define PLUGIN_MIN_API_VERSION 232
/* plugin return codes */
/* internal returns start at 0x100 to make exit(1..255) work */
@@ -433,17 +433,17 @@ struct plugin_api {
/* file */
int (*open_utf8)(const char* pathname, int flags);
- int (*open)(const char* pathname, int flags, ...);
- int (*close)(int fd);
- ssize_t (*read)(int fd, void* buf, size_t count);
- off_t (*lseek)(int fd, off_t offset, int whence);
- int (*creat)(const char *pathname, mode_t mode);
- ssize_t (*write)(int fd, const void* buf, size_t count);
- int (*remove)(const char* pathname);
- int (*rename)(const char* path, const char* newname);
- int (*ftruncate)(int fd, off_t length);
- off_t (*filesize)(int fd);
- int (*fdprintf)(int fd, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
+ int (*open)(const char *path, int oflag, ...);
+ int (*creat)(const char *path, mode_t mode);
+ int (*close)(int fildes);
+ ssize_t (*read)(int fildes, void *buf, size_t nbyte);
+ off_t (*lseek)(int fildes, off_t offset, int whence);
+ ssize_t (*write)(int fildes, const void *buf, size_t nbyte);
+ int (*remove)(const char *path);
+ int (*rename)(const char *old, const char *new);
+ int (*ftruncate)(int fildes, off_t length);
+ off_t (*filesize)(int fildes);
+ int (*fdprintf)(int fildes, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
int (*read_line)(int fd, char* buffer, int buffer_size);
bool (*settings_parseline)(char* line, char** name, char** value);
void (*storage_sleep)(void);
@@ -457,7 +457,7 @@ struct plugin_api {
char *(*create_numbered_filename)(char *buffer, const char *path,
const char *prefix, const char *suffix,
int numberlen IF_CNFN_NUM_(, int *num));
- bool (*file_exists)(const char *file);
+ bool (*file_exists)(const char *path);
char* (*strip_extension)(char* buffer, int buffer_size, const char *filename);
uint32_t (*crc_32)(const void *src, uint32_t len, uint32_t crc32);
@@ -466,13 +466,13 @@ struct plugin_api {
/* dir */
- DIR* (*opendir)(const char* name);
- int (*closedir)(DIR* dir);
- struct dirent* (*readdir)(DIR* dir);
- int (*mkdir)(const char *name);
- int (*rmdir)(const char *name);
- bool (*dir_exists)(const char *path);
- struct dirinfo (*dir_get_info)(DIR* parent, struct dirent *entry);
+ DIR * (*opendir)(const char *dirname);
+ int (*closedir)(DIR *dirp);
+ struct dirent * (*readdir)(DIR *dirp);
+ int (*mkdir)(const char *path);
+ int (*rmdir)(const char *path);
+ bool (*dir_exists)(const char *dirname);
+ struct dirinfo (*dir_get_info)(DIR *dirp, struct dirent *entry);
/* browsing */
void (*browse_context_init)(struct browse_context *browse,
@@ -838,6 +838,7 @@ struct plugin_api {
int (*kbd_input)(char* buffer, int buflen);
struct tm* (*get_time)(void);
int (*set_time)(const struct tm *tm);
+ struct tm * (*gmtime_r)(const time_t *timep, struct tm *tm);
#if CONFIG_RTC
time_t (*mktime)(struct tm *t);
#endif
diff --git a/apps/plugins/properties.c b/apps/plugins/properties.c
index 0f3ec5c458..3115da94a4 100644
--- a/apps/plugins/properties.c
+++ b/apps/plugins/properties.c
@@ -99,13 +99,12 @@ static bool file_properties(char* selected_file)
log = human_size_log((unsigned long)info.size);
rb->snprintf(str_size, sizeof str_size, "%lu %cB",
((unsigned long)info.size) >> (log*10), human_size_prefix[log]);
+ struct tm tm;
+ rb->gmtime_r(&info.mtime, &tm);
rb->snprintf(str_date, sizeof str_date, "%04d/%02d/%02d",
- ((info.wrtdate >> 9 ) & 0x7F) + 1980, /* year */
- ((info.wrtdate >> 5 ) & 0x0F), /* month */
- ((info.wrtdate ) & 0x1F)); /* day */
- rb->snprintf(str_time, sizeof str_time, "%02d:%02d",
- ((info.wrttime >> 11) & 0x1F), /* hour */
- ((info.wrttime >> 5 ) & 0x3F)); /* minutes */
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+ rb->snprintf(str_time, sizeof str_time, "%02d:%02d:%02d",
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
num_properties = 5;
@@ -175,7 +174,10 @@ static bool _dir_properties(DPS* dps)
dirlen = rb->strlen(dps->dirname);
dir = rb->opendir(dps->dirname);
if (!dir)
+ {
+ rb->splashf(HZ*2, "%s", dps->dirname);
return false; /* open error */
+ }
/* walk through the directory content */
while(result && (0 != (entry = rb->readdir(dir))))
diff --git a/apps/radio/presets.c b/apps/radio/presets.c
index 9eab4901f1..d9a2aa9bcd 100644
--- a/apps/radio/presets.c
+++ b/apps/radio/presets.c
@@ -30,7 +30,7 @@
#include "file.h"
#include "string-extra.h"
#include "misc.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "lang.h"
#include "action.h"
#include "list.h"
diff --git a/apps/radio/radioart.c b/apps/radio/radioart.c
index 283815167a..5e1a0ad5cf 100644
--- a/apps/radio/radioart.c
+++ b/apps/radio/radioart.c
@@ -31,7 +31,7 @@
#include "file.h"
#include "kernel.h"
#include "string-extra.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "core_alloc.h"
#define MAX_RADIOART_IMAGES 10
diff --git a/apps/recorder/albumart.c b/apps/recorder/albumart.c
index 4cbabbc8ce..c561e36ae2 100644
--- a/apps/recorder/albumart.c
+++ b/apps/recorder/albumart.c
@@ -27,7 +27,7 @@
#include "buffering.h"
#include "dircache.h"
#include "misc.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "settings.h"
#include "wps.h"
diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c
index 5b341fd141..1c53c8026f 100644
--- a/apps/recorder/recording.c
+++ b/apps/recorder/recording.c
@@ -56,7 +56,7 @@
#include "timefuncs.h"
#include "debug.h"
#include "misc.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "tree.h"
#include "string.h"
#include "dir.h"
diff --git a/apps/root_menu.c b/apps/root_menu.c
index 189b2ec35c..7ec803f585 100644
--- a/apps/root_menu.c
+++ b/apps/root_menu.c
@@ -146,11 +146,10 @@ static int browser(void* param)
int i;
for (i = 0; i < NUM_VOLUMES; i++)
{
- char vol_string[VOL_ENUM_POS + 8];
+ char vol_string[VOL_MAX_LEN + 1];
if (!volume_removable(i))
continue;
- /* VOL_NAMES contains a %d */
- snprintf(vol_string, sizeof(vol_string), "/"VOL_NAMES, i);
+ get_volume_name(i, vol_string);
/* test whether we would browse the external card */
if (!volume_present(i) &&
(strstr(last_folder, vol_string)
diff --git a/apps/scrobbler.c b/apps/scrobbler.c
index b8a95f85cb..4f3693e716 100644
--- a/apps/scrobbler.c
+++ b/apps/scrobbler.c
@@ -33,7 +33,7 @@ http://www.audioscrobbler.net/wiki/Portable_Player_Logging
#include "core_alloc.h"
#include "settings.h"
#include "ata_idle_notify.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "appevents.h"
#if CONFIG_RTC
diff --git a/apps/settings.c b/apps/settings.c
index f2a923e24d..819924a421 100644
--- a/apps/settings.c
+++ b/apps/settings.c
@@ -822,6 +822,10 @@ void settings_apply(bool read_disk)
#ifdef HAVE_LCD_BITMAP
int rc;
#endif
+ CHART(">set_codepage");
+ set_codepage(global_settings.default_codepage);
+ CHART("<set_codepage");
+
sound_settings_apply();
#ifdef HAVE_DISK_STORAGE
@@ -1008,10 +1012,6 @@ void settings_apply(bool read_disk)
lcd_scroll_delay(global_settings.scroll_delay);
- CHART(">set_codepage");
- set_codepage(global_settings.default_codepage);
- CHART("<set_codepage");
-
#ifdef HAVE_PLAY_FREQ
settings_apply_play_freq(global_settings.play_frequency, false);
#endif
diff --git a/apps/settings_list.c b/apps/settings_list.c
index af83866356..53acb78d98 100644
--- a/apps/settings_list.c
+++ b/apps/settings_list.c
@@ -1724,13 +1724,13 @@ const struct settings_list settings[] = {
OFFON_SETTING(F_BANFROMQS, tagcache_autoupdate, LANG_TAGCACHE_AUTOUPDATE, false,
"tagcache_autoupdate", NULL),
#endif
- CHOICE_SETTING(0, default_codepage, LANG_DEFAULT_CODEPAGE, 0,
+ CHOICE_SETTING(F_TEMPVAR, default_codepage, LANG_DEFAULT_CODEPAGE, 0,
"default codepage",
#ifdef HAVE_LCD_BITMAP
/* The order must match with that in unicode.c */
"iso8859-1,iso8859-7,iso8859-8,cp1251,iso8859-11,cp1256,"
"iso8859-9,iso8859-2,cp1250,cp1252,sjis,gb2312,ksx1001,big5,utf-8",
- set_codepage, 15,
+ NULL, 15,
ID2P(LANG_CODEPAGE_LATIN1),
ID2P(LANG_CODEPAGE_GREEK),
ID2P(LANG_CODEPAGE_HEBREW), ID2P(LANG_CODEPAGE_CYRILLIC),
@@ -1745,7 +1745,7 @@ const struct settings_list settings[] = {
#else /* !HAVE_LCD_BITMAP */
/* The order must match with that in unicode.c */
"iso8859-1,iso8859-7,cp1251,iso8859-9,iso8859-2,cp1250,cp1252,utf-8",
- set_codepage, 8,
+ NULL, 8,
ID2P(LANG_CODEPAGE_LATIN1), ID2P(LANG_CODEPAGE_GREEK),
ID2P(LANG_CODEPAGE_CYRILLIC), ID2P(LANG_CODEPAGE_TURKISH),
ID2P(LANG_CODEPAGE_LATIN_EXTENDED),
diff --git a/apps/shortcuts.c b/apps/shortcuts.c
index a9ae8248f1..1153edd2ad 100644
--- a/apps/shortcuts.c
+++ b/apps/shortcuts.c
@@ -37,7 +37,7 @@
#include "misc.h"
#include "tree.h"
#include "splash.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "filetypes.h"
#include "shortcuts.h"
#include "onplay.h"
diff --git a/apps/tagcache.c b/apps/tagcache.c
index b7d5516e81..2b6041227b 100644
--- a/apps/tagcache.c
+++ b/apps/tagcache.c
@@ -79,7 +79,7 @@
#include "misc.h"
#include "settings.h"
#include "dir.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "structec.h"
#include "debug.h"
@@ -88,6 +88,8 @@
#include "eeprom_settings.h"
#endif
+#undef HAVE_DIRCACHE
+
#ifdef __PCTOOL__
#define yield() do { } while(0)
#define sim_sleep(timeout) do { } while(0)
@@ -2986,20 +2988,21 @@ static bool commit(void)
/* Try to steal every buffer we can :) */
if (tempbuf_size == 0)
local_allocation = true;
-
+
+#if 0 /* FIXME: How much big? dircache buffer can no longer be taken but
+ may be freed to make room and the cache resumed. --jethead71 */
#ifdef HAVE_DIRCACHE
if (tempbuf_size == 0)
{
- /* Try to steal the dircache buffer. */
- tempbuf = dircache_steal_buffer(&tempbuf_size);
- tempbuf_size &= ~0x03;
-
+ /* Shut down dircache to free its allocation. */
+ dircache_free_buffer();
if (tempbuf_size > 0)
{
dircache_buffer_stolen = true;
}
}
-#endif
+#endif
+#endif
#ifdef HAVE_TC_RAMCACHE
if (tempbuf_size == 0 && tc_stat.ramcache_allocated > 0)
@@ -4462,7 +4465,7 @@ static bool check_dir(const char *dirname, int add_files)
tc_stat.curentry = curpath;
/* Add a new entry to the temporary db file. */
- add_tagcache(curpath, (info.wrtdate << 16) | info.wrttime
+ add_tagcache(curpath, info.mtime
#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
, dir->internal_entry
#endif
@@ -4780,7 +4783,7 @@ void tagcache_shutdown(void)
/* Flush the command queue. */
run_command_queue(true);
-#ifdef HAVE_EEPROM_SETTINGS
+#if defined(HAVE_EEPROM_SETTINGS) && defined(HAVE_TC_RAMCACHE)
if (tc_stat.ramcache)
tagcache_dumpsave();
#endif
diff --git a/apps/tree.c b/apps/tree.c
index f72774fe1e..938e44d350 100644
--- a/apps/tree.c
+++ b/apps/tree.c
@@ -52,7 +52,7 @@
#include "talk.h"
#include "filetypes.h"
#include "misc.h"
-#include "filefuncs.h"
+#include "pathfuncs.h"
#include "filetree.h"
#include "tagtree.h"
#ifdef HAVE_RECORDING
@@ -1205,26 +1205,36 @@ void tree_flush(void)
#endif
#ifdef HAVE_DIRCACHE
+ int old_val = global_status.dircache_size;
+#ifdef HAVE_EEPROM_SETTINGS
+ bool savecache = false;
+#endif
+
+ if (global_settings.dircache)
{
- int old_val = global_status.dircache_size;
- if (global_settings.dircache)
- {
- if (!dircache_is_initializing())
- global_status.dircache_size = dircache_get_cache_size();
-# ifdef HAVE_EEPROM_SETTINGS
- if (firmware_settings.initialized)
- dircache_save();
-# endif
- dircache_suspend();
- }
- else
- {
- global_status.dircache_size = 0;
- }
- if (old_val != global_status.dircache_size)
- status_save();
+ dircache_suspend();
+
+ struct dircache_info info;
+ dircache_get_info(&info);
+
+ global_status.dircache_size = info.last_size;
+ #ifdef HAVE_EEPROM_SETTINGS
+ savecache = firmware_settings.initialized;
+ #endif
}
-#endif
+ else
+ {
+ global_status.dircache_size = 0;
+ }
+
+ if (old_val != global_status.dircache_size)
+ status_save();
+
+ #ifdef HAVE_EEPROM_SETTINGS
+ if (savecache)
+ dircache_save();
+ #endif
+#endif /* HAVE_DIRCACHE */
}
void tree_restore(void)
@@ -1238,15 +1248,14 @@ void tree_restore(void)
#endif
#ifdef HAVE_DIRCACHE
- remove(DIRCACHE_FILE);
- if (global_settings.dircache)
+ if (global_settings.dircache && dircache_resume() > 0)
{
/* Print "Scanning disk..." to the display. */
splash(0, str(LANG_SCANNING_DISK));
-
- dircache_build(global_status.dircache_size);
+ dircache_wait();
}
#endif
+
#ifdef HAVE_TAGCACHE
tagcache_start_scan();
#endif
diff --git a/bootloader/creativezvm.c b/bootloader/creativezvm.c
index f31f6490a4..6597448193 100644
--- a/bootloader/creativezvm.c
+++ b/bootloader/creativezvm.c
@@ -22,6 +22,7 @@
#include "../kernel-internal.h"
#include "storage.h"
#include "ata-target.h"
+#include "file_internal.h"
#include "disk.h"
#include "font.h"
#include "backlight.h"
@@ -73,6 +74,8 @@ void main(void)
ret = storage_init();
if(ret)
printf("ATA error: %d", ret);
+
+ filesystem_init();
/* If no button is held, start the OF */
if(button_read_device() == 0)
@@ -93,8 +96,6 @@ void main(void)
}
else
{
- disk_init();
-
ret = disk_mount_all();
if (ret <= 0)
error(EDISK, ret, true);
diff --git a/bootloader/gigabeat-s.c b/bootloader/gigabeat-s.c
index 3b852dbaa8..499d9c218c 100644
--- a/bootloader/gigabeat-s.c
+++ b/bootloader/gigabeat-s.c
@@ -21,13 +21,16 @@
#include "config.h"
#include "system.h"
#include <stdio.h>
+#include <errno.h>
#include "../kernel-internal.h"
#include "gcc_extensions.h"
#include "string.h"
#include "adc.h"
#include "powermgmt.h"
#include "storage.h"
+#include "file_internal.h"
#include "dir.h"
+#include "file.h"
#include "disk.h"
#include "common.h"
#include "rb-loader.h"
@@ -219,7 +222,7 @@ static void untar(int tar_fd)
/* Create the dir */
ret = mkdir(path);
- if (ret < 0 && ret != -4)
+ if (ret < 0 && errno != EEXIST)
{
printf("failed to create dir (%d)", ret);
}
@@ -233,14 +236,14 @@ static void handle_untar(void)
char buf[MAX_PATH];
char tarstring[6];
char model[5];
- struct dirent_uncached* entry;
- DIR_UNCACHED* dir;
+ struct dirent* entry;
+ DIR* dir;
int fd;
int rc;
- dir = opendir_uncached(basedir);
+ dir = opendir(basedir);
- while ((entry = readdir_uncached(dir)))
+ while ((entry = readdir(dir)))
{
if (*entry->d_name == '.')
continue;
@@ -262,7 +265,6 @@ static void handle_untar(void)
verbose = true;
printf("Found rockbox binary. Moving...");
close(fd);
- remove( BOOTDIR "/" BOOTFILE);
int ret = rename(buf, BOOTDIR "/" BOOTFILE);
printf("returned %d", ret);
sleep(HZ);
@@ -365,7 +367,7 @@ void main(void)
if(rc)
error(EATA, rc, true);
- disk_init();
+ filesystem_init();
rc = disk_mount_all();
if (rc <= 0)
diff --git a/bootloader/gigabeat.c b/bootloader/gigabeat.c
index 52f55a5cdd..4d52407655 100644
--- a/bootloader/gigabeat.c
+++ b/bootloader/gigabeat.c
@@ -30,6 +30,7 @@
#include "../kernel-internal.h"
#include "storage.h"
#include "fat.h"
+#include "file_internal.h"
#include "disk.h"
#include "font.h"
#include "adc.h"
@@ -185,7 +186,7 @@ void main(void)
error(EATA, rc, true);
}
- disk_init();
+ filesystem_init();
rc = disk_mount_all();
if (rc<=0)
diff --git a/bootloader/iaudio_coldfire.c b/bootloader/iaudio_coldfire.c
index 013e8859e8..2f31958a87 100644
--- a/bootloader/iaudio_coldfire.c
+++ b/bootloader/iaudio_coldfire.c
@@ -196,7 +196,7 @@ void main(void)
if(rc)
error(EATA, rc, true);
- disk_init();
+ filesystem_init();
rc = disk_mount_all();
if (rc<=0)
diff --git a/bootloader/imx233.c b/bootloader/imx233.c
index d2562d0676..76fdf884da 100644
--- a/bootloader/imx233.c
+++ b/bootloader/imx233.c
@@ -35,6 +35,7 @@
#include "rb-loader.h"
#include "loader_strerror.h"
#include "storage.h"
+#include "file_internal.h"
#include "disk.h"
#include "panic.h"
#include "power.h"
@@ -171,7 +172,7 @@ void main(uint32_t arg, uint32_t addr)
if(ret < 0)
error(EATA, ret, true);
- disk_init_subsystem();
+ filesystem_init();
/* NOTE: disk_mount_all to fail since we can do USB after.
* We need this order to determine the correct logical sector size */
diff --git a/bootloader/ipod.c b/bootloader/ipod.c
index b2d4532168..8580886ce4 100644
--- a/bootloader/ipod.c
+++ b/bootloader/ipod.c
@@ -33,6 +33,7 @@
#include "../kernel-internal.h"
#include "ata.h"
#include "fat.h"
+#include "file_internal.h"
#include "disk.h"
#include "font.h"
#include "adc.h"
@@ -295,7 +296,7 @@ void* main(void)
int rc;
bool haveramos;
bool button_was_held;
- struct partinfo* pinfo;
+ struct partinfo pinfo;
unsigned short* identify_info;
/* Check the button hold status as soon as possible - to
@@ -353,7 +354,8 @@ void* main(void)
printf("ATA: %d", i);
}
- disk_init();
+ filesystem_init();
+
rc = disk_mount_all();
if (rc<=0)
{
@@ -361,9 +363,9 @@ void* main(void)
fatal_error();
}
- pinfo = disk_partinfo(1);
+ disk_partinfo(1, &pinfo);
printf("Partition 1: 0x%02x %ld sectors",
- pinfo->type, pinfo->size);
+ pinfo.type, pinfo.size);
if (button_was_held || (btn==BUTTON_MENU)) {
/* If either the hold switch was on, or the Menu button was held, then
diff --git a/bootloader/ipodnano2g.c b/bootloader/ipodnano2g.c
index cdd866f71e..d0a8e0206c 100644
--- a/bootloader/ipodnano2g.c
+++ b/bootloader/ipodnano2g.c
@@ -32,6 +32,7 @@
#include "lcd.h"
#include "i2c-s5l8700.h"
#include "../kernel-internal.h"
+#include "file_internal.h"
#include "storage.h"
#include "fat.h"
#include "disk.h"
@@ -213,7 +214,8 @@ void main(void)
fatal_error();
}
- disk_init();
+ filesystem_init();
+
rc = disk_mount_all();
if (rc<=0)
{
diff --git a/bootloader/iriver_h1x0.c b/bootloader/iriver_h1x0.c
index 7f236d4510..742d0dd254 100644
--- a/bootloader/iriver_h1x0.c
+++ b/bootloader/iriver_h1x0.c
@@ -31,6 +31,7 @@
#include "scroll_engine.h"
#include "../kernel-internal.h"
#include "storage.h"
+#include "file_internal.h"
#include "usb.h"
#include "disk.h"
#include "font.h"
@@ -596,7 +597,7 @@ void main(void)
}
- disk_init();
+ filesystem_init();
rc = disk_mount_all();
if (rc<=0)
diff --git a/bootloader/iriver_h300.c b/bootloader/iriver_h300.c
index 362eb947c8..100e660a67 100644
--- a/bootloader/iriver_h300.c
+++ b/bootloader/iriver_h300.c
@@ -31,6 +31,7 @@
#include "scroll_engine.h"
#include "../kernel-internal.h"
#include "storage.h"
+#include "file_internal.h"
#include "usb.h"
#include "disk.h"
#include "font.h"
@@ -349,8 +350,7 @@ void main(void)
while(!(button_get(true) & BUTTON_REL));
}
-
- disk_init();
+ filesystem_init();
rc = disk_mount_all();
if (rc<=0)
diff --git a/bootloader/main-e200r-installer.c b/bootloader/main-e200r-installer.c
index 490f1f04dd..c5751d3095 100644
--- a/bootloader/main-e200r-installer.c
+++ b/bootloader/main-e200r-installer.c
@@ -26,12 +26,13 @@
#include <stdlib.h>
#include "common.h"
#include "cpu.h"
-#include "file.h"
#include "system.h"
#include "../kernel-internal.h"
#include "lcd.h"
#include "font.h"
#include "storage.h"
+#include "file_internal.h"
+#include "file.h"
#include "button.h"
#include "disk.h"
#include "crc32-mi4.h"
@@ -92,7 +93,7 @@ void* main(void)
int num_partitions;
int crc32;
char sector[512];
- struct partinfo* pinfo;
+ struct partinfo pinfo;
system_init();
kernel_init();
@@ -117,7 +118,7 @@ void* main(void)
printf("");
i=storage_init();
- disk_init(IF_MV(0));
+ filesystem_init();
num_partitions = disk_mount_all();
if (num_partitions<=0)
@@ -125,17 +126,17 @@ void* main(void)
error(EDISK, num_partitions, true);
}
- pinfo = disk_partinfo(1);
+ disk_partinfo(1, &pinfo);
#if 0 /* not needed in release builds */
printf("--- Partition info ---");
- printf("start: %x", pinfo->start);
- printf("size: %x", pinfo->size);
- printf("type: %x", pinfo->type);
+ printf("start: %x", pinfo.start);
+ printf("size: %x", pinfo.size);
+ printf("type: %x", pinfo.type);
printf("reading: %x", (START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK)*512);
#endif
- storage_read_sectors(pinfo->start + START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK,
+ storage_read_sectors(pinfo.start + START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK,
1 , sector);
crc32 = chksum_crc32 (sector, 512);
@@ -157,7 +158,7 @@ void* main(void)
memcpy(&sector[HACK_OFFSET], changedBytes,
sizeof(changedBytes)/sizeof(*changedBytes));
storage_write_sectors(
- pinfo->start + START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK,
+ pinfo.start + START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK,
1 , sector);
printf("Firmware unlocked");
printf("Proceed to Step 2");
diff --git a/bootloader/main-pp.c b/bootloader/main-pp.c
index 38760a0e58..562cf17ca4 100644
--- a/bootloader/main-pp.c
+++ b/bootloader/main-pp.c
@@ -33,6 +33,7 @@
#include "lcd.h"
#include "font.h"
#include "storage.h"
+#include "file_internal.h"
#include "adc.h"
#include "button.h"
#include "disk.h"
@@ -291,7 +292,7 @@ void* main(void)
int btn;
int rc;
int num_partitions;
- struct partinfo* pinfo;
+ struct partinfo pinfo;
#if !(CONFIG_STORAGE & STORAGE_SD)
char buf[256];
unsigned short* identify_info;
@@ -370,7 +371,7 @@ void* main(void)
}
#endif
- disk_init(IF_MV(0));
+ filesystem_init();
num_partitions = disk_mount_all();
if (num_partitions<=0)
{
@@ -381,9 +382,9 @@ void* main(void)
that have more than that */
for(i=0; i<NUM_PARTITIONS; i++)
{
- pinfo = disk_partinfo(i);
+ disk_partinfo(i, &pinfo);
printf("Partition %d: 0x%02x %ld MB",
- i, pinfo->type, pinfo->size / 2048);
+ i, pinfo.type, pinfo.size / 2048);
}
/* Now that storage is initialized, check for USB connection */
@@ -430,10 +431,10 @@ void* main(void)
#if (CONFIG_STORAGE & STORAGE_SD)
/* First try a (hidden) firmware partition */
printf("Trying firmware partition");
- pinfo = disk_partinfo(1);
- if(pinfo->type == PARTITION_TYPE_OS2_HIDDEN_C_DRIVE)
+ disk_partinfo(1, &pinfo);
+ if(pinfo.type == PARTITION_TYPE_OS2_HIDDEN_C_DRIVE)
{
- rc = load_mi4_part(loadbuffer, pinfo, MAX_LOADSIZE,
+ rc = load_mi4_part(loadbuffer, &pinfo, MAX_LOADSIZE,
usb == USB_INSERTED);
if (rc <= EFILE_EMPTY) {
printf("Can't load from partition");
diff --git a/bootloader/mini2440.c b/bootloader/mini2440.c
index b3d73d1270..4088065550 100644
--- a/bootloader/mini2440.c
+++ b/bootloader/mini2440.c
@@ -29,6 +29,7 @@
#include "lcd.h"
#include "../kernel-internal.h"
#include "storage.h"
+#include "file_internal.h"
#include "fat.h"
#include "disk.h"
#include "font.h"
@@ -88,7 +89,7 @@ int main(void)
error(EATA, rc, true);
}
- disk_init(IF_MD(0));
+ filesystem_init();
rc = disk_mount_all();
if (rc<=0)
{
diff --git a/bootloader/mpio_hd200_hd300.c b/bootloader/mpio_hd200_hd300.c
index a4e13e1f88..e66732f848 100644
--- a/bootloader/mpio_hd200_hd300.c
+++ b/bootloader/mpio_hd200_hd300.c
@@ -29,6 +29,7 @@
#include "lcd.h"
#include "../kernel-internal.h"
#include "storage.h"
+#include "file_internal.h"
#include "usb.h"
#include "disk.h"
#include "font.h"
@@ -202,7 +203,7 @@ static void rb_boot(void)
if(rc)
error(EATA, rc, true);
- disk_init();
+ filesystem_init();
rc = disk_mount_all();
if (rc <= 0)
diff --git a/bootloader/mrobe500.c b/bootloader/mrobe500.c
index 0b9ffb6281..08ba6f2acd 100644
--- a/bootloader/mrobe500.c
+++ b/bootloader/mrobe500.c
@@ -26,6 +26,7 @@
#include "lcd.h"
#include "../kernel-internal.h"
#include "storage.h"
+#include "file_internal.h"
#include "fat.h"
#include "disk.h"
#include "font.h"
@@ -123,8 +124,8 @@ void main(void)
error(EATA, rc, true);
}
- printf("disk");
- disk_init();
+ printf("filesystem");
+ filesystem_init();
printf("mount");
rc = disk_mount_all();
diff --git a/bootloader/ondavx747.c b/bootloader/ondavx747.c
index 3d03c36eae..0f1c504553 100644
--- a/bootloader/ondavx747.c
+++ b/bootloader/ondavx747.c
@@ -33,6 +33,7 @@
#include "rb-loader.h"
#include "loader_strerror.h"
#include "storage.h"
+#include "file_internal.h"
#include "disk.h"
#include "string.h"
#include "adc.h"
@@ -269,6 +270,8 @@ int main(void)
show_logo();
+ filesystem_init();
+
rc = storage_init();
if(rc)
error(EATA, rc, true);
diff --git a/bootloader/rk27xx.c b/bootloader/rk27xx.c
index d190ea5a5a..57e5868a87 100644
--- a/bootloader/rk27xx.c
+++ b/bootloader/rk27xx.c
@@ -12,6 +12,7 @@
#include "button.h"
#include "common.h"
#include "storage.h"
+#include "file_internal.h"
#include "disk.h"
#include "panic.h"
#include "power.h"
@@ -146,8 +147,7 @@ void main(void)
if(ret < 0)
error(EATA, ret, true);
- while(!disk_init(IF_MV(0)))
- panicf("disk_init failed!");
+ filesystem_init();
while((ret = disk_mount_all()) <= 0)
error(EDISK, ret, true);
diff --git a/bootloader/sansa_as3525.c b/bootloader/sansa_as3525.c
index 8592f2450f..01fb8459b4 100644
--- a/bootloader/sansa_as3525.c
+++ b/bootloader/sansa_as3525.c
@@ -39,6 +39,7 @@
#include "rb-loader.h"
#include "loader_strerror.h"
#include "storage.h"
+#include "file_internal.h"
#include "disk.h"
#include "panic.h"
#include "power.h"
@@ -119,6 +120,8 @@ void main(void)
if(ret < 0)
error(EATA, ret, true);
+ filesystem_init();
+
#ifdef USE_ROCKBOX_USB
usb_init();
usb_start_monitoring();
@@ -128,13 +131,6 @@ void main(void)
usb_mode();
#endif /* USE_ROCKBOX_USB */
- while(!disk_init(IF_MV(0)))
-#ifdef USE_ROCKBOX_USB
- usb_mode();
-#else
- panicf("disk_init failed!");
-#endif
-
while((ret = disk_mount_all()) <= 0)
{
#ifdef USE_ROCKBOX_USB
diff --git a/bootloader/sansaconnect.c b/bootloader/sansaconnect.c
index 48617ec337..5bd59b3ac4 100644
--- a/bootloader/sansaconnect.c
+++ b/bootloader/sansaconnect.c
@@ -21,6 +21,7 @@
#include "lcd.h"
#include "../kernel-internal.h"
#include "storage.h"
+#include "file_internal.h"
#include "disk.h"
#include "font.h"
#include "backlight.h"
@@ -72,7 +73,7 @@ void main(void)
if(ret)
printf("SD error: %d", ret);
- disk_init(IF_MD(0));
+ filesystem_init();
ret = disk_mount_all();
if (ret <= 0)
diff --git a/bootloader/telechips.c b/bootloader/telechips.c
index 17ba509a6c..d2cf10eb21 100644
--- a/bootloader/telechips.c
+++ b/bootloader/telechips.c
@@ -30,6 +30,7 @@
#include "lcd.h"
#include "../kernel-internal.h"
#include "storage.h"
+#include "file_internal.h"
#include "fat.h"
#include "disk.h"
#include "font.h"
@@ -162,6 +163,8 @@ void* main(void)
error(EATA, rc, true);
}
+ filesystem_init();
+
printf("mount");
rc = disk_mount_all();
if (rc<=0)
diff --git a/bootloader/tpj1022.c b/bootloader/tpj1022.c
index 159dcc63cd..30adb6ba4e 100644
--- a/bootloader/tpj1022.c
+++ b/bootloader/tpj1022.c
@@ -31,6 +31,7 @@
#include "kernel.h"
#include "thread.h"
#include "storage.h"
+#include "file_internal.h"
#include "fat.h"
#include "disk.h"
#include "font.h"
@@ -56,7 +57,7 @@ void* main(void)
i=storage_init();
- disk_init();
+ filesystem_init();
rc = disk_mount_all();
#if 0
diff --git a/firmware/SOURCES b/firmware/SOURCES
index a68c4921ba..070f0d4a62 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -59,17 +59,27 @@ target/hosted/sdl/lcd-remote-bitmap.c
target/hosted/sdl/lcd-sdl.c
target/hosted/sdl/system-sdl.c
#ifdef HAVE_SDL_THREADS
-target/hosted/sdl/thread-sdl.c
-#else
+target/hosted/sdl/filesystem-sdl.c
#endif
+target/hosted/sdl/load_code-sdl.c
target/hosted/sdl/timer-sdl.c
#ifdef HAVE_TOUCHSCREEN
target/hosted/sdl/key_to_touch-sdl.c
#endif
#ifdef APPLICATION
+target/hosted/sdl/app/load_code-sdl-app.c
target/hosted/sdl/app/button-application.c
-#endif
-#endif
+#ifdef WIN32
+target/hosted/filesystem-win32.c
+#else /* !WIN32 */
+target/hosted/filesystem-unix.c
+#endif /* WIN32 */
+#endif /* APPLICATION */
+#endif /* HAVE_SDL */
+
+#ifdef APPLICATION
+target/hosted/filesystem-app.c
+#endif /* APPLICATION */
#if defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1)
target/hosted/kernel-unix.c
@@ -163,19 +173,19 @@ common/crc32-mi4.c
common/crc32-rkw.c
#endif
#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
-common/dir_uncached.c
+common/dir.c
+common/disk_cache.c
common/file.c
+common/file_internal.c
common/disk.c
+common/fileobj_mgr.c
#endif /* PLATFORM_NATIVE */
#ifdef HAVE_DIRCACHE
common/dircache.c
#endif /* HAVE_DIRCACHE */
-common/filefuncs.c
+common/pathfuncs.c
common/format.c
common/linked_list.c
-#ifdef APPLICATION
-common/rbpaths.c
-#endif
common/strcasecmp.c
common/strcasestr.c
common/strnatcmp.c
@@ -1816,7 +1826,20 @@ target/hosted/android/app/button-application.c
drivers/audio/android.c
#endif
-#endif /* defined(SIMULATOR) */
+#else /* defined(SIMULATOR) */
+
+#ifdef WIN32
+asm/mempcpy.c
+target/hosted/filesystem-win32.c
+#else /* !WIN32 */
+target/hosted/filesystem-unix.c
+#endif /* WIN32 */
+target/hosted/sdl/load_code-sdl.c
+#ifdef HAVE_SDL_THREADS
+target/hosted/sdl/filesystem-sdl.c
+#endif
+
+#endif /* !defined(SIMULATOR) */
#if defined(HAVE_TOUCHPAD) && !defined(HAS_BUTTON_HOLD)
drivers/touchpad.c
@@ -1826,9 +1849,7 @@ drivers/touchpad.c
#ifdef HAVE_CORELOCK_OBJECT
kernel/corelock.c
#endif
-#if 0 /* pending dependent code */
kernel/mrsw_lock.c
-#endif
kernel/mutex.c
kernel/queue.c
#ifdef HAVE_SEMAPHORE_OBJECTS
diff --git a/firmware/asm/mips/memcpy.S b/firmware/asm/mips/memcpy.S
index 2e7f245c69..edbf5ac5eb 100644
--- a/firmware/asm/mips/memcpy.S
+++ b/firmware/asm/mips/memcpy.S
@@ -63,13 +63,13 @@ memcpy:
SWHI t0, 0(a0)
addu a0, t1
-chk8w:
+chk8w:
andi t0, a2, 0x1f # 32 or more bytes left?
beq t0, a2, chk1w
subu a3, a2, t0 # Yes
addu a3, a1 # a3 = end address of loop
move a2, t0 # a2 = what will be left after loop
-lop8w:
+lop8w:
lw t0, 0(a1) # Loop taking 8 words at a time
lw t1, 4(a1)
lw t2, 8(a1)
@@ -90,34 +90,34 @@ lop8w:
bne a1, a3, lop8w
sw t7, -4(a0)
-chk1w:
+chk1w:
andi t0, a2, 0x3 # 4 or more bytes left?
beq t0, a2, last8
subu a3, a2, t0 # Yes, handle them one word at a time
addu a3, a1 # a3 again end address
move a2, t0
-lop1w:
+lop1w:
lw t0, 0(a1)
addiu a0, 4
addiu a1, 4
bne a1, a3, lop1w
sw t0, -4(a0)
-last8:
+last8:
blez a2, lst8e # Handle last 8 bytes, one at a time
addu a3, a2, a1
-lst8l:
+lst8l:
lb t0, 0(a1)
addiu a0, 1
addiu a1, 1
bne a1, a3, lst8l
sb t0, -1(a0)
-lst8e:
+lst8e:
jr ra # Bye, bye
nop
-shift:
- subu a3, zero, a0 # Src and Dest unaligned
+shift:
+ subu a3, zero, a0 # Src and Dest unaligned
andi a3, 0x3 # (unoptimized case...)
beq a3, zero, shft1
subu a2, a3 # a2 = bytes left
@@ -126,11 +126,11 @@ shift:
addu a1, a3
SWHI t0, 0(a0)
addu a0, a3
-shft1:
+shft1:
andi t0, a2, 0x3
subu a3, a2, t0
addu a3, a1
-shfth:
+shfth:
LWHI t1, 0(a1) # Limp through, word by word
LWLO t1, 3(a1)
addiu a0, 4
diff --git a/firmware/asm/sh/memcpy.S b/firmware/asm/sh/memcpy.S
index e23a579b05..59c5801ac0 100644
--- a/firmware/asm/sh/memcpy.S
+++ b/firmware/asm/sh/memcpy.S
@@ -60,13 +60,13 @@ ___memcpy_fwd_entry:
cmp/hs r0,r6 /* at least 11 bytes to copy? (ensures 2 aligned longs) */
add r5,r6 /* r6 = source_end */
bf .start_b2 /* no: jump directly to byte loop */
-
+
mov #3,r0
neg r5,r3
and r0,r3 /* r3 = (4 - align_offset) % 4 */
tst r3,r3 /* already aligned? */
bt .end_b1 /* yes: skip leading byte loop */
-
+
add r5,r3 /* r3 = first source long bound */
/* leading byte loop: copies 0..3 bytes */
@@ -89,7 +89,7 @@ ___memcpy_fwd_entry:
mov r6,r3 /* move end address to r3 */
jmp @r1 /* and jump to it */
add #-7,r3 /* adjust end addr for main loops doing 2 longs/pass */
-
+
/** main loops, copying 2 longs per pass to profit from fast page mode **/
/* long aligned destination (fastest) */
@@ -102,11 +102,11 @@ ___memcpy_fwd_entry:
mov.l r0,@-r4 /* store second long */
mov.l r1,@-r4 /* store first long; NOT ALIGNED - no speed loss here! */
bt .loop_do0
-
+
add #4,r3 /* readjust end address */
cmp/hi r5,r3 /* one long left? */
bf .start_b2 /* no, jump to trailing byte loop */
-
+
mov.l @r5+,r0 /* load last long & increment source addr */
add #4,r4 /* increment dest addr */
bra .start_b2 /* jump to trailing byte loop */
diff --git a/firmware/common/dir.c b/firmware/common/dir.c
new file mode 100644
index 0000000000..da798c71d5
--- /dev/null
+++ b/firmware/common/dir.c
@@ -0,0 +1,413 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Björn Stenberg
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#define DIRFUNCTIONS_DEFINED
+#include "config.h"
+#include <errno.h>
+#include <string.h>
+#include "debug.h"
+#include "dir.h"
+#include "pathfuncs.h"
+#include "fileobj_mgr.h"
+#include "dircache_redirect.h"
+
+/* structure used for open directory streams */
+static struct dirstr_desc
+{
+ struct filestr_base stream; /* basic stream info (first!) */
+ struct dirscan_info scan; /* directory scan cursor */
+ struct dirent entry; /* current parsed entry information */
+#ifdef HAVE_MULTIVOLUME
+ int volumecounter; /* counter for root volume entries */
+#endif
+} open_streams[MAX_OPEN_DIRS];
+
+/* check and return a struct dirstr_desc* from a DIR* */
+static struct dirstr_desc * get_dirstr(DIR *dirp)
+{
+ struct dirstr_desc *dir = (struct dirstr_desc *)dirp;
+
+ if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
+ dir = NULL;
+ else if (dir->stream.flags & FDO_BUSY)
+ return dir;
+
+ int errnum;
+
+ if (!dir)
+ {
+ errnum = EFAULT;
+ }
+ else if (dir->stream.flags == FV_NONEXIST)
+ {
+ DEBUGF("dir #%d: nonexistant device\n", (int)(dir - open_streams));
+ errnum = ENXIO;
+ }
+ else
+ {
+ DEBUGF("dir #%d: dir not open\n", (int)(dir - open_streams));
+ errnum = EBADF;
+ }
+
+ errno = errnum;
+ return NULL;
+}
+
+#define GET_DIRSTR(type, dirp) \
+ ({ \
+ file_internal_lock_##type(); \
+ struct dirstr_desc *_dir = get_dirstr(dirp); \
+ if (_dir) \
+ FILESTR_LOCK(type, &_dir->stream); \
+ else \
+ file_internal_unlock_##type(); \
+ _dir; \
+ })
+
+/* release the lock on the dirstr_desc* */
+#define RELEASE_DIRSTR(type, dir) \
+ ({ \
+ FILESTR_UNLOCK(type, &(dir)->stream); \
+ file_internal_unlock_##type(); \
+ })
+
+
+/* find a free dir stream descriptor */
+static struct dirstr_desc * alloc_dirstr(void)
+{
+ for (unsigned int dd = 0; dd < MAX_OPEN_DIRS; dd++)
+ {
+ struct dirstr_desc *dir = &open_streams[dd];
+ if (!dir->stream.flags)
+ return dir;
+ }
+
+ DEBUGF("Too many dirs open\n");
+ return NULL;
+}
+
+#ifdef HAVE_MULTIVOLUME
+static int readdir_volume_inner(struct dirstr_desc *dir, struct dirent *entry)
+{
+ /* Volumes (secondary file systems) get inserted into the system root
+ * directory. If the path specified volume 0, enumeration will not
+ * include other volumes, but just its own files and directories.
+ *
+ * Fake special directories, which don't really exist, that will get
+ * redirected upon opendir()
+ */
+ while (++dir->volumecounter < NUM_VOLUMES)
+ {
+ /* on the system root */
+ if (!fat_ismounted(dir->volumecounter))
+ continue;
+
+ get_volume_name(dir->volumecounter, entry->d_name);
+ dir->entry.info.attr = ATTR_MOUNT_POINT;
+ dir->entry.info.size = 0;
+ dir->entry.info.wrtdate = 0;
+ dir->entry.info.wrttime = 0;
+ return 1;
+ }
+
+ /* do normal directory entry fetching */
+ return 0;
+}
+#endif /* HAVE_MULTIVOLUME */
+
+static inline int readdir_volume(struct dirstr_desc *dir,
+ struct dirent *entry)
+{
+#ifdef HAVE_MULTIVOLUME
+ /* fetch virtual volume entries? */
+ if (dir->volumecounter < NUM_VOLUMES)
+ return readdir_volume_inner(dir, entry);
+#endif /* HAVE_MULTIVOLUME */
+
+ /* do normal directory entry fetching */
+ return 0;
+ (void)dir; (void)entry;
+}
+
+
+/** POSIX interface **/
+
+/* open a directory */
+DIR * opendir(const char *dirname)
+{
+ DEBUGF("opendir(dirname=\"%s\"\n", dirname);
+
+ DIR *dirp = NULL;
+
+ file_internal_lock_WRITER();
+
+ int rc;
+
+ struct dirstr_desc * const dir = alloc_dirstr();
+ if (!dir)
+ FILE_ERROR(EMFILE, RC);
+
+ rc = open_stream_internal(dirname, FF_DIR, &dir->stream, NULL);
+ if (rc < 0)
+ {
+ DEBUGF("Open failed: %d\n", rc);
+ FILE_ERROR(ERRNO, RC);
+ }
+
+#ifdef HAVE_MULTIVOLUME
+ /* volume counter is relevant only to the system root */
+ dir->volumecounter = rc > 1 ? 0 : INT_MAX;
+#endif /* HAVE_MULTIVOLUME */
+
+ fat_rewind(&dir->stream.fatstr);
+ rewinddir_dirent(&dir->scan);
+
+ dirp = (DIR *)dir;
+file_error:
+ file_internal_unlock_WRITER();
+ return dirp;
+}
+
+/* close a directory stream */
+int closedir(DIR *dirp)
+{
+ int rc;
+
+ file_internal_lock_WRITER();
+
+ /* needs to work even if marked "nonexistant" */
+ struct dirstr_desc * const dir = (struct dirstr_desc *)dirp;
+ if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
+ FILE_ERROR(EFAULT, -1);
+
+ if (!dir->stream.flags)
+ {
+ DEBUGF("dir #%d: dir not open\n", (int)(dir - open_streams));
+ FILE_ERROR(EBADF, -2);
+ }
+
+ rc = close_stream_internal(&dir->stream);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 3);
+
+file_error:
+ file_internal_unlock_WRITER();
+ return rc;
+}
+
+/* read a directory */
+struct dirent * readdir(DIR *dirp)
+{
+ struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
+ if (!dir)
+ FILE_ERROR_RETURN(ERRNO, NULL);
+
+ struct dirent *res = NULL;
+
+ int rc = readdir_volume(dir, &dir->entry);
+ if (rc == 0)
+ {
+ rc = readdir_dirent(&dir->stream, &dir->scan, &dir->entry);
+ if (rc < 0)
+ FILE_ERROR(EIO, RC);
+ }
+
+ if (rc > 0)
+ res = &dir->entry;
+
+file_error:
+ RELEASE_DIRSTR(READER, dir);
+
+ if (rc > 1)
+ iso_decode_d_name(res->d_name);
+
+ return res;
+}
+
+#if 0 /* not included now but probably should be */
+/* read a directory (reentrant) */
+int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
+{
+ if (!result)
+ FILE_ERROR_RETURN(EFAULT, -2);
+
+ *result = NULL;
+
+ if (!entry)
+ FILE_ERROR_RETURN(EFAULT, -3);
+
+ struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
+ if (!dir)
+ FILE_ERROR_RETURN(ERRNO, -1);
+
+ int rc = readdir_volume(dir, entry);
+ if (rc == 0)
+ {
+ rc = readdir_dirent(&dir->stream, &dir->scan, entry);
+ if (rc < 0)
+ FILE_ERROR(EIO, rc * 10 - 4);
+ }
+
+file_error:
+ RELEASE_DIRSTR(READER, dir);
+
+ if (rc > 0)
+ {
+ if (rc > 1)
+ iso_decode_d_name(entry->d_name);
+
+ *result = entry;
+ rc = 0;
+ }
+
+ return rc;
+}
+
+/* reset the position of a directory stream to the beginning of a directory */
+void rewinddir(DIR *dirp)
+{
+ struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
+ if (!dir)
+ FILE_ERROR_RETURN(ERRNO);
+
+ rewinddir_dirent(&dir->scan);
+
+#ifdef HAVE_MULTIVOLUME
+ if (dir->volumecounter != INT_MAX)
+ dir->volumecounter = 0;
+#endif /* HAVE_MULTIVOLUME */
+
+ RELEASE_DIRSTR(READER, dir);
+}
+
+#endif /* 0 */
+
+/* make a directory */
+int mkdir(const char *path)
+{
+ DEBUGF("mkdir(path=\"%s\")\n", path);
+
+ int rc;
+
+ file_internal_lock_WRITER();
+
+ struct filestr_base stream;
+ struct path_component_info compinfo;
+ rc = open_stream_internal(path, FF_DIR, &stream, &compinfo);
+ if (rc < 0)
+ {
+ DEBUGF("Can't open parent dir or path is not a directory\n");
+ FILE_ERROR(ERRNO, rc * 10 - 1);
+ }
+ else if (rc > 0)
+ {
+ DEBUGF("File exists\n");
+ FILE_ERROR(EEXIST, -2);
+ }
+
+ rc = create_stream_internal(&compinfo.parentinfo, compinfo.name,
+ compinfo.length, ATTR_NEW_DIRECTORY,
+ FO_DIRECTORY, &stream);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 3);
+
+ rc = 0;
+file_error:
+ close_stream_internal(&stream);
+ file_internal_unlock_WRITER();
+ return rc;
+}
+
+/* remove a directory */
+int rmdir(const char *name)
+{
+ DEBUGF("rmdir(name=\"%s\")\n", name);
+
+ if (name)
+ {
+ /* path may not end with "." */
+ const char *basename;
+ size_t len = path_basename(name, &basename);
+ if (basename[0] == '.' && len == 1)
+ {
+ DEBUGF("Invalid path; last component is \".\"\n");
+ FILE_ERROR_RETURN(EINVAL, -9);
+ }
+ }
+
+ file_internal_lock_WRITER();
+ int rc = remove_stream_internal(name, NULL, FF_DIR);
+ file_internal_unlock_WRITER();
+ return rc;
+}
+
+
+/** Extended interface **/
+
+/* return if two directory streams refer to the same directory */
+int samedir(DIR *dirp1, DIR *dirp2)
+{
+ struct dirstr_desc * const dir1 = GET_DIRSTR(WRITER, dirp1);
+ if (!dir1)
+ FILE_ERROR_RETURN(ERRNO, -1);
+
+ int rc = -2;
+
+ struct dirstr_desc * const dir2 = get_dirstr(dirp2);
+ if (dir2)
+ rc = dir1->stream.bindp == dir2->stream.bindp ? 1 : 0;
+
+ RELEASE_DIRSTR(WRITER, dir1);
+ return rc;
+}
+
+/* test directory existence (returns 'false' if a file) */
+bool dir_exists(const char *dirname)
+{
+ file_internal_lock_WRITER();
+ bool rc = test_stream_exists_internal(dirname, FF_DIR) > 0;
+ file_internal_unlock_WRITER();
+ return rc;
+}
+
+/* get the portable info from the native entry */
+struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry)
+{
+ int rc;
+ if (!dirp || !entry)
+ FILE_ERROR(EFAULT, RC);
+
+ if (entry->d_name[0] == '\0')
+ FILE_ERROR(ENOENT, RC);
+
+ if ((file_size_t)entry->info.size > FILE_SIZE_MAX)
+ FILE_ERROR(EOVERFLOW, RC);
+
+ return (struct dirinfo)
+ {
+ .attribute = entry->info.attr,
+ .size = entry->info.size,
+ .mtime = fattime_mktime(entry->info.wrtdate, entry->info.wrttime),
+ };
+
+file_error:
+ return (struct dirinfo){ .attribute = 0 };
+}
diff --git a/firmware/common/dir_uncached.c b/firmware/common/dir_uncached.c
deleted file mode 100644
index b850a514e7..0000000000
--- a/firmware/common/dir_uncached.c
+++ /dev/null
@@ -1,312 +0,0 @@
-/***************************************************************************
- * __________ __ ___.
- * Open \______ \ ____ ____ | | _\_ |__ _______ ___
- * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
- * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
- * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
- * \/ \/ \/ \/ \/
- * $Id: dir.c 13741 2007-06-30 02:08:27Z jethead71 $
- *
- * Copyright (C) 2002 by Björn Stenberg
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- ****************************************************************************/
-#include <stdio.h>
-#include <errno.h>
-#include <string.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include "fat.h"
-#include "dir.h"
-#include "debug.h"
-#include "filefuncs.h"
-
-#if (MEMORYSIZE > 8)
-#define MAX_OPEN_DIRS 12
-#else
-#define MAX_OPEN_DIRS 8
-#endif
-
-static DIR_UNCACHED opendirs[MAX_OPEN_DIRS];
-
-// release all dir handles on a given volume "by force", to avoid leaks
-int release_dirs(int volume)
-{
- DIR_UNCACHED* pdir = opendirs;
- int dd;
- int closed = 0;
- for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++)
- {
-#ifdef HAVE_MULTIVOLUME
- if (pdir->fatdir.file.volume == volume)
-#else
- (void)volume;
-#endif
- {
- pdir->busy = false; /* mark as available, no further action */
- closed++;
- }
- }
- return closed; /* return how many we did */
-}
-
-DIR_UNCACHED* opendir_uncached(const char* name)
-{
- char namecopy[MAX_PATH];
- char* part;
- char* end;
- struct fat_direntry entry;
- int dd;
- DIR_UNCACHED* pdir = opendirs;
-#ifdef HAVE_MULTIVOLUME
- int volume;
-#endif
-
- if ( name[0] != '/' ) {
- DEBUGF("Only absolute paths supported right now\n");
- return NULL;
- }
-
- /* find a free dir descriptor */
- for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++)
- if ( !pdir->busy )
- break;
-
- if ( dd == MAX_OPEN_DIRS ) {
- DEBUGF("Too many dirs open\n");
- errno = EMFILE;
- return NULL;
- }
-
- pdir->busy = true;
-
-#ifdef HAVE_MULTIVOLUME
- /* try to extract a heading volume name, if present */
- volume = strip_volume(name, namecopy);
- pdir->volumecounter = 0;
-#else
- strlcpy(namecopy, name, sizeof(namecopy)); /* just copy */
-#endif
-
- if ( fat_opendir(IF_MV(volume,) &pdir->fatdir, 0, NULL) < 0 ) {
- DEBUGF("Failed opening root dir\n");
- pdir->busy = false;
- return NULL;
- }
-
- for ( part = strtok_r(namecopy, "/", &end); part;
- part = strtok_r(NULL, "/", &end)) {
- /* scan dir for name */
- while (1) {
- if ((fat_getnext(&pdir->fatdir,&entry) < 0) ||
- (!entry.name[0])) {
- pdir->busy = false;
- return NULL;
- }
- if ( (entry.attr & FAT_ATTR_DIRECTORY) &&
- (!strcasecmp(part, entry.name)) ) {
- /* In reality, the parent_dir parameter of fat_opendir seems
- * useless because it's sole purpose it to have a way to
- * update the file metadata, but here we are only reading
- * a directory so there's no need for that kind of stuff.
- * However, the rmdir_uncached function uses a ugly hack to
- * avoid opening a directory twice when deleting it and thus
- * needs those information. That's why we pass pdir->fatdir both
- * as the parent directory and the resulting one (this is safe,
- * in doubt, check fat_open(dir) code) which will allow this kind of
- * (ugly) things */
- if ( fat_opendir(IF_MV(volume,)
- &pdir->fatdir,
- entry.firstcluster,
- &pdir->fatdir) < 0 ) {
- DEBUGF("Failed opening dir '%s' (%ld)\n",
- part, entry.firstcluster);
- pdir->busy = false;
- return NULL;
- }
-#ifdef HAVE_MULTIVOLUME
- pdir->volumecounter = -1; /* n.a. to subdirs */
-#endif
- break;
- }
- }
- }
-
- return pdir;
-}
-
-int closedir_uncached(DIR_UNCACHED* dir)
-{
- dir->busy=false;
- return 0;
-}
-
-struct dirent_uncached* readdir_uncached(DIR_UNCACHED* dir)
-{
- struct fat_direntry entry;
- struct dirent_uncached* theent = &(dir->theent);
-
- if (!dir->busy)
- return NULL;
-
-#ifdef HAVE_MULTIVOLUME
- /* Volumes (secondary file systems) get inserted into the root directory
- of the first volume, since we have no separate top level. */
- if (dir->volumecounter >= 0 /* on a root dir */
- && dir->volumecounter < NUM_VOLUMES /* in range */
- && dir->fatdir.file.volume == 0) /* at volume 0 */
- { /* fake special directories, which don't really exist, but
- will get redirected upon opendir_uncached() */
- while (++dir->volumecounter < NUM_VOLUMES)
- {
- if (fat_ismounted(dir->volumecounter))
- {
- memset(theent, 0, sizeof(*theent));
- theent->info.attribute = FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME;
- snprintf(theent->d_name, sizeof(theent->d_name),
- VOL_NAMES, dir->volumecounter);
- return theent;
- }
- }
- }
-#endif
- /* normal directory entry fetching follows here */
- if (fat_getnext(&(dir->fatdir),&entry) < 0)
- return NULL;
-
- if ( !entry.name[0] )
- return NULL;
-
- strlcpy(theent->d_name, entry.name, sizeof(theent->d_name));
- theent->info.attribute = entry.attr;
- theent->info.wrtdate = entry.wrtdate;
- theent->info.wrttime = entry.wrttime;
- theent->info.size = entry.filesize;
- theent->startcluster = entry.firstcluster;
-
- return theent;
-}
-
-int mkdir_uncached(const char *name)
-{
- DIR_UNCACHED *dir;
- char namecopy[MAX_PATH];
- char* end;
- char *basename;
- char *parent;
- struct dirent_uncached *entry;
- int dd;
- DIR_UNCACHED* pdir = opendirs;
- struct fat_dir *newdir;
- int rc;
-
- if ( name[0] != '/' ) {
- DEBUGF("mkdir: Only absolute paths supported right now\n");
- return -1;
- }
- /* find a free dir descriptor */
- for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++)
- if ( !pdir->busy )
- break;
-
- if ( dd == MAX_OPEN_DIRS ) {
- DEBUGF("Too many dirs open\n");
- errno = EMFILE;
- return -5;
- }
-
- pdir->busy = true;
- newdir = &pdir->fatdir;
-
- strlcpy(namecopy, name, sizeof(namecopy));
-
- /* Split the base name and the path */
- end = strrchr(namecopy, '/');
- *end = 0;
- basename = end+1;
-
- if(namecopy == end) /* Root dir? */
- parent = "/";
- else
- parent = namecopy;
-
- DEBUGF("mkdir: parent: %s, name: %s\n", parent, basename);
-
- dir = opendir_uncached(parent);
-
- if(!dir) {
- DEBUGF("mkdir: can't open parent dir\n");
- pdir->busy = false;
- return -2;
- }
-
- if(basename[0] == 0) {
- DEBUGF("mkdir: Empty dir name\n");
- pdir->busy = false;
- errno = EINVAL;
- return -3;
- }
-
- /* Now check if the name already exists */
- while ((entry = readdir_uncached(dir))) {
- if ( !strcasecmp(basename, entry->d_name) ) {
- DEBUGF("mkdir error: file exists\n");
- errno = EEXIST;
- closedir_uncached(dir);
- pdir->busy = false;
- return - 4;
- }
- }
-
- memset(newdir, 0, sizeof(struct fat_dir));
-
- rc = fat_create_dir(basename, newdir, &(dir->fatdir));
- closedir_uncached(dir);
- pdir->busy = false;
-
- return rc;
-}
-
-int rmdir_uncached(const char* name)
-{
- int rc;
- DIR_UNCACHED* dir;
- struct dirent_uncached* entry;
-
- dir = opendir_uncached(name);
- if (!dir)
- {
- errno = ENOENT; /* open error */
- return -1;
- }
-
- /* check if the directory is empty */
- while ((entry = readdir_uncached(dir)))
- {
- if (strcmp(entry->d_name, ".") &&
- strcmp(entry->d_name, ".."))
- {
- DEBUGF("rmdir error: not empty\n");
- errno = ENOTEMPTY;
- closedir_uncached(dir);
- return -2;
- }
- }
-
- rc = fat_remove(&(dir->fatdir.file));
- if ( rc < 0 ) {
- DEBUGF("Failed removing dir: %d\n", rc);
- errno = EIO;
- rc = rc * 10 - 3;
- }
-
- closedir_uncached(dir);
- return rc;
-}
diff --git a/firmware/common/dircache.c b/firmware/common/dircache.c
index b53fc4d7a6..1e580bf3af 100644
--- a/firmware/common/dircache.c
+++ b/firmware/common/dircache.c
@@ -8,6 +8,7 @@
* $Id$
*
* Copyright (C) 2005 by Miika Pekkarinen
+ * 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,13 +19,7 @@
* KIND, either express or implied.
*
****************************************************************************/
-
-/* TODO:
- - Allow cache live updating while transparent rebuild is running.
-*/
-
#include "config.h"
-
#include <stdio.h>
#include <errno.h>
#include "string-extra.h"
@@ -33,6 +28,8 @@
#include "debug.h"
#include "system.h"
#include "logf.h"
+#include "fileobj_mgr.h"
+#include "pathfuncs.h"
#include "dircache.h"
#include "thread.h"
#include "kernel.h"
@@ -42,393 +39,1626 @@
#include "dir.h"
#include "storage.h"
#include "audio.h"
-#if CONFIG_RTC
-#include "time.h"
-#include "timefuncs.h"
-#endif
#include "rbpaths.h"
+#include "linked_list.h"
+#ifdef HAVE_EEPROM_SETTINGS
+#include "crc32.h"
+#endif
+/**
+ * Cache memory layout:
+ * x - array of struct dircache_entry
+ * r - reserved buffer
+ * d - name buffer for the name entry of the struct dircache_entry
+ * 0 - zero bytes to assist free name block sentinel scanning (not 0xfe or 0xff)
+ * |xxxxxx|rrrrrrrrr|0|dddddd|0|
+ *
+ * Subsequent x are allocated from the front, d are allocated from the back,
+ * using the reserve buffer for entries added after initial scan.
+ *
+ * After a while the cache may look like:
+ * |xxxxxxxx|rrrrr|0|dddddddd|0|
+ *
+ * After a reboot, the reserve buffer is restored in it's size, so that the
+ * total allocation size grows:
+ * |xxxxxxxx|rrrrrrrrr|0|dddddddd|0|
+ *
+ *
+ * Cache structure:
+ * Format is memory position independent and uses only indexes as links. The
+ * buffer pointers are offset back by one entry to make the array 1-based so
+ * that an index of 0 may be considered an analog of a NULL pointer.
+ *
+ * Entry elements are linked together analagously to the filesystem directory
+ * structure with minor variations that are helpful to the cache's algorithms.
+ * Each volume has a special root structure in the dircache structure, not an
+ * entry in the cache, comprising a forest of volume trees which facilitates
+ * mounting or dismounting of specified volumes on the fly.
+ *
+ * Indexes identifying a volume are computed as: index = -volume - 1
+ * Returning the volume from these indexes is thus: volume = -index - 1
+ * Such indexes are used in root binding and as the 'up' index for an entry
+ * who's parent is the root directory.
+ *
+ * Open files list:
+ * When dircache is made it is the maintainer of the main volume open files
+ * lists, even when it is off. Any files open before dircache is enabled or
+ * initialized must be bound to cache entries by the scan and build operation.
+ * It maintains these lists in a special way.
+ *
+ * Queued (unresolved) bindings are at the back and resolved at the front.
+ * A pointer to the first of each kind of binding is provided to skip to help
+ * iterating one sublist or another.
+ *
+ * r0->r1->r2->q0->q1->q2->NULL
+ * ^resolved0 ^queued0
+ */
-/* Queue commands. */
-#define DIRCACHE_BUILD 1
-#define DIRCACHE_STOP 2
+#ifdef DIRCACHE_NATIVE
+#define dircache_lock() file_internal_lock_WRITER()
+#define dircache_unlock() file_internal_unlock_WRITER()
-#if (MEMORYSIZE > 8)
-#define MAX_OPEN_DIRS 12
-#else
-#define MAX_OPEN_DIRS 8
-#endif
-static DIR_CACHED opendirs[MAX_OPEN_DIRS];
-static char opendir_dnames[MAX_OPEN_DIRS][MAX_PATH];
+/* scan and build parameter data */
+struct sab_component;
+struct sab
+{
+ struct filestr_base stream; /* scan directory stream */
+ struct file_base_info info; /* scanned entry info */
+ bool volatile quit; /* halt all scanning */
+ struct sab_component *stackend; /* end of stack pointer */
+ struct sab_component *top; /* current top of stack */
+ struct sab_component
+ {
+ int volatile idx; /* cache index of directory */
+ int *downp; /* pointer to ce->down */
+ int *volatile prevp; /* previous item accessed */
+ } stack[]; /* "recursion" stack */
+};
+
+#else /* !DIRCACHE_NATIVE */
+
+#error need locking scheme
+#define FILESYS_WRITER_LOCK()
+#define FILESYS_WRITER_UNLOCK()
-#define MAX_PENDING_BINDINGS 2
-struct fdbind_queue {
+struct sab_component
+{
+};
+
+struct sab
+{
+#ifdef HAVE_MUTLIVOLUME
+ int volume;
+#endif /* HAVE_MULTIVOLUME */
char path[MAX_PATH];
- int fd;
+ unsigned int append;
+};
+#endif /* DIRCACHE_NATIVE */
+
+enum
+{
+ FRONTIER_SETTLED = 0x0, /* dir entry contents are complete */
+ FRONTIER_NEW = 0x1, /* dir entry contents are in-progress */
+ FRONTIER_ZONED = 0x2, /* frontier entry permanent mark (very sticky!) */
+ FRONTIER_RENEW = 0x4, /* override FRONTIER_ZONED sticky (not stored) */
};
-/* Unions with char to make pointer arithmetic simpler and avoid casting */
-struct dircache_entry {
- struct dirinfo info;
+enum
+{
+ DCM_BUILD, /* build a volume */
+ DCM_PROCEED, /* merged DCM_BUILD messages */
+ DCM_FIRST = DCM_BUILD,
+ DCM_LAST = DCM_PROCEED,
+};
+
+#define MAX_TINYNAME sizeof (uint32_t)
+#define DC_MAX_NAME MIN(MAX_NAME, UINT8_MAX)
+
+/* Throw some warnings if about the limits if things may not work */
+#if MAX_NAME > UINT8_MAX
+#warning Need more than 8 bits in name length bitfield
+#endif
+
+#if DIRCACHE_LIMIT > ((1 << 24)-255)
+#warning Names may not be addressable with 24 bits
+#endif
+
+/* data structure used by cache entries */
+struct dircache_entry
+{
+ int next; /* next at same level */
union {
- struct dircache_entry *next;
- char* next_char;
+ int down; /* first at child level (if directory) */
+ file_size_t filesize; /* size of file in bytes (if file) */
};
+ int up; /* parent index (-volume-1 if root) */
union {
- struct dircache_entry *up;
- char* up_char;
+ struct {
+ uint32_t name : 24; /* indirect storage (.tinyname == 0) */
+ uint32_t length : 8; /* length of name indexed by 'name' */
};
- union {
- struct dircache_entry *down;
- char* down_char;
+ unsigned char namebuf[MAX_TINYNAME]; /* direct storage (.tinyname == 1) */
};
- long startcluster;
- char *d_name;
-};
-
-/* Cache Layout:
- *
- * x - array of struct dircache_entry
- * r - reserved buffer
- * d - name buffer for the d_name entry of the struct dircache_entry
- * |xxxxxx|rrrrrrrrr|dddddd|
- *
- * subsequent x are allocated from the front, d are allocated from the back,
- * using the reserve buffer for entries added after initial scan
- *
- * after a while the cache may look like:
- * |xxxxxxxx|rrrrr|dddddddd|
- *
- * after a reboot, the reserve buffer is restored in it's size, so that the
- * total allocation size grows
- * |xxxxxxxx|rrrrrrrrr|dddddddd|
- */
-/* this points to the beginnging of the buffer and the first entry */
-static struct dircache_entry *dircache_root;
-/* these point to the start and end of the name buffer (d above) */
-static char *d_names_start, *d_names_end;
-/* put "." and ".." into the d_names buffer to enable easy pointer logic */
-static char *dot, *dotdot;
-#ifdef HAVE_MULTIVOLUME
-static struct dircache_entry *append_position;
+ uint32_t direntry : 16; /* entry # in parent - max 0xffff */
+ uint32_t direntries : 5; /* # of entries used - max 21 */
+ uint32_t tinyname : 1; /* if == 1, name fits in .namebuf */
+ uint32_t frontier : 2; /* (FRONTIER_* bitflags) */
+ uint32_t attr : 8; /* entry file attributes */
+#ifdef DIRCACHE_NATIVE
+ long firstcluster; /* first file cluster - max 0x0ffffff4 */
+ uint16_t wrtdate; /* FAT write date */
+ uint16_t wrttime; /* FAT write time */
+#else
+ time_t mtime; /* file last-modified time */
#endif
+ dc_serial_t serialnum; /* entry serial number */
+};
-static DIR_CACHED opendirs[MAX_OPEN_DIRS];
-static struct dircache_entry *fd_bindings[MAX_OPEN_FILES];
-
-static bool dircache_initialized = false;
-static bool dircache_initializing = false;
-static bool thread_enabled = false;
-static unsigned long allocated_size = 0;
-static unsigned long dircache_size = 0;
-static unsigned long entry_count = 0;
-static unsigned long reserve_used = 0;
-static unsigned int cache_build_ticks = 0;
-static unsigned long appflags = 0;
+/* spare us some tedium */
+#define ENTRYSIZE (sizeof (struct dircache_entry))
+/* thread and kernel stuff */
static struct event_queue dircache_queue SHAREDBSS_ATTR;
-static long dircache_stack[(DEFAULT_STACK_SIZE + 0x400)/sizeof(long)];
+static uintptr_t dircache_stack[DIRCACHE_STACK_SIZE / sizeof (uintptr_t)];
static const char dircache_thread_name[] = "dircache";
-static struct fdbind_queue fdbind_cache[MAX_PENDING_BINDINGS];
-static int fdbind_idx = 0;
+/* struct that is both used during run time and for persistent storage */
+static struct dircache
+{
+ /* cache-wide data */
+ int free_list; /* first index of free entry list */
+ size_t size; /* total size of data (including holes) */
+ size_t sizeused; /* bytes of .size bytes actually used */
+ union {
+ unsigned int numentries; /* entry count (including holes) */
+#ifdef HAVE_EEPROM_SETTINGS
+ size_t sizeentries; /* used when persisting */
+#endif
+ };
+ int names; /* index of first name in name block */
+ size_t sizenames; /* size of all names (including holes) */
+ size_t namesfree; /* amount of wasted name space */
+ int nextnamefree; /* hint of next free name in buffer */
+ /* per-volume data */
+ struct dircache_volume /* per volume cache data */
+ {
+ uint32_t status : 2; /* cache status of this volume */
+ uint32_t frontier : 2; /* (FRONTIER_* bitflags) */
+ dc_serial_t serialnum; /* dircache serial number of root */
+ int root_down; /* index of first entry of volume root */
+ union {
+ long start_tick; /* when did scan start (scanning) */
+ long build_ticks; /* how long to build volume? (ready) */
+ };
+ } dcvol[NUM_VOLUMES];
+ /* these remain unchanged between cache resets */
+ size_t last_size; /* last reported size at boot */
+ size_t reserve_used; /* reserved used at last build */
+ dc_serial_t last_serialnum; /* last serialnumber generated */
+} dircache;
+
+/* struct that is used only for the cache in main memory */
+struct dircache_runinfo
+{
+ /* cache setting and build info */
+ int suspended; /* dircache suspend count */
+ bool enabled; /* dircache master enable switch */
+ unsigned int thread_id; /* current/last thread id */
+ bool thread_done; /* thread has exited */
+ /* cache buffer info */
+ int handle; /* buflib buffer handle */
+ size_t bufsize; /* size of buflib allocation - 1 */
+ int buflocked; /* don't move due to other allocs */
+ union {
+ void *p; /* address of buffer - ENTRYSIZE */
+ struct dircache_entry *pentry; /* alias of .p to assist entry resolution */
+ unsigned char *pname; /* alias of .p to assist name resolution */
+ };
+ struct buflib_callbacks ops; /* buflib ops callbacks */
+ /* per-volume data */
+ struct dircache_runinfo_volume
+ {
+ struct file_base_binding *resolved0; /* first resolved binding in list */
+ struct file_base_binding *queued0; /* first queued binding in list */
+ struct sab *sabp; /* if building, struct sab in use */
+ } dcrivol[NUM_VOLUMES];
+} dircache_runinfo;
+
+#define BINDING_NEXT(bindp) \
+ ((struct file_base_binding *)(bindp)->node.next)
+
+#define FOR_EACH_BINDING(start, p) \
+ for (struct file_base_binding *p = (start); p; p = BINDING_NEXT(p))
+
+#define FOR_EACH_CACHE_ENTRY(ce) \
+ for (struct dircache_entry *ce = &dircache_runinfo.pentry[1], \
+ *_ceend = ce + dircache.numentries; \
+ ce < _ceend; ce++) if (ce->serialnum)
+
+#define FOR_EACH_SAB_COMP(sabp, p) \
+ for (struct sab_component *p = (sabp)->top; p < (sabp)->stackend; p++)
+
+/* "overloaded" macros to get volume structures */
+#define DCVOL_i(i) (&dircache.dcvol[i])
+#define DCVOL_volume(volume) (&dircache.dcvol[volume])
+#define DCVOL_infop(infop) (&dircache.dcvol[BASEINFO_VOL(infop)])
+#define DCVOL_dirinfop(dirinfop) (&dircache.dcvol[BASEINFO_VOL(dirinfop)])
+#define DCVOL(x) DCVOL_##x(x)
+
+#define DCRIVOL_i(i) (&dircache_runinfo.dcrivol[i])
+#define DCRIVOL_infop(infop) (&dircache_runinfo.dcrivol[BASEINFO_VOL(infop)])
+#define DCRIVOL_bindp(bindp) (&dircache_runinfo.dcrivol[BASEBINDING_VOL(bindp)])
+#define DCRIVOL(x) DCRIVOL_##x(x)
+
+/* reserve over 75% full? */
+#define DIRCACHE_STUFFED(reserve_used) \
+ ((reserve_used) > 3*DIRCACHE_RESERVE / 4)
-/* --- Internal cache structure control functions --- */
+#ifdef HAVE_EEPROM_SETTINGS
+/**
+ * remove the snapshot file
+ */
+static int remove_dircache_file(void)
+{
+ return remove(DIRCACHE_FILE);
+}
+
+/**
+ * open or create the snapshot file
+ */
+static int open_dircache_file(int oflag)
+{
+ return open(DIRCACHE_FILE, oflag, 0666);
+}
+#endif /* HAVE_EEPROM_SETTINGS */
-static inline struct dircache_entry* get_entry(int id)
+#ifdef DIRCACHE_DUMPSTER
+/**
+ * clean up the memory allocation to make viewing in a hex editor more friendly
+ * and highlight problems
+ */
+static inline void dumpster_clean_buffer(void *p, size_t size)
{
- return &dircache_root[id];
+ memset(p, 0xAA, size);
}
+#endif /* DIRCACHE_DUMPSTER */
-/* flag to make sure buffer doesn't move due to other allocs.
- * this is set to true completely during dircache build */
-static bool dont_move = false;
-static int dircache_handle;
-static int move_callback(int handle, void* current, void* new)
+/**
+ * relocate the cache when the buffer has moved
+ */
+static int move_callback(int handle, void *current, void *new)
{
- (void)handle;
- if (dont_move)
+ if (dircache_runinfo.buflocked)
return BUFLIB_CB_CANNOT_MOVE;
-#define UPDATE(x) if (x) { x = PTR_ADD(x, diff); }
- /* relocate the cache */
- ptrdiff_t diff = new - current;
- for(unsigned i = 0; i < entry_count; i++)
+ dircache_runinfo.p = new - ENTRYSIZE;
+
+ return BUFLIB_CB_OK;
+ (void)handle; (void)current;
+}
+
+/**
+ * add a "don't move" lock count
+ */
+static inline void buffer_lock(void)
+{
+ dircache_runinfo.buflocked++;
+}
+
+/**
+ * remove a "don't move" lock count
+ */
+static inline void buffer_unlock(void)
+{
+ dircache_runinfo.buflocked--;
+}
+
+
+/** Open file bindings management **/
+
+/* compare the basic file information and return 'true' if they are logically
+ equivalent or the same item, else return 'false' if not */
+static inline bool binding_compare(const struct file_base_info *infop1,
+ const struct file_base_info *infop2)
+{
+#ifdef DIRCACHE_NATIVE
+ return fat_file_is_same(&infop1->fatfile, &infop2->fatfile);
+#else
+ #error hey watch it!
+#endif
+}
+
+/**
+ * bind a file to the cache; "queued" or "resolved" depending upon whether or
+ * not it has entry information
+ */
+static void binding_open(struct file_base_binding *bindp)
+{
+ struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp);
+ if (bindp->info.dcfile.serialnum)
{
- UPDATE(dircache_root[i].d_name);
- UPDATE(dircache_root[i].next_char);
- UPDATE(dircache_root[i].up_char);
- UPDATE(dircache_root[i].down_char);
+ /* already resolved */
+ dcrivolp->resolved0 = bindp;
+ file_binding_insert_first(bindp);
}
- dircache_root = new;
- UPDATE(d_names_start);
- UPDATE(d_names_end);
- UPDATE(dot);
- UPDATE(dotdot);
+ else
+ {
+ if (dcrivolp->queued0 == NULL)
+ dcrivolp->queued0 = bindp;
- for(unsigned i = 0; i < MAX_OPEN_FILES; i++)
- UPDATE(fd_bindings[i]);
+ file_binding_insert_last(bindp);
+ }
+}
-#ifdef HAVE_MULTIVOLUME
- UPDATE(append_position);
-#endif
- return BUFLIB_CB_OK;
+/**
+ * remove a binding from the cache
+ */
+static void binding_close(struct file_base_binding *bindp)
+{
+ struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp);
+
+ if (bindp == dcrivolp->queued0)
+ dcrivolp->queued0 = BINDING_NEXT(bindp);
+ else if (bindp == dcrivolp->resolved0)
+ {
+ struct file_base_binding *nextp = BINDING_NEXT(bindp);
+ dcrivolp->resolved0 = (nextp == dcrivolp->queued0) ? NULL : nextp;
+ }
+
+ file_binding_remove(bindp);
+ /* no need to reset it */
}
-static struct buflib_callbacks ops = {
- .move_callback = move_callback,
- .shrink_callback = NULL,
-};
+/**
+ * resolve a queued binding with the information from the given source file
+ */
+static void binding_resolve(const struct file_base_info *infop)
+{
+ struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop);
+
+ /* quickly check the queued list to see if it's there */
+ struct file_base_binding *prevp = NULL;
+ FOR_EACH_BINDING(dcrivolp->queued0, p)
+ {
+ if (!binding_compare(infop, &p->info))
+ {
+ prevp = p;
+ continue;
+ }
+
+ if (p == dcrivolp->queued0)
+ dcrivolp->queued0 = BINDING_NEXT(p);
+ else
+ {
+ file_binding_remove_next(prevp, p);
+ file_binding_insert_first(p);
+ }
+
+ dcrivolp->resolved0 = p;
+
+ /* srcinfop may be the actual one */
+ if (&p->info != infop)
+ p->info.dcfile = infop->dcfile;
+
+ break;
+ }
+}
-#ifdef HAVE_EEPROM_SETTINGS
/**
- * Open the dircache file to save a snapshot on disk
+ * dissolve a resolved binding on its volume
*/
-static int open_dircache_file(unsigned flags, int permissions)
+static void binding_dissolve(struct file_base_binding *prevp,
+ struct file_base_binding *bindp)
{
- if (permissions != 0)
- return open(DIRCACHE_FILE, flags, permissions);
+ struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp);
- return open(DIRCACHE_FILE, flags);
+ if (bindp == dcrivolp->resolved0)
+ {
+ struct file_base_binding *nextp = BINDING_NEXT(bindp);
+ dcrivolp->resolved0 = (nextp == dcrivolp->queued0) ? NULL : nextp;
+ }
+
+ if (dcrivolp->queued0 == NULL)
+ dcrivolp->queued0 = bindp;
+
+ file_binding_remove_next(prevp, bindp);
+ file_binding_insert_last(bindp);
+
+ dircache_dcfile_init(&bindp->info.dcfile);
}
/**
- * Remove the snapshot file
+ * dissolve all resolved bindings on a given volume
*/
-static int remove_dircache_file(void)
+static void binding_dissolve_volume(struct dircache_runinfo_volume *dcrivolp)
{
- return remove(DIRCACHE_FILE);
+ if (!dcrivolp->resolved0)
+ return;
+
+ FOR_EACH_BINDING(dcrivolp->resolved0, p)
+ {
+ if (p == dcrivolp->queued0)
+ break;
+
+ dircache_dcfile_init(&p->info.dcfile);
+ }
+
+ dcrivolp->queued0 = dcrivolp->resolved0;
+ dcrivolp->resolved0 = NULL;
}
-#endif
-/**
- * Internal function to allocate a new dircache_entry from memory.
+
+
+/** Dircache buffer management **/
+
+/**
+ * allocate the cache's memory block
*/
-static struct dircache_entry* allocate_entry(void)
+static int alloc_cache(size_t size)
{
- struct dircache_entry *next_entry;
-
- if (dircache_size > allocated_size - MAX_PATH*2)
+ /* pad with one extra-- see alloc_name() and free_name() */
+ return core_alloc_ex("dircache", size + 1, &dircache_runinfo.ops);
+}
+
+/**
+ * put the allocation in dircache control
+ */
+static void set_buffer(int handle, size_t size)
+{
+ void *p = core_get_data(handle);
+
+#ifdef DIRCACHE_DUMPSTER
+ dumpster_clean_buffer(p, size);
+#endif /* DIRCACHE_DUMPSTER */
+
+ /* set it up as a 1-based array */
+ dircache_runinfo.p = p - ENTRYSIZE;
+
+ if (dircache_runinfo.handle != handle)
{
- logf("size limit reached");
- return NULL;
+ /* new buffer */
+ dircache_runinfo.handle = handle;
+ dircache_runinfo.bufsize = size;
+ dircache.names = size + ENTRYSIZE;
+ dircache_runinfo.pname[dircache.names - 1] = 0;
+ dircache_runinfo.pname[dircache.names ] = 0;
}
-
- next_entry = &dircache_root[entry_count++];
- next_entry->d_name = NULL;
- next_entry->up = NULL;
- next_entry->down = NULL;
- next_entry->next = NULL;
+}
- dircache_size += sizeof(struct dircache_entry);
+/**
+ * remove the allocation from dircache control and return the handle
+ */
+static int reset_buffer(void)
+{
+ int handle = dircache_runinfo.handle;
+ if (handle > 0)
+ {
+ /* don't mind .p; it might get changed by the callback even after
+ this call; buffer presence is determined by the following: */
+ dircache_runinfo.handle = 0;
+ dircache_runinfo.bufsize = 0;
+ }
+
+ return handle;
+}
+
+/**
+ * return the number of bytes remaining in the buffer
+ */
+static size_t dircache_buf_remaining(void)
+{
+ if (!dircache_runinfo.handle)
+ return 0;
+
+ return dircache_runinfo.bufsize - dircache.size;
+}
+
+/**
+ * return the amount of reserve space used
+ */
+static size_t reserve_buf_used(void)
+{
+ size_t remaining = dircache_buf_remaining();
+ return (remaining < DIRCACHE_RESERVE) ?
+ DIRCACHE_RESERVE - remaining : 0;
+}
+
+
+/** Internal cache structure control functions **/
+
+/**
+ * generate the next serial number in the sequence
+ */
+static dc_serial_t next_serialnum(void)
+{
+ dc_serial_t serialnum = MAX(dircache.last_serialnum + 1, 1);
+ dircache.last_serialnum = serialnum;
+ return serialnum;
+}
+
+/**
+ * return the dircache volume pointer for the special index
+ */
+static struct dircache_volume * get_idx_dcvolp(int idx)
+{
+ if (idx >= 0)
+ return NULL;
+
+ return &dircache.dcvol[IF_MV_VOL(-idx - 1)];
+}
+
+/**
+ * return the cache entry referenced by idx (NULL if outside buffer)
+ */
+static struct dircache_entry * get_entry(int idx)
+{
+ if (idx <= 0 || (unsigned int)idx > dircache.numentries)
+ return NULL;
- return next_entry;
+ return &dircache_runinfo.pentry[idx];
}
/**
- * Internal function to allocate a dircache_entry and set
- * ->next entry pointers.
+ * return the index of the cache entry (0 if outside buffer)
*/
-static struct dircache_entry* dircache_gen_next(struct dircache_entry *ce)
+static int get_index(const struct dircache_entry *ce)
{
- struct dircache_entry *next_entry;
+ if (!PTR_IN_ARRAY(dircache_runinfo.pentry + 1, ce,
+ dircache.numentries + 1))
+ {
+ return 0;
+ }
+
+ return ce - dircache_runinfo.pentry;
+}
- if ( (next_entry = allocate_entry()) == NULL)
+/**
+ * return the sublist down pointer for the sublist that contains entry 'idx'
+ */
+static int * get_downidxp(int idx)
+{
+ /* NOTE: 'idx' must refer to a directory or the result is undefined */
+ if (idx == 0 || idx < -NUM_VOLUMES)
return NULL;
- next_entry->up = ce->up;
- ce->next = next_entry;
-
- return next_entry;
+
+ if (idx > 0)
+ {
+ /* a normal entry */
+ struct dircache_entry *ce = get_entry(idx);
+ return ce ? &ce->down : NULL;
+ }
+ else
+ {
+ /* a volume root */
+ return &get_idx_dcvolp(idx)->root_down;
+ }
}
-/*
- * Internal function to allocate a dircache_entry and set
- * ->down entry pointers.
+/**
+ * return a pointer to the index referencing the cache entry that 'idx'
+ * references
*/
-static struct dircache_entry* dircache_gen_down(struct dircache_entry *ce)
+static int * get_previdxp(int idx)
{
- struct dircache_entry *next_entry;
+ struct dircache_entry *ce = get_entry(idx);
- if ( (next_entry = allocate_entry()) == NULL)
+ int *prevp = get_downidxp(ce->up);
+ if (!prevp)
return NULL;
- next_entry->up = ce;
- ce->down = next_entry;
-
- return next_entry;
+
+ while (1)
+ {
+ int next = *prevp;
+ if (!next || next == idx)
+ break;
+
+ prevp = &get_entry(next)->next;
+ }
+
+ return prevp;
+}
+
+/**
+ * if required, adjust the lists and directory read of any scan and build in
+ * progress
+ */
+static void sab_sync_scan(struct sab *sabp, int *prevp, int *nextp)
+{
+ struct sab_component *abovep = NULL;
+ FOR_EACH_SAB_COMP(sabp, p)
+ {
+ if (nextp != p->prevp)
+ {
+ abovep = p;
+ continue;
+ }
+
+ /* removing an item being scanned; set the component position to the
+ entry before this */
+ p->prevp = prevp;
+
+ if (p == sabp->top)
+ {
+ /* removed at item in the directory who's immediate contents are
+ being scanned */
+ if (prevp == p->downp)
+ {
+ /* was first item; rewind it */
+ dircache_rewinddir_internal(&sabp->info);
+ }
+ else
+ {
+ struct dircache_entry *ceprev =
+ container_of(prevp, struct dircache_entry, next);
+ #ifdef DIRCACHE_NATIVE
+ sabp->info.fatfile.e.entry = ceprev->direntry;
+ sabp->info.fatfile.e.entries = ceprev->direntries;
+ #endif
+ }
+ }
+ else if (abovep)
+ {
+ /* the directory being scanned or a parent of it has been removed;
+ abort its build or cache traversal */
+ abovep->idx = 0;
+ }
+
+ break;
+ }
+}
+
+/**
+ * get a pointer to an allocated name given a cache index
+ */
+static inline unsigned char * get_name(int nameidx)
+{
+ return &dircache_runinfo.pname[nameidx];
+}
+
+/**
+ * get the cache buffer index of the given name
+ */
+static inline int get_nameidx(const unsigned char *pname)
+{
+ return pname - dircache_runinfo.pname;
+}
+
+/**
+ * copy the entry's name to a buffer (which assumed to be of sufficient size)
+ */
+static void entry_name_copy(char *dst, const struct dircache_entry *ce)
+{
+ if (LIKELY(!ce->tinyname))
+ {
+ strmemcpy(dst, get_name(ce->name), ce->length);
+ return;
+ }
+
+ const unsigned char *src = ce->namebuf;
+ size_t len = 0;
+ while (len++ < MAX_TINYNAME && *src)
+ *dst++ = *src++;
+
+ *dst = '\0';
+}
+
+/**
+ * set the namesfree hint to a new position
+ */
+static void set_namesfree_hint(const unsigned char *hintp)
+{
+ int hintidx = get_nameidx(hintp);
+
+ if (hintidx >= (int)(dircache.names + dircache.sizenames))
+ hintidx = dircache.names;
+
+ dircache.nextnamefree = hintidx;
}
/**
- * Returns true if there is an event waiting in the queue
- * that requires the current operation to be aborted.
+ * allocate a buffer to use for a new name
*/
-static bool check_event_queue(void)
+static int alloc_name(size_t size)
+{
+ int nameidx = 0;
+
+ if (dircache.namesfree >= size)
+ {
+ /* scan for a free gap starting at the hint point - first fit */
+ unsigned char *start = get_name(dircache.nextnamefree), *p = start;
+ unsigned char *namesend = get_name(dircache.names + dircache.sizenames);
+ size_t gapsize = 0;
+
+ while (gapsize < size)
+ {
+ if ((p = memchr(p, 0xff, namesend - p)))
+ {
+ /* found a sentinel; see if there are enough in a row */
+ gapsize = 1;
+ while (*++p == 0xff && gapsize < size)
+ gapsize++;
+ }
+ else
+ {
+ if (namesend == start)
+ break; /* exhausted */
+
+ /* wrap */
+ namesend = start;
+ p = get_name(dircache.names);
+
+ if (p == namesend)
+ break; /* initial hint was at names start */
+ }
+ }
+
+ if (gapsize >= size)
+ {
+ unsigned char *namep = p - gapsize;
+ nameidx = get_nameidx(namep);
+
+ if (*p == 0xff)
+ {
+ /* if only a tiny block remains after buffer, claim it too */
+ size_t tinysize = 1;
+ while (*++p == 0xff && tinysize <= MAX_TINYNAME)
+ tinysize++;
+
+ if (tinysize <= MAX_TINYNAME)
+ {
+ /* mark with tiny block sentinel */
+ memset(p - tinysize, 0xfe, tinysize);
+ size += tinysize;
+ }
+ }
+
+ dircache.namesfree -= size;
+ dircache.sizeused += size;
+ set_namesfree_hint(namep + size);
+ }
+ }
+
+ if (!nameidx)
+ {
+ /* no sufficiently long free gaps; allocate anew */
+ if (dircache_buf_remaining() <= size)
+ {
+ dircache.last_size = 0;
+ return 0;
+ }
+
+ dircache.names -= size;
+ dircache.sizenames += size;
+ nameidx = dircache.names;
+ dircache.size += size;
+ dircache.sizeused += size;
+ *get_name(dircache.names - 1) = 0;
+ }
+
+ return nameidx;
+}
+
+/**
+ * mark a name as free and note that its bytes are available
+ */
+static void free_name(int nameidx, size_t size)
+{
+ unsigned char *beg = get_name(nameidx);
+ unsigned char *end = beg + size;
+
+ /* merge with any adjacent tiny blocks */
+ while (beg[-1] == 0xfe)
+ --beg;
+
+ while (end[1] == 0xfe)
+ ++end;
+
+ size = end - beg;
+ memset(beg, 0xff, size);
+ dircache.namesfree += size;
+ dircache.sizeused -= size;
+ set_namesfree_hint(beg);
+}
+
+/**
+ * allocate and assign a name to the entry
+ */
+static bool entry_assign_name(struct dircache_entry *ce,
+ const unsigned char *name, size_t size)
+{
+ unsigned char *copyto;
+
+ if (size <= MAX_TINYNAME)
+ {
+ copyto = ce->namebuf;
+
+ if (size < MAX_TINYNAME)
+ copyto[size] = '\0';
+
+ ce->tinyname = 1;
+ }
+ else
+ {
+ if (size > DC_MAX_NAME)
+ size = DC_MAX_NAME;
+
+ int nameidx = alloc_name(size);
+ if (!nameidx)
+ return false;
+
+ copyto = get_name(nameidx);
+
+ ce->tinyname = 0;
+ ce->name = nameidx;
+ ce->length = size;
+ }
+
+ memcpy(copyto, name, size);
+ return true;
+}
+
+/**
+ * free the name for the entry
+ */
+static void entry_unassign_name(struct dircache_entry *ce)
+{
+ if (!ce->tinyname)
+ free_name(ce->name, ce->length);
+}
+
+/**
+ * assign a new name to the entry
+ */
+static bool entry_reassign_name(struct dircache_entry *ce,
+ const unsigned char *newname)
+{
+ size_t oldlen = ce->tinyname ? 0 : ce->length;
+ size_t newlen = strlen(newname);
+
+ if (oldlen == newlen || (oldlen == 0 && newlen <= MAX_TINYNAME))
+ {
+ char *p = mempcpy(oldlen == 0 ? ce->namebuf : get_name(ce->name),
+ newname, newlen);
+ if (newlen < MAX_TINYNAME)
+ *p = '\0';
+ return true;
+ }
+
+ /* needs a new name allocation; if the new name fits in the freed block,
+ it will use it immediately without a lengthy search */
+ entry_unassign_name(ce);
+ return entry_assign_name(ce, newname, newlen);
+}
+
+/**
+ * allocate a dircache_entry from memory using freed ones if available
+ */
+static int alloc_entry(struct dircache_entry **res)
+{
+ struct dircache_entry *ce;
+ int idx = dircache.free_list;
+
+ if (idx)
+ {
+ /* reuse a freed entry */
+ ce = get_entry(idx);
+ dircache.free_list = ce->next;
+ }
+ else if (dircache_buf_remaining() > ENTRYSIZE)
+ {
+ /* allocate a new one */
+ idx = ++dircache.numentries;
+ dircache.size += ENTRYSIZE;
+ ce = get_entry(idx);
+ }
+ else
+ {
+ dircache.last_size = 0;
+ *res = NULL;
+ return 0;
+ }
+
+ dircache.sizeused += ENTRYSIZE;
+
+ ce->next = 0;
+ ce->up = 0;
+ ce->down = 0;
+ ce->length = 0;
+ ce->frontier = FRONTIER_SETTLED;
+ ce->serialnum = next_serialnum();
+
+ *res = ce;
+ return idx;
+}
+
+/**
+ * free an entry's allocations in the cache; must not be linked to anything
+ * by this time (orphan!)
+ */
+static void free_orphan_entry(struct dircache_runinfo_volume *dcrivolp,
+ struct dircache_entry *ce, int idx)
+{
+ if (dcrivolp)
+ {
+ /* was an established entry; find any associated resolved binding and
+ dissolve it; bindings are kept strictly synchronized with changes
+ to the storage so a simple serial number comparison is sufficient */
+ struct file_base_binding *prevp = NULL;
+ FOR_EACH_BINDING(dcrivolp->resolved0, p)
+ {
+ if (p == dcrivolp->queued0)
+ break;
+
+ if (ce->serialnum == p->info.dcfile.serialnum)
+ {
+ binding_dissolve(prevp, p);
+ break;
+ }
+
+ prevp = p;
+ }
+ }
+
+ entry_unassign_name(ce);
+
+ /* no serialnum says "it's free" (for cache-wide iterators) */
+ ce->serialnum = 0;
+
+ /* add to free list */
+ ce->next = dircache.free_list;
+ dircache.free_list = idx;
+ dircache.sizeused -= ENTRYSIZE;
+}
+
+/**
+ * allocates a new entry of with the name specified by 'basename'
+ */
+static int create_entry(const char *basename,
+ struct dircache_entry **res)
+{
+ int idx = alloc_entry(res);
+ if (!idx)
+ {
+ logf("size limit reached (entry)");
+ return 0; /* full */
+ }
+
+ if (!entry_assign_name(*res, basename, strlen(basename)))
+ {
+ free_orphan_entry(NULL, *res, idx);
+ logf("size limit reached (name)");
+ return 0; /* really full! */
+ }
+
+ return idx;
+}
+
+/**
+ * unlink the entry at *prevp and adjust the scanner if needed
+ */
+static void remove_entry(struct dircache_runinfo_volume *dcrivolp,
+ struct dircache_entry *ce, int *prevp)
+{
+ /* unlink it from its list */
+ *prevp = ce->next;
+
+ if (dcrivolp)
+ {
+ /* adjust scanner iterator if needed */
+ struct sab *sabp = dcrivolp->sabp;
+ if (sabp)
+ sab_sync_scan(sabp, prevp, &ce->next);
+ }
+}
+
+/**
+ * free the entire subtree in the referenced parent down index
+ */
+static void free_subentries(struct dircache_runinfo_volume *dcrivolp, int *downp)
+{
+ while (1)
+ {
+ int idx = *downp;
+ struct dircache_entry *ce = get_entry(idx);
+ if (!ce)
+ break;
+
+ if ((ce->attr & ATTR_DIRECTORY) && ce->down)
+ free_subentries(dcrivolp, &ce->down);
+
+ remove_entry(dcrivolp, ce, downp);
+ free_orphan_entry(dcrivolp, ce, idx);
+ }
+}
+
+/**
+ * free the specified file entry and its children
+ */
+static void free_file_entry(struct file_base_info *infop)
+{
+ int idx = infop->dcfile.idx;
+ if (idx <= 0)
+ return; /* can't remove a root/invalid */
+
+ struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop);
+
+ struct dircache_entry *ce = get_entry(idx);
+ if ((ce->attr & ATTR_DIRECTORY) && ce->down)
+ {
+ /* gonna get all this contents (normally the "." and "..") */
+ free_subentries(dcrivolp, &ce->down);
+ }
+
+ remove_entry(dcrivolp, ce, get_previdxp(idx));
+ free_orphan_entry(dcrivolp, ce, idx);
+}
+
+/**
+ * insert the new entry into the parent, sorted into position
+ */
+static void insert_file_entry(struct file_base_info *dirinfop,
+ struct dircache_entry *ce)
+{
+ /* DIRCACHE_NATIVE: the entires are sorted into the spot it would be on
+ * the storage medium based upon the directory entry number, in-progress
+ * scans will catch it or miss it in just the same way they would if
+ * directly scanning the disk. If this is behind an init scan, it gets
+ * added anyway; if in front of it, then scanning will compare what it
+ * finds in order to avoid adding a duplicate.
+ *
+ * All others: the order of entries of the host filesystem is not known so
+ * this must be placed at the end so that a build scan won't miss it and
+ * add a duplicate since it will be comparing any entries it finds in front
+ * of it.
+ */
+ int diridx = dirinfop->dcfile.idx;
+ int *nextp = get_downidxp(diridx);
+
+ while (8675309)
+ {
+ struct dircache_entry *nextce = get_entry(*nextp);
+ if (!nextce)
+ break;
+
+#ifdef DIRCACHE_NATIVE
+ if (nextce->direntry > ce->direntry)
+ break;
+
+ /* now, nothing should be equal to ours or that is a bug since it
+ would already exist (and it shouldn't because it's just been
+ created or moved) */
+#endif /* DIRCACHE_NATIVE */
+
+ nextp = &nextce->next;
+ }
+
+ ce->up = diridx;
+ ce->next = *nextp;
+ *nextp = get_index(ce);
+}
+
+/**
+ * unlink the entry from its parent and return its pointer to the caller
+ */
+static struct dircache_entry * remove_file_entry(struct file_base_info *infop)
+{
+ struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop);
+ struct dircache_entry *ce = get_entry(infop->dcfile.idx);
+ remove_entry(dcrivolp, ce, get_previdxp(infop->dcfile.idx));
+ return ce;
+}
+
+/**
+ * set the frontier indicator for the given cache index
+ */
+static void establish_frontier(int idx, uint32_t code)
+{
+ if (idx < 0)
+ {
+ int volume = IF_MV_VOL(-idx - 1);
+ uint32_t val = dircache.dcvol[volume].frontier;
+ if (code & FRONTIER_RENEW)
+ val &= ~FRONTIER_ZONED;
+ dircache.dcvol[volume].frontier = code | (val & FRONTIER_ZONED);
+ }
+ else if (idx > 0)
+ {
+ struct dircache_entry *ce = get_entry(idx);
+ uint32_t val = ce->frontier;
+ if (code & FRONTIER_RENEW)
+ val &= ~FRONTIER_ZONED;
+ ce->frontier = code | (val & FRONTIER_ZONED);
+ }
+}
+
+/**
+ * remove all messages from the queue, responding to anyone waiting
+ */
+static void clear_dircache_queue(void)
{
struct queue_event ev;
-
- if(!queue_peek(&dircache_queue, &ev))
- return false;
-
- switch (ev.id)
- {
- case DIRCACHE_STOP:
- case SYS_USB_CONNECTED:
-#ifdef HAVE_HOTSWAP
- case SYS_FS_CHANGED:
-#endif
- return true;
+
+ while (1)
+ {
+ queue_wait_w_tmo(&dircache_queue, &ev, 0);
+ if (ev.id == SYS_TIMEOUT)
+ break;
+
+ /* respond to any synchronous build queries; since we're already
+ building and thusly allocated, any additional requests can be
+ processed async */
+ if (ev.id == DCM_BUILD)
+ {
+ int *rcp = (int *)ev.data;
+ if (rcp)
+ *rcp = 0;
+ }
}
-
- return false;
}
-#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
-/* scan and build static data (avoid redundancy on stack) */
-static struct
+/**
+ * service dircache_queue during a scan and build
+ */
+static void process_events(void)
{
-#ifdef HAVE_MULTIVOLUME
- int volume;
-#endif
- struct fat_dir *dir;
- struct fat_direntry *direntry;
-}sab;
-
-static int sab_process_dir(unsigned long startcluster, struct dircache_entry *ce)
-{
- /* normally, opendir expects a full fat_dir as parent but in our case,
- * it's completely useless because we don't modify anything
- * WARNING: this heavily relies on current FAT implementation ! */
-
- /* those field are necessary to update the FAT entry in case of modification
- here we don't touch anything so we put dummy values */
- sab.dir->entry = 0;
- sab.dir->entrycount = 0;
- sab.dir->file.firstcluster = 0;
- /* open directory */
- int rc = fat_opendir(IF_MV(sab.volume,) sab.dir, startcluster, sab.dir);
- if(rc < 0)
- {
- logf("fat_opendir failed: %d", rc);
- return rc;
+ yield();
+
+ /* only count externally generated commands */
+ if (!queue_peek_ex(&dircache_queue, NULL, 0, QPEEK_FILTER1(DCM_BUILD)))
+ return;
+
+ clear_dircache_queue();
+
+ /* this reminds us to keep moving after we're done here; a volume we passed
+ up earlier could have been mounted and need refreshing; it just condenses
+ a slew of requests into one and isn't mistaken for an externally generated
+ command */
+ queue_post(&dircache_queue, DCM_PROCEED, 0);
+}
+
+#if defined (DIRCACHE_NATIVE)
+/**
+ * scan and build the contents of a subdirectory
+ */
+static void sab_process_sub(struct sab *sabp)
+{
+ struct fat_direntry *const fatentp = get_dir_fatent();
+ struct filestr_base *const streamp = &sabp->stream;
+ struct file_base_info *const infop = &sabp->info;
+
+ int idx = infop->dcfile.idx;
+ int *downp = get_downidxp(idx);
+ if (!downp)
+ return;
+
+ while (1)
+ {
+ struct sab_component *compp = --sabp->top;
+ compp->idx = idx;
+ compp->downp = downp;
+ compp->prevp = downp;
+
+ /* open directory stream */
+ filestr_base_init(streamp);
+ fileobj_fileop_open(streamp, infop, FO_DIRECTORY);
+ fat_rewind(&streamp->fatstr);
+ uncached_rewinddir_internal(infop);
+
+ const long dircluster = streamp->infop->fatfile.firstcluster;
+
+ /* first pass: read directory */
+ while (1)
+ {
+ if (sabp->stack + 1 < sabp->stackend)
+ {
+ /* release control and process queued events */
+ dircache_unlock();
+ process_events();
+ dircache_lock();
+
+ if (sabp->quit || !compp->idx)
+ break;
+ }
+ /* else an immediate-contents directory scan */
+
+ int rc = uncached_readdir_internal(streamp, infop, fatentp);
+ if (rc <= 0)
+ {
+ if (rc < 0)
+ sabp->quit = true;
+ else
+ compp->prevp = downp; /* rewind list */
+
+ break;
+ }
+
+ struct dircache_entry *ce;
+ int prev = *compp->prevp;
+
+ if (prev)
+ {
+ /* there are entries ahead of us; they will be what was just
+ read or something to be subsequently read; if it belongs
+ ahead of this one, insert a new entry before it; if it's
+ the entry just scanned, do nothing further and continue
+ with the next */
+ ce = get_entry(prev);
+ if (ce->direntry == infop->fatfile.e.entry)
+ {
+ compp->prevp = &ce->next;
+ continue; /* already there */
+ }
+ }
+
+ int idx = create_entry(fatentp->name, &ce);
+ if (!idx)
+ {
+ sabp->quit = true;
+ break;
+ }
+
+ /* link it in */
+ ce->up = compp->idx;
+ ce->next = prev;
+ *compp->prevp = idx;
+ compp->prevp = &ce->next;
+
+ if (!(fatentp->attr & ATTR_DIRECTORY))
+ ce->filesize = fatentp->filesize;
+ else if (!is_dotdir_name(fatentp->name))
+ ce->frontier = FRONTIER_NEW; /* this needs scanning */
+
+ /* copy remaining FS info */
+ ce->direntry = infop->fatfile.e.entry;
+ ce->direntries = infop->fatfile.e.entries;
+ ce->attr = fatentp->attr;
+ ce->firstcluster = fatentp->firstcluster;
+ ce->wrtdate = fatentp->wrtdate;
+ ce->wrttime = fatentp->wrttime;
+
+ /* resolve queued user bindings */
+ infop->fatfile.firstcluster = fatentp->firstcluster;
+ infop->fatfile.dircluster = dircluster;
+ infop->dcfile.idx = idx;
+ infop->dcfile.serialnum = ce->serialnum;
+ binding_resolve(infop);
+ } /* end while */
+
+ close_stream_internal(streamp);
+
+ if (sabp->quit)
+ return;
+
+ establish_frontier(compp->idx, FRONTIER_SETTLED);
+
+ /* second pass: "recurse!" */
+ struct dircache_entry *ce = NULL;
+
+ while (1)
+ {
+ idx = compp->idx && compp > sabp->stack ? *compp->prevp : 0;
+ if (idx)
+ {
+ ce = get_entry(idx);
+ compp->prevp = &ce->next;
+
+ if (ce->frontier != FRONTIER_SETTLED)
+ break;
+ }
+ else
+ {
+ /* directory completed or removed/deepest level */
+ compp = ++sabp->top;
+ if (compp >= sabp->stackend)
+ return; /* scan completed/initial directory removed */
+ }
+ }
+
+ /* even if it got zoned from outside it is about to be scanned in
+ its entirety and may be considered new again */
+ ce->frontier = FRONTIER_NEW;
+ downp = &ce->down;
+
+ /* set up info for next open
+ * IF_MV: "volume" was set when scan began */
+ infop->fatfile.firstcluster = ce->firstcluster;
+ infop->fatfile.dircluster = dircluster;
+ infop->fatfile.e.entry = ce->direntry;
+ infop->fatfile.e.entries = ce->direntries;
+ } /* end while */
+}
+
+/**
+ * scan and build the contents of a directory or volume root
+ */
+static void sab_process_dir(struct file_base_info *infop, bool issab)
+{
+ /* infop should have been fully opened meaning that all its parent
+ directory information is filled in and intact; the binding information
+ should also filled in beforehand */
+
+ /* allocate the stack right now to the max demand */
+ struct dirsab
+ {
+ struct sab sab;
+ struct sab_component stack[issab ? DIRCACHE_MAX_DEPTH : 1];
+ } dirsab;
+ struct sab *sabp = &dirsab.sab;
+
+ sabp->quit = false;
+ sabp->stackend = &sabp->stack[ARRAYLEN(dirsab.stack)];
+ sabp->top = sabp->stackend;
+ sabp->info = *infop;
+
+ if (issab)
+ DCRIVOL(infop)->sabp = sabp;
+
+ establish_frontier(infop->dcfile.idx, FRONTIER_NEW | FRONTIER_RENEW);
+ sab_process_sub(sabp);
+
+ if (issab)
+ DCRIVOL(infop)->sabp = NULL;
+}
+
+/**
+ * scan and build the entire tree for a volume
+ */
+static void sab_process_volume(struct dircache_volume *dcvolp)
+{
+ int rc;
+
+ int volume = IF_MV_VOL(dcvolp - dircache.dcvol);
+ int idx = -volume - 1;
+
+ logf("dircache - building volume %d", volume);
+
+ /* gather everything sab_process_dir() needs in order to begin a scan */
+ struct file_base_info info;
+ rc = fat_open_rootdir(IF_MV(volume,) &info.fatfile);
+ if (rc < 0)
+ {
+ /* probably not mounted */
+ logf("SAB - no root %d: %d", volume, rc);
+ establish_frontier(idx, FRONTIER_NEW);
+ return;
}
-
- /* first pass : read dir */
- struct dircache_entry *first_ce = ce;
-
- /* read through directory */
- while((rc = fat_getnext(sab.dir, sab.direntry)) >= 0 && sab.direntry->name[0])
+
+ info.dcfile.idx = idx;
+ info.dcfile.serialnum = dcvolp->serialnum;
+ sab_process_dir(&info, true);
+}
+
+/**
+ * this function is the back end to the public API's like readdir()
+ */
+int dircache_readdir_dirent(struct filestr_base *stream,
+ struct dirscan_info *scanp,
+ struct dirent *entry)
+{
+ struct file_base_info *dirinfop = stream->infop;
+
+ if (!dirinfop->dcfile.serialnum)
+ goto read_uncached; /* no parent cached => no entries cached */
+
+ struct dircache_volume *dcvolp = DCVOL(dirinfop);
+
+ int diridx = dirinfop->dcfile.idx;
+ unsigned int frontier = diridx <= 0 ? dcvolp->frontier :
+ get_entry(diridx)->frontier;
+
+ /* if not settled, just readthrough; no binding information is needed for
+ this; if it becomes settled, we'll get scan->dcfile caught up and do
+ subsequent reads with the cache */
+ if (frontier != FRONTIER_SETTLED)
+ goto read_uncached;
+
+ int idx = scanp->dcscan.idx;
+ struct dircache_entry *ce;
+
+ unsigned int direntry = scanp->fatscan.entry;
+ while (1)
{
- if(!strcmp(".", sab.direntry->name) ||
- !strcmp("..", sab.direntry->name))
- continue;
+ if (idx == 0 || direntry == FAT_RW_VAL) /* rewound? */
+ {
+ idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down;
+ break;
+ }
- size_t size = strlen(sab.direntry->name) + 1;
- ce->d_name = (d_names_start -= size);
- ce->startcluster = sab.direntry->firstcluster;
- ce->info.size = sab.direntry->filesize;
- ce->info.attribute = sab.direntry->attr;
- ce->info.wrtdate = sab.direntry->wrtdate;
- ce->info.wrttime = sab.direntry->wrttime;
+ /* validate last entry scanned; it might have been replaced between
+ calls or not there at all any more; if so, start the cache reader
+ at the beginning and fast-forward to the correct point as indicated
+ by the FS scanner */
+ ce = get_entry(idx);
+ if (ce && ce->serialnum == scanp->dcscan.serialnum)
+ {
+ idx = ce->next;
+ break;
+ }
- strcpy(ce->d_name, sab.direntry->name);
- dircache_size += size;
-
- if(ce->info.attribute & FAT_ATTR_DIRECTORY)
- dircache_gen_down(ce);
-
- ce = dircache_gen_next(ce);
- if(ce == NULL)
- return -5;
-
- /* When simulator is used, it's only safe to yield here. */
- if(thread_enabled)
+ idx = 0;
+ }
+
+ while (1)
+ {
+ ce = get_entry(idx);
+ if (!ce)
{
- /* Stop if we got an external signal. */
- if(check_event_queue())
- return -6;
- yield();
+ empty_dirent(entry);
+ scanp->fatscan.entries = 0;
+ return 0; /* end of dir */
}
+
+ if (ce->direntry > direntry || direntry == FAT_RW_VAL)
+ break; /* cache reader is caught up to FS scan */
+
+ idx = ce->next;
}
-
- /* add "." and ".." */
- ce->d_name = dot;
- ce->info.attribute = FAT_ATTR_DIRECTORY;
- ce->startcluster = startcluster;
- ce->info.size = 0;
- ce->down = first_ce;
-
- ce = dircache_gen_next(ce);
-
- ce->d_name = dotdot;
- ce->info.attribute = FAT_ATTR_DIRECTORY;
- ce->startcluster = (first_ce->up ? first_ce->up->startcluster : 0);
- ce->info.size = 0;
- ce->down = first_ce->up;
-
- /* second pass: recurse ! */
- ce = first_ce;
-
- while(rc >= 0 && ce)
- {
- if(ce->d_name != NULL && ce->down != NULL && strcmp(ce->d_name, ".")
- && strcmp(ce->d_name, ".."))
- rc = sab_process_dir(ce->startcluster, ce->down);
-
- ce = ce->next;
- }
-
+
+ /* basic dirent information */
+ entry_name_copy(entry->d_name, ce);
+ entry->info.attr = ce->attr;
+ entry->info.size = (ce->attr & ATTR_DIRECTORY) ? 0 : ce->filesize;
+ entry->info.wrtdate = ce->wrtdate;
+ entry->info.wrttime = ce->wrttime;
+
+ /* FS scan information */
+ scanp->fatscan.entry = ce->direntry;
+ scanp->fatscan.entries = ce->direntries;
+
+ /* dircache scan information */
+ scanp->dcscan.idx = idx;
+ scanp->dcscan.serialnum = ce->serialnum;
+
+ /* return whether this needs decoding */
+ int rc = ce->direntries == 1 ? 2 : 1;
+
+ yield();
return rc;
+
+read_uncached:
+ dircache_dcfile_init(&scanp->dcscan);
+ return uncached_readdir_dirent(stream, scanp, entry);
}
-/* used during the generation */
-static struct fat_dir sab_fat_dir;
+/**
+ * rewind the directory scan cursor
+ */
+void dircache_rewinddir_dirent(struct dirscan_info *scanp)
+{
+ uncached_rewinddir_dirent(scanp);
+ dircache_dcfile_init(&scanp->dcscan);
+}
-static int dircache_scan_and_build(IF_MV(int volume,) struct dircache_entry *ce)
+/**
+ * this function is the back end to file API internal scanning, which requires
+ * much more detail about the directory entries; this is allowed to make
+ * assumptions about cache state because the cache will not be altered during
+ * the scan process; an additional important property of internal scanning is
+ * that any available binding information is not ignored even when a scan
+ * directory is frontier zoned.
+ */
+int dircache_readdir_internal(struct filestr_base *stream,
+ struct file_base_info *infop,
+ struct fat_direntry *fatent)
{
- memset(ce, 0, sizeof(struct dircache_entry));
+ /* call with writer exclusion */
+ struct file_base_info *dirinfop = stream->infop;
+ struct dircache_volume *dcvolp = DCVOL(dirinfop);
-#ifdef HAVE_MULTIVOLUME
- if (volume > 0)
- {
- /* broken for 100+ volumes because the format string is too small
- * and we use that for size calculation */
- const size_t max_len = VOL_ENUM_POS + 3;
- ce->d_name = (d_names_start -= max_len);
- snprintf(ce->d_name, max_len, VOL_NAMES, volume);
- dircache_size += max_len;
- ce->info.attribute = FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME;
- ce->info.size = 0;
- append_position = dircache_gen_next(ce);
- ce = dircache_gen_down(ce);
+ /* assume binding "not found" */
+ infop->dcfile.serialnum = 0;
+
+ /* is parent cached? if not, readthrough because nothing is here yet */
+ if (!dirinfop->dcfile.serialnum)
+ return uncached_readdir_internal(stream, infop, fatent);
+
+ int diridx = dirinfop->dcfile.idx;
+ unsigned int frontier = diridx < 0 ?
+ dcvolp->frontier : get_entry(diridx)->frontier;
+
+ int idx = infop->dcfile.idx;
+ if (idx == 0) /* rewound? */
+ idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down;
+ else
+ idx = get_entry(idx)->next;
+
+ struct dircache_entry *ce = get_entry(idx);
+ if (frontier != FRONTIER_SETTLED)
+ {
+ /* the directory being read is reported to be incompletely cached;
+ readthrough and if the entry exists, return it with its binding
+ information; otherwise return the uncached read result while
+ maintaining the last index */
+ int rc = uncached_readdir_internal(stream, infop, fatent);
+ if (rc <= 0 || !ce || ce->direntry > infop->fatfile.e.entry)
+ return rc;
+
+ /* entry matches next one to read */
+ }
+ else if (!ce)
+ {
+ /* end of dir */
+ fat_empty_fat_direntry(fatent);
+ infop->fatfile.e.entries = 0;
+ return 0;
}
-#endif
- struct fat_direntry direntry; /* ditto */
-#ifdef HAVE_MULTIVOLUME
- sab.volume = volume;
-#endif
- sab.dir = &sab_fat_dir;
- sab.direntry = &direntry;
-
- return sab_process_dir(0, ce);
+ /* FS entry information that we maintain */
+ entry_name_copy(fatent->name, ce);
+ fatent->shortname[0] = '\0';
+ fatent->attr = ce->attr;
+ /* file code file scanning does not need time information */
+ fatent->filesize = (ce->attr & ATTR_DIRECTORY) ? 0 : ce->filesize;
+ fatent->firstcluster = ce->firstcluster;
+
+ /* FS entry directory information */
+ infop->fatfile.e.entry = ce->direntry;
+ infop->fatfile.e.entries = ce->direntries;
+
+ /* dircache file binding information */
+ infop->dcfile.idx = idx;
+ infop->dcfile.serialnum = ce->serialnum;
+
+ /* return whether this needs decoding */
+ int rc = ce->direntries == 1 ? 2 : 1;
+
+ if (frontier == FRONTIER_SETTLED)
+ {
+ static long next_yield;
+ if (TIME_AFTER(current_tick, next_yield))
+ {
+ yield();
+ next_yield = current_tick + HZ/50;
+ }
+ }
+
+ return rc;
}
-#elif (CONFIG_PLATFORM & PLATFORM_HOSTED) /* PLATFORM_HOSTED */
+
+/**
+ * rewind the scan position for an internal scan
+ */
+void dircache_rewinddir_internal(struct file_base_info *infop)
+{
+ uncached_rewinddir_internal(infop);
+ dircache_dcfile_init(&infop->dcfile);
+}
+
+#else /* !DIRCACHE_NATIVE (for all others) */
+
+#####################
+/* we require access to the host functions */
+#undef opendir
+#undef readdir
+#undef closedir
+#undef rewinddir
+
static char sab_path[MAX_PATH];
static int sab_process_dir(struct dircache_entry *ce)
{
struct dirent_uncached *entry;
struct dircache_entry *first_ce = ce;
- DIR_UNCACHED *dir = opendir_uncached(sab_path);
+ DIR *dir = opendir(sab_path);
if(dir == NULL)
{
logf("Failed to opendir_uncached(%s)", sab_path);
return -1;
}
-
- while((entry = readdir_uncached(dir)))
+
+ while (1)
{
- if(!strcmp(".", entry->d_name) ||
- !strcmp("..", entry->d_name))
+ if (!(entry = readdir(dir)))
+ break;
+
+ if (IS_DOTDIR_NAME(entry->d_name))
+ {
+ /* add "." and ".." */
+ ce->info.attribute = ATTR_DIRECTORY;
+ ce->info.size = 0;
+ ce->down = entry->d_name[1] == '\0' ? first_ce : first_ce->up;
+ strcpy(ce->dot_d_name, entry->d_name);
continue;
+ }
size_t size = strlen(entry->d_name) + 1;
ce->d_name = (d_names_start -= size);
@@ -436,10 +1666,10 @@ static int sab_process_dir(struct dircache_entry *ce)
strcpy(ce->d_name, entry->d_name);
dircache_size += size;
-
+
if(entry->info.attribute & ATTR_DIRECTORY)
{
- dircache_gen_down(ce);
+ dircache_gen_down(ce, ce);
if(ce->down == NULL)
{
closedir_uncached(dir);
@@ -450,1189 +1680,1504 @@ static int sab_process_dir(struct dircache_entry *ce)
/* append entry */
strlcpy(&sab_path[pathpos], "/", sizeof(sab_path) - pathpos);
strlcpy(&sab_path[pathpos+1], entry->d_name, sizeof(sab_path) - pathpos - 1);
-
+
int rc = sab_process_dir(ce->down);
/* restore path */
sab_path[pathpos] = '\0';
-
+
if(rc < 0)
{
closedir_uncached(dir);
return rc;
}
}
-
- ce = dircache_gen_next(ce);
- if(ce == NULL)
+
+ ce = dircache_gen_entry(ce);
+ if (ce == NULL)
return -5;
-
- /* When simulator is used, it's only safe to yield here. */
- if(thread_enabled)
- {
- /* Stop if we got an external signal. */
- if(check_event_queue())
- return -1;
- yield();
- }
+
+ yield();
}
-
- /* add "." and ".." */
- ce->d_name = dot;
- ce->info.attribute = ATTR_DIRECTORY;
- ce->info.size = 0;
- ce->down = first_ce;
-
- ce = dircache_gen_next(ce);
-
- ce->d_name = dotdot;
- ce->info.attribute = ATTR_DIRECTORY;
- ce->info.size = 0;
- ce->down = first_ce->up;
-
+
closedir_uncached(dir);
- return 0;
+ return 1;
}
-static int dircache_scan_and_build(IF_MV(int volume,) struct dircache_entry *ce)
+static int sab_process_volume(IF_MV(int volume,) struct dircache_entry *ce)
{
- #ifdef HAVE_MULTIVOLUME
- (void) volume;
- #endif
memset(ce, 0, sizeof(struct dircache_entry));
-
strlcpy(sab_path, "/", sizeof sab_path);
return sab_process_dir(ce);
}
-#endif /* PLATFORM_NATIVE */
-/**
- * Internal function to get a pointer to dircache_entry for a given filename.
- * path: Absolute path to a file or directory (see comment)
- * go_down: Returns the first entry of the directory given by the path (see comment)
- *
- * As a a special case, accept path="" as an alias for "/".
- * Also if the path omits the first '/', it will be accepted.
- *
- * * If get_down=true:
- * If path="/", the returned entry is the first of root directory (ie dircache_root)
- * Otherwise, if 'entry' is the returned value when get_down=false,
- * the functions returns entry->down (which can be NULL)
- *
- * * If get_down=false:
- * If path="/chunk_1/chunk_2/.../chunk_n" then this functions returns the entry
- * root_entry()->chunk_1->chunk_2->...->chunk_(n-1)
- * Which means that
- * dircache_get_entry(path)->d_name == chunk_n
- *
- * If path="/", the returned entry is NULL.
- * If the entry doesn't exist, return NULL
- *
- * NOTE: this functions silently handles double '/'
- */
-static struct dircache_entry* dircache_get_entry(const char *path, bool go_down)
-{
- char namecopy[MAX_PATH];
- char* part;
- char* end;
-
- bool at_root = true;
- struct dircache_entry *cache_entry = dircache_root;
-
- strlcpy(namecopy, path, sizeof(namecopy));
-
- for(part = strtok_r(namecopy, "/", &end); part; part = strtok_r(NULL, "/", &end))
- {
- /* If request another chunk, the current entry has to be directory
- * and so cache_entry->down has to be non-NULL/
- * Special case of root because it's already the first entry of the root directory
- *
- * NOTE: this is safe even if cache_entry->down is NULL */
- if(!at_root)
- cache_entry = cache_entry->down;
- else
- at_root = false;
-
- /* scan dir for name */
- while(cache_entry != NULL)
- {
- /* skip unused entries */
- if(cache_entry->d_name == NULL)
- {
- cache_entry = cache_entry->next;
- continue;
- }
- /* compare names */
- if(!strcasecmp(part, cache_entry->d_name))
- break;
- /* go to next entry */
- cache_entry = cache_entry->next;
- }
-
- /* handle not found case */
- if(cache_entry == NULL)
- return NULL;
- }
+int dircache_readdir_r(struct dircache_dirscan *dir, struct dirent *result)
+{
+ if (dircache_state != DIRCACHE_READY)
+ return readdir_r(dir->###########3, result, &result);
- /* NOTE: here cache_entry!=NULL so taking ->down is safe */
- if(go_down)
- return at_root ? cache_entry : cache_entry->down;
- else
- return at_root ? NULL : cache_entry;
-}
+ bool first = dir->dcinfo.scanidx == REWIND_INDEX;
+ struct dircache_entry *ce = get_entry(first ? dir->dcinfo.index :
+ dir->dcinfo.scanidx);
-#ifdef HAVE_EEPROM_SETTINGS
+ ce = first ? ce->down : ce->next;
-#define DIRCACHE_MAGIC 0x00d0c0a1
-struct dircache_maindata {
- long magic;
- long size;
- long entry_count;
- long appflags;
- struct dircache_entry *root_entry;
- char *d_names_start;
-};
+ if (ce == NULL)
+ return 0;
+
+ dir->scanidx = ce - dircache_root;
+
+ strlcpy(result->d_name, ce->d_name, sizeof (result->d_name));
+ result->info = ce->dirinfo;
+
+ return 1;
+}
+
+#endif /* DIRCACHE_* */
/**
- * Function to load the internal cache structure from disk to initialize
- * the dircache really fast and little disk access.
+ * reset the cache for the specified volume
*/
-int dircache_load(void)
+static void reset_volume(IF_MV_NONVOID(int volume))
{
- struct dircache_maindata maindata;
- ssize_t bytes_read;
- int fd;
-
- if (dircache_initialized)
- return -1;
-
- logf("Loading directory cache");
- dircache_size = 0;
-
- fd = open_dircache_file(O_RDONLY, 0);
- if (fd < 0)
- return -2;
-
- bytes_read = read(fd, &maindata, sizeof(struct dircache_maindata));
- if (bytes_read != sizeof(struct dircache_maindata)
- || maindata.magic != DIRCACHE_MAGIC || maindata.size <= 0)
+ FOR_EACH_VOLUME(volume, i)
{
- logf("Dircache file header error");
- close(fd);
- remove_dircache_file();
- return -3;
- }
-
- allocated_size = maindata.size + DIRCACHE_RESERVE;
- dircache_handle = core_alloc_ex("dircache", allocated_size, &ops);
- /* block movement during upcoming I/O */
- dont_move = true;
- dircache_root = core_get_data(dircache_handle);
- ALIGN_BUFFER(dircache_root, allocated_size, sizeof(struct dircache_entry*));
- entry_count = maindata.entry_count;
- appflags = maindata.appflags;
-
- /* read the dircache file into memory,
- * start with the struct dircache_entries */
- ssize_t bytes_to_read = entry_count*sizeof(struct dircache_entry);
- bytes_read = read(fd, dircache_root, bytes_to_read);
-
- if (bytes_read != bytes_to_read)
- {
- logf("Dircache read failed #1");
- return -6;
- }
-
- /* continue with the d_names. Fix up pointers to them if needed */
- bytes_to_read = maindata.size - bytes_to_read;
- d_names_start = (char*)dircache_root + allocated_size - bytes_to_read;
- bytes_read = read(fd, d_names_start, bytes_to_read);
- close(fd);
- remove_dircache_file();
- if (bytes_read != bytes_to_read)
- {
- logf("Dircache read failed #2");
- return -7;
+ struct dircache_volume *dcvolp = DCVOL(i);
+
+ if (dcvolp->status == DIRCACHE_IDLE)
+ continue; /* idle => nothing happening there */
+
+ struct dircache_runinfo_volume *dcrivolp = DCRIVOL(i);
+
+ /* stop any scan and build on this one */
+ if (dcrivolp->sabp)
+ dcrivolp->sabp->quit = true;
+
+ #ifdef HAVE_MULTIVOLUME
+ /* if this call is for all volumes, subsequent code will just reset
+ the cache memory usage and the freeing of individual entries may
+ be skipped */
+ if (volume >= 0)
+ free_subentries(NULL, &dcvolp->root_down);
+ else
+ #endif
+ binding_dissolve_volume(dcrivolp);
+
+ /* set it back to unscanned */
+ dcvolp->status = DIRCACHE_IDLE;
+ dcvolp->frontier = FRONTIER_NEW;
+ dcvolp->root_down = 0;
+ dcvolp->build_ticks = 0;
+ dcvolp->serialnum = 0;
}
+}
- d_names_end = d_names_start + bytes_read;
- dot = d_names_end - sizeof(".");
- dotdot = dot - sizeof("..");
+/**
+ * reset the entire cache state for all volumes
+ */
+static void reset_cache(void)
+{
+ if (!dircache_runinfo.handle)
+ return; /* no buffer => nothing cached */
+
+ /* blast all the volumes */
+ reset_volume(IF_MV(-1));
+
+#ifdef DIRCACHE_DUMPSTER
+ dumpster_clean_buffer(dircache_runinfo.p + ENTRYSIZE,
+ dircache_runinfo.bufsize);
+#endif /* DIRCACHE_DUMPSTER */
+
+ /* reset the memory */
+ dircache.free_list = 0;
+ dircache.size = 0;
+ dircache.sizeused = 0;
+ dircache.numentries = 0;
+ dircache.names = dircache_runinfo.bufsize + ENTRYSIZE;
+ dircache.sizenames = 0;
+ dircache.namesfree = 0;
+ dircache.nextnamefree = 0;
+ *get_name(dircache.names - 1) = 0;
+ /* dircache.last_serialnum stays */
+ /* dircache.reserve_used stays */
+ /* dircache.last_size stays */
+}
- /* d_names are in reverse order, so the last entry points to the first string */
- ptrdiff_t offset_d_names = maindata.d_names_start - d_names_start;
- ptrdiff_t offset_entries = maindata.root_entry - dircache_root;
- offset_entries *= sizeof(struct dircache_entry); /* make it bytes */
+/**
+ * checks each "idle" volume and builds it
+ */
+static void build_volumes(void)
+{
+ buffer_lock();
- /* offset_entries is less likely to differ, so check if it's 0 in the loop
- * offset_d_names however is almost always non-zero, since dircache_save()
- * creates a file which causes the reserve buffer to be used. since
- * we allocate a new, empty DIRCACHE_RESERVE here, the strings are
- * farther behind */
- if (offset_entries != 0 || offset_d_names != 0)
+ for (int i = 0; i < NUM_VOLUMES; i++)
{
- for(unsigned i = 0; i < entry_count; i++)
- {
- if (dircache_root[i].d_name)
- dircache_root[i].d_name -= offset_d_names;
+ /* this does reader locking but we already own that */
+ if (!volume_ismounted(IF_MV(i)))
+ continue;
- if (offset_entries == 0)
- continue;
- if (dircache_root[i].next_char)
- dircache_root[i].next_char -= offset_entries;
- if (dircache_root[i].up_char)
- dircache_root[i].up_char -= offset_entries;
- if (dircache_root[i].down_char)
- dircache_root[i].down_char -= offset_entries;
- }
+ struct dircache_volume *dcvolp = DCVOL(i);
+
+ /* can't already be "scanning" because that's us; doesn't retry
+ "ready" volumes */
+ if (dcvolp->status == DIRCACHE_READY)
+ continue;
+
+ /* measure how long it takes to build the cache for each volume */
+ if (!dcvolp->serialnum)
+ dcvolp->serialnum = next_serialnum();
+
+ dcvolp->status = DIRCACHE_SCANNING;
+ dcvolp->start_tick = current_tick;
+
+ sab_process_volume(dcvolp);
+
+ if (dircache_runinfo.suspended)
+ break;
+
+ /* whatever happened, it's ready unless reset */
+ dcvolp->build_ticks = current_tick - dcvolp->start_tick;
+ dcvolp->status = DIRCACHE_READY;
}
- /* Cache successfully loaded. */
- dircache_size = maindata.size;
- reserve_used = 0;
- logf("Done, %ld KiB used", dircache_size / 1024);
- dircache_initialized = true;
- memset(fd_bindings, 0, sizeof(fd_bindings));
- dont_move = false;
+ size_t reserve_used = reserve_buf_used();
+ if (reserve_used > dircache.reserve_used)
+ dircache.reserve_used = reserve_used;
+
+ if (DIRCACHE_STUFFED(reserve_used))
+ dircache.last_size = 0; /* reset */
+ else if (dircache.size > dircache.last_size ||
+ dircache.last_size - dircache.size > DIRCACHE_RESERVE)
+ dircache.last_size = dircache.size;
+
+ logf("Done, %ld KiB used", dircache.size / 1024);
- return 0;
+ buffer_unlock();
}
/**
- * Function to save the internal cache stucture to disk for fast loading
- * on boot.
+ * allocate buffer and return whether or not a synchronous build should take
+ * place; if 'realloced' is NULL, it's just a query about what will happen
*/
-int dircache_save(void)
+static int prepare_build(bool *realloced)
{
- struct dircache_maindata maindata;
- int fd;
- unsigned long bytes_written;
+ /* called holding dircache lock */
+ size_t size = dircache.last_size;
- remove_dircache_file();
-
- if (!dircache_initialized)
- return -1;
+#ifdef HAVE_EEPROM_SETTINGS
+ if (realloced)
+ {
+ dircache_unlock();
+ remove_dircache_file();
+ dircache_lock();
- logf("Saving directory cache");
- dont_move = true;
- fd = open_dircache_file(O_WRONLY | O_CREAT | O_TRUNC, 0666);
+ if (dircache_runinfo.suspended)
+ return -1;
+ }
+#endif /* HAVE_EEPROM_SETTINGS */
+
+ bool stuffed = DIRCACHE_STUFFED(dircache.reserve_used);
+ if (dircache_runinfo.bufsize > size && !stuffed)
+ {
+ if (realloced)
+ *realloced = false;
- maindata.magic = DIRCACHE_MAGIC;
- maindata.size = dircache_size;
- maindata.root_entry = dircache_root;
- maindata.d_names_start = d_names_start;
- maindata.entry_count = entry_count;
- maindata.appflags = appflags;
+ return 0; /* start a transparent rebuild */
+ }
- /* Save the info structure */
- bytes_written = write(fd, &maindata, sizeof(struct dircache_maindata));
- if (bytes_written != sizeof(struct dircache_maindata))
+ int syncbuild = size > 0 && !stuffed ? 0 : 1;
+
+ if (!realloced)
+ return syncbuild;
+
+ if (syncbuild)
{
- close(fd);
- logf("dircache: write failed #1");
- return -2;
+ /* start a non-transparent rebuild */
+ /* we'll use the entire audiobuf to allocate the dircache */
+ size = audio_buffer_available() + dircache_runinfo.bufsize;
+ /* try to allocate at least the min and no more than the limit */
+ size = MAX(DIRCACHE_MIN, MIN(size, DIRCACHE_LIMIT));
}
+ else
+ {
+ /* start a transparent rebuild */
+ size = MAX(size, DIRCACHE_RESERVE) + DIRCACHE_RESERVE*2;
+ }
+
+ *realloced = true;
+ reset_cache();
- /* Dump whole directory cache to disk
- * start by writing the dircache_entries */
- size_t bytes_to_write = entry_count*sizeof(struct dircache_entry);
- bytes_written = write(fd, dircache_root, bytes_to_write);
- if (bytes_written != bytes_to_write)
+ buffer_lock();
+
+ int handle = reset_buffer();
+ dircache_unlock();
+
+ if (handle > 0)
+ core_free(handle);
+
+ handle = alloc_cache(size);
+
+ dircache_lock();
+
+ if (dircache_runinfo.suspended && handle > 0)
{
- logf("dircache: write failed #2");
- return -3;
+ /* if we got suspended, don't keep this huge buffer around */
+ dircache_unlock();
+ core_free(handle);
+ handle = 0;
+ dircache_lock();
}
- /* continue with the d_names */
- bytes_to_write = d_names_end - d_names_start;
- bytes_written = write(fd, d_names_start, bytes_to_write);
- close(fd);
- if (bytes_written != bytes_to_write)
+ if (handle <= 0)
{
- logf("dircache: write failed #3");
- return -4;
+ buffer_unlock();
+ return -1;
}
- dont_move = false;
- return 0;
+ set_buffer(handle, size);
+ buffer_unlock();
+
+ return syncbuild;
}
-#endif /* HAVE_EEPROM_SETTINGS */
/**
- * Internal function which scans the disk and creates the dircache structure.
+ * compact the dircache buffer after a successful full build
*/
-static int dircache_do_rebuild(void)
+static void compact_cache(void)
{
- struct dircache_entry* root_entry;
- unsigned int start_tick;
- int i;
-
- /* Measure how long it takes build the cache. */
- start_tick = current_tick;
- dircache_initializing = true;
- appflags = 0;
+ /* called holding dircache lock */
+ if (dircache_runinfo.suspended)
+ return;
- /* reset dircache and alloc root entry */
- entry_count = 0;
- root_entry = allocate_entry();
- dont_move = true;
+ void *p = dircache_runinfo.p + ENTRYSIZE;
+ size_t leadsize = dircache.numentries * ENTRYSIZE + DIRCACHE_RESERVE;
-#ifdef HAVE_MULTIVOLUME
- append_position = root_entry;
+ void *dst = p + leadsize;
+ void *src = get_name(dircache.names - 1);
+ if (dst >= src)
+ return; /* cache got bigger than expected; never mind that */
+
+ /* slide the names up in memory */
+ memmove(dst, src, dircache.sizenames + 2);
- for (i = NUM_VOLUMES; i >= 0; i--)
+ /* fix up name indexes */
+ ptrdiff_t offset = dst - src;
+
+ FOR_EACH_CACHE_ENTRY(ce)
{
- if (fat_ismounted(i))
- {
-#endif
- cpu_boost(true);
-#ifdef HAVE_MULTIVOLUME
- if (dircache_scan_and_build(IF_MV(i,) append_position) < 0)
-#else
- if (dircache_scan_and_build(IF_MV(0,) root_entry) < 0)
-#endif /* HAVE_MULTIVOLUME */
- {
- logf("dircache_scan_and_build failed");
- cpu_boost(false);
- dircache_size = 0;
- dircache_initializing = false;
- dont_move = false;
- return -2;
- }
- cpu_boost(false);
-#ifdef HAVE_MULTIVOLUME
- }
+ if (!ce->tinyname)
+ ce->name += offset;
}
-#endif
- logf("Done, %ld KiB used", dircache_size / 1024);
-
- dircache_initialized = true;
- dircache_initializing = false;
- cache_build_ticks = current_tick - start_tick;
-
- /* Initialized fd bindings. */
- memset(fd_bindings, 0, sizeof(fd_bindings));
- for (i = 0; i < fdbind_idx; i++)
- dircache_bind(fdbind_cache[i].fd, fdbind_cache[i].path);
- fdbind_idx = 0;
-
- if (thread_enabled)
- {
- if (allocated_size - dircache_size < DIRCACHE_RESERVE)
- reserve_used = DIRCACHE_RESERVE - (allocated_size - dircache_size);
- }
-
- dont_move = false;
- return 1;
-}
+ dircache.names += offset;
-/*
- * Free all associated resources, if any */
-static void dircache_free(void)
-{
- if (dircache_handle > 0)
- dircache_handle = core_free(dircache_handle);
- dircache_size = allocated_size = 0;
+ /* assumes beelzelib doesn't do things like calling callbacks or changing
+ the pointer as a result of the shrink operation; it doesn't as of now
+ but if it ever does that may very well cause deadlock problems since
+ we're holding filesystem locks */
+ size_t newsize = leadsize + dircache.sizenames + 1;
+ core_shrink(dircache_runinfo.handle, p, newsize + 1);
+ dircache_runinfo.bufsize = newsize;
+ dircache.reserve_used = 0;
}
/**
- * Internal thread that controls transparent cache building.
+ * internal thread that controls cache building; exits when no more requests
+ * are pending or the cache is suspended
*/
static void dircache_thread(void)
{
struct queue_event ev;
+ /* calls made within the loop reopen the lock */
+ dircache_lock();
+
while (1)
{
- queue_wait(&dircache_queue, &ev);
-
- switch (ev.id)
+ queue_wait_w_tmo(&dircache_queue, &ev, 0);
+ if (ev.id == SYS_TIMEOUT || dircache_runinfo.suspended)
{
-#ifdef HAVE_HOTSWAP
- case SYS_FS_CHANGED:
- if (!dircache_initialized)
- break;
- dircache_initialized = false;
-#endif
- case DIRCACHE_BUILD:
- thread_enabled = true;
- if (dircache_do_rebuild() < 0)
- dircache_free();
- thread_enabled = false;
- break ;
-
- case DIRCACHE_STOP:
- logf("Stopped the rebuilding.");
- dircache_initialized = false;
- break ;
-
- case SYS_USB_CONNECTED:
- usb_acknowledge(SYS_USB_CONNECTED_ACK);
- usb_wait_for_disconnect(&dircache_queue);
- break ;
+ /* nothing left to do/suspended */
+ if (dircache_runinfo.suspended)
+ clear_dircache_queue();
+ dircache_runinfo.thread_done = true;
+ break;
}
- }
-}
-static void generate_dot_d_names(void)
-{
- dot = (d_names_start -= sizeof("."));
- dotdot = (d_names_start -= sizeof(".."));
- dircache_size += sizeof(".") + sizeof("..");
- strcpy(dot, ".");
- strcpy(dotdot, "..");
+ /* background-only builds are not allowed if a synchronous build is
+ required first; test what needs to be done and if it checks out,
+ do it for real below */
+ int *rcp = (int *)ev.data;
+
+ if (!rcp && prepare_build(NULL) > 0)
+ continue;
+
+ bool realloced;
+ int rc = prepare_build(&realloced);
+ if (rcp)
+ *rcp = rc;
+
+ if (rc < 0)
+ continue;
+
+ trigger_cpu_boost();
+ build_volumes();
+
+ /* if it was reallocated, compact it */
+ if (realloced)
+ compact_cache();
+ }
+
+ dircache_unlock();
}
/**
- * Start scanning the disk to build the dircache.
- * Either transparent or non-transparent build method is used.
+ * post a scan and build message to the thread, starting it if required
*/
-int dircache_build(int last_size)
+static bool dircache_thread_post(int volatile *rcp)
{
- if (dircache_initialized || thread_enabled)
- return -3;
+ if (dircache_runinfo.thread_done)
+ {
+ /* mustn't recreate until it exits so that the stack isn't reused */
+ thread_wait(dircache_runinfo.thread_id);
+ dircache_runinfo.thread_done = false;
+ dircache_runinfo.thread_id = create_thread(
+ dircache_thread, dircache_stack, sizeof (dircache_stack), 0,
+ dircache_thread_name IF_PRIO(, PRIORITY_BACKGROUND)
+ IF_COP(, CPU));
+ }
- logf("Building directory cache");
-#ifdef HAVE_EEPROM_SETTINGS
- remove_dircache_file();
-#endif
+ bool started = dircache_runinfo.thread_id != 0;
- /* Background build, dircache has been previously allocated and */
- if (allocated_size > MAX(last_size, 0))
- {
- d_names_start = d_names_end;
- dircache_size = 0;
- reserve_used = 0;
- thread_enabled = true;
- dircache_initializing = true;
- generate_dot_d_names();
-
- queue_post(&dircache_queue, DIRCACHE_BUILD, 0);
- return 2;
- }
-
- /* start by freeing, as we possibly re-allocate */
- dircache_free();
-
- if (last_size > DIRCACHE_RESERVE && last_size < DIRCACHE_LIMIT )
- {
- allocated_size = last_size + DIRCACHE_RESERVE;
- dircache_handle = core_alloc_ex("dircache", allocated_size, &ops);
- dircache_root = core_get_data(dircache_handle);
- ALIGN_BUFFER(dircache_root, allocated_size, sizeof(struct dircache_entry*));
- d_names_start = d_names_end = ((char*)dircache_root)+allocated_size-1;
- dircache_size = 0;
- thread_enabled = true;
- generate_dot_d_names();
-
- /* Start a transparent rebuild. */
- queue_post(&dircache_queue, DIRCACHE_BUILD, 0);
- return 3;
- }
-
- /* We'll use the entire audiobuf to allocate the dircache
- * struct dircache_entrys are allocated from the beginning
- * and their corresponding d_name from the end
- * after generation the buffer will be compacted with DIRCACHE_RESERVE
- * free bytes inbetween */
- size_t available = audio_buffer_available();
- /* try to allocate at least 1MB, the more the better */
- if (available < 1<<20) available = 1<<20;
- if (available > DIRCACHE_LIMIT) available = DIRCACHE_LIMIT;
-
- dircache_handle = core_alloc_ex("dircache", available, &ops);
- if (dircache_handle <= 0)
- return -1; /* that was not successful, should try rebooting */
- char* buf = core_get_data(dircache_handle);
- dircache_root = (struct dircache_entry*)ALIGN_UP(buf,
- sizeof(struct dircache_entry*));
- d_names_start = d_names_end = buf + available - 1;
- dircache_size = 0;
- generate_dot_d_names();
-
- /* Start a non-transparent rebuild. */
- int res = dircache_do_rebuild();
- if (res < 0)
- goto fail;
-
- /* now compact the dircache buffer */
- char* dst = ((char*)&dircache_root[entry_count] + DIRCACHE_RESERVE);
- ptrdiff_t offset = d_names_start - dst;
- if (offset <= 0) /* something went wrong */
- {
- res = -1;
- goto fail;
- }
-
- /* memmove d_names down, there's a possibility of overlap
- * equivaent to dircache_size - entry_count*sizeof(struct dircache_entry) */
- ptrdiff_t size_to_move = d_names_end - d_names_start;
- memmove(dst, d_names_start, size_to_move);
-
- /* fix up pointers to the d_names */
- for(unsigned i = 0; i < entry_count; i++)
- dircache_root[i].d_name -= offset;
-
- d_names_start -= offset;
- d_names_end -= offset;
- dot -= offset;
- dotdot -= offset;
-
- /* equivalent to dircache_size + DIRCACHE_RESERVE + align */
- allocated_size = (d_names_end - buf);
- reserve_used = 0;
-
- core_shrink(dircache_handle, dircache_root, allocated_size);
- return res;
-fail:
- dircache_disable();
- return res;
-}
-
-/**
- * Main initialization function that must be called before any other
- * operations within the dircache.
- */
-void dircache_init(void)
-{
- int i;
- int thread_id __attribute__((unused));
-
- dircache_initialized = false;
- dircache_initializing = false;
-
- memset(opendirs, 0, sizeof(opendirs));
- for (i = 0; i < MAX_OPEN_DIRS; i++)
- {
- opendirs[i].theent.d_name = opendir_dnames[i];
- }
-
- queue_init(&dircache_queue, true);
- thread_id = create_thread(dircache_thread, dircache_stack,
- sizeof(dircache_stack), 0, dircache_thread_name
- IF_PRIO(, PRIORITY_BACKGROUND)
- IF_COP(, CPU));
-#ifdef HAVE_IO_PRIORITY
- thread_set_io_priority(thread_id,IO_PRIORITY_BACKGROUND);
-#endif
+ if (started)
+ queue_post(&dircache_queue, DCM_BUILD, (intptr_t)rcp);
+ return started;
}
/**
- * Returns true if dircache has been initialized and is ready to be used.
+ * wait for the dircache thread to finish building; intended for waiting for a
+ * non-transparent build to finish when dircache_resume() returns > 0
*/
-bool dircache_is_enabled(void)
+void dircache_wait(void)
{
- return dircache_initialized;
+ thread_wait(dircache_runinfo.thread_id);
}
/**
- * Returns true if dircache is being initialized.
+ * call after mounting a volume or all volumes
*/
-bool dircache_is_initializing(void)
+void dircache_mount(void)
{
- return dircache_initializing || thread_enabled;
+ /* call with writer exclusion */
+ if (dircache_runinfo.suspended)
+ return;
+
+ dircache_thread_post(NULL);
}
/**
- * Set application flags used to determine if dircache is still intact.
+ * call after unmounting a volume; specifying < 0 for all or >= 0 for the
+ * specific one
*/
-void dircache_set_appflag(long mask)
+void dircache_unmount(IF_MV_NONVOID(int volume))
{
- appflags |= mask;
+ /* call with writer exclusion */
+ if (dircache_runinfo.suspended)
+ return;
+
+#ifdef HAVE_MULTIVOLUME
+ if (volume >= 0)
+ reset_volume(volume);
+ else
+#endif /* HAVE_MULTIVOLUME */
+ reset_cache();
+}
+
+/* backend to dircache_suspend() and dircache_disable() */
+static void dircache_suspend_internal(bool freeit)
+{
+ if (dircache_runinfo.suspended++ > 0 &&
+ (!freeit || dircache_runinfo.handle <= 0))
+ return;
+
+ unsigned int thread_id = dircache_runinfo.thread_id;
+
+ reset_cache();
+ clear_dircache_queue();
+
+ /* grab the buffer away into our control; the cache won't need it now */
+ int handle = 0;
+ if (freeit)
+ handle = reset_buffer();
+
+ dircache_unlock();
+
+ if (handle > 0)
+ core_free(handle);
+
+ thread_wait(thread_id);
+
+ dircache_lock();
+}
+
+/* backend to dircache_resume() and dircache_enable() */
+static int dircache_resume_internal(bool build_now)
+{
+ int volatile rc = 0;
+
+ if (dircache_runinfo.suspended == 0 || --dircache_runinfo.suspended == 0)
+ rc = build_now && dircache_runinfo.enabled ? 1 : 0;
+
+ if (rc)
+ rc = dircache_thread_post(&rc) ? INT_MIN : -1;
+
+ if (rc == INT_MIN)
+ {
+ dircache_unlock();
+
+ while (rc == INT_MIN) /* poll for response */
+ sleep(0);
+
+ dircache_lock();
+ }
+
+ return rc < 0 ? rc * 10 - 2 : rc;
}
/**
- * Get application flags used to determine if dircache is still intact.
+ * service to dircache_enable() and dircache_load(); "build_now" starts a build
+ * immediately if the cache was not enabled
*/
-bool dircache_get_appflag(long mask)
+static int dircache_enable_internal(bool build_now)
{
- return dircache_is_enabled() && (appflags & mask);
+ int rc = 0;
+
+ if (!dircache_runinfo.enabled)
+ {
+ dircache_runinfo.enabled = true;
+ rc = dircache_resume_internal(build_now);
+ }
+
+ return rc;
}
/**
- * Returns the current number of entries (directories and files) in the cache.
+ * service to dircache_disable()
*/
-int dircache_get_entry_count(void)
+static void dircache_disable_internal(void)
{
- return entry_count;
+ if (dircache_runinfo.enabled)
+ {
+ dircache_runinfo.enabled = false;
+ dircache_suspend_internal(true);
+ }
}
/**
- * Returns the allocated space for dircache (without reserve space).
+ * disables dircache without freeing the buffer (so it can be re-enabled
+ * afterwards with dircache_resume(); usually called when accepting an USB
+ * connection
*/
-int dircache_get_cache_size(void)
+void dircache_suspend(void)
{
- return dircache_is_enabled() ? dircache_size : 0;
+ dircache_lock();
+ dircache_suspend_internal(false);
+ dircache_unlock();
}
/**
- * Returns how many bytes of the reserve allocation for live cache
- * updates have been used.
+ * re-enables the dircache if previously suspended by dircache_suspend
+ * or dircache_steal_buffer(), re-using the already allocated buffer if
+ * available
+ *
+ * returns: 0 if the background build is started or dircache is still
+ * suspended
+ * > 0 if the build is non-background
+ * < 0 upon failure
*/
-int dircache_get_reserve_used(void)
+int dircache_resume(void)
{
- return dircache_is_enabled() ? reserve_used : 0;
+ dircache_lock();
+ int rc = dircache_resume_internal(true);
+ dircache_unlock();
+ return rc;
}
/**
- * Returns the time in kernel ticks that took to build the cache.
+ * as dircache_resume() but globally enables it; called by settings and init
*/
-int dircache_get_build_ticks(void)
+int dircache_enable(void)
{
- return dircache_is_enabled() ? cache_build_ticks : 0;
+ dircache_lock();
+ int rc = dircache_enable_internal(true);
+ dircache_unlock();
+ return rc;
}
/**
- * Disables dircache without freeing the buffer (so it can be re-enabled
- * afterwards with dircache_resume() or dircache_build()), usually
- * called when accepting an usb connection */
-void dircache_suspend(void)
+ * as dircache_suspend() but also frees the buffer; usually called on shutdown
+ * or when deactivated
+ */
+void dircache_disable(void)
{
- int i;
- bool cache_in_use;
-
- if (thread_enabled)
- queue_post(&dircache_queue, DIRCACHE_STOP, 0);
-
- while (thread_enabled)
- sleep(1);
- dircache_initialized = false;
-
- logf("Waiting for cached dirs to release");
- do {
- cache_in_use = false;
- for (i = 0; i < MAX_OPEN_DIRS; i++) {
- if (!opendirs[i].regulardir && opendirs[i].busy)
- {
- cache_in_use = true;
- sleep(1);
- break ;
- }
- }
- } while (cache_in_use) ;
-
- logf("Cache released");
- entry_count = 0;
+ dircache_lock();
+ dircache_disable_internal();
+ dircache_unlock();
}
/**
- * Re-enables the dircache if previous suspended by dircache_suspend
- * or dircache_steal_buffer(), re-using the already allocated buffer
- *
- * Returns true if the background build is started, false otherwise
- * (e.g. if no buffer was previously allocated)
+ * have dircache give up its allocation; call dircache_resume() to restart it
*/
-bool dircache_resume(void)
+void dircache_free_buffer(void)
{
- bool ret = allocated_size > 0;
- if (ret) /* only resume if already allocated */
- ret = (dircache_build(0) > 0);
+ dircache_lock();
+ dircache_suspend_internal(true);
+ dircache_unlock();
+}
+
+
+/** Dircache live updating **/
- return (allocated_size > 0);
+/**
+ * obtain binding information for the file's root volume; this is the starting
+ * point for internal path parsing and binding
+ */
+void dircache_get_rootinfo(struct file_base_info *infop)
+{
+ int volume = BASEINFO_VOL(infop);
+ struct dircache_volume *dcvolp = DCVOL(volume);
+
+ if (dcvolp->serialnum)
+ {
+ /* root has a binding */
+ infop->dcfile.idx = -volume - 1;
+ infop->dcfile.serialnum = dcvolp->serialnum;
+ }
+ else
+ {
+ /* root is idle */
+ dircache_dcfile_init(&infop->dcfile);
+ }
}
/**
- * Disables the dircache entirely. Usually called on shutdown or when
- * deactivated
+ * called by file code when the first reference to a file or directory is
+ * opened
*/
-void dircache_disable(void)
+void dircache_bind_file(struct file_base_binding *bindp)
+{
+ /* requires write exclusion */
+ logf("dc open: %u", (unsigned int)bindp->info.dcfile.serialnum);
+ binding_open(bindp);
+}
+
+/**
+ * called by file code when the last reference to a file or directory is
+ * closed
+ */
+void dircache_unbind_file(struct file_base_binding *bindp)
{
- dircache_suspend();
- dircache_free();
+ /* requires write exclusion */
+ logf("dc close: %u", (unsigned int)bindp->info.dcfile.serialnum);
+ binding_close(bindp);
}
/**
- * Steal the allocated dircache buffer and disable dircache.
+ * called by file code when a file is newly created
*/
-void* dircache_steal_buffer(size_t *size)
+void dircache_fileop_create(struct file_base_info *dirinfop,
+ struct file_base_binding *bindp,
+ const char *basename,
+ const struct dirinfo_native *dinp)
{
- dircache_suspend();
- if (dircache_size == 0)
+ /* requires write exclusion */
+ logf("dc create: %u \"%s\"",
+ (unsigned int)bindp->info.dcfile.serialnum, basename);
+
+ if (!dirinfop->dcfile.serialnum)
{
- *size = 0;
- return NULL;
+ /* no parent binding => no child binding */
+ return;
}
- /* since we give up the buffer (without freeing), it must not move anymore */
- dont_move = true;
- *size = dircache_size + (DIRCACHE_RESERVE-reserve_used);
-
- return dircache_root;
+ struct dircache_entry *ce;
+ int idx = create_entry(basename, &ce);
+ if (idx == 0)
+ {
+ /* failed allocation; parent cache contents are not complete */
+ establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED);
+ return;
+ }
+
+ struct file_base_info *infop = &bindp->info;
+
+#ifdef DIRCACHE_NATIVE
+ ce->firstcluster = infop->fatfile.firstcluster;
+ ce->direntry = infop->fatfile.e.entry;
+ ce->direntries = infop->fatfile.e.entries;
+ ce->wrtdate = dinp->wrtdate;
+ ce->wrttime = dinp->wrttime;
+#else
+ ce->mtime = dinp->mtime;
+#endif
+ ce->attr = dinp->attr;
+ if (!(dinp->attr & ATTR_DIRECTORY))
+ ce->filesize = dinp->size;
+
+ insert_file_entry(dirinfop, ce);
+
+ /* file binding will have been queued when it was opened; just resolve */
+ infop->dcfile.idx = idx;
+ infop->dcfile.serialnum = ce->serialnum;
+ binding_resolve(infop);
+
+ if ((dinp->attr & ATTR_DIRECTORY) && !is_dotdir_name(basename))
+ {
+ /* scan-in the contents of the new directory at this level only */
+ buffer_lock();
+ sab_process_dir(infop, false);
+ buffer_unlock();
+ }
}
/**
- * Usermode function to return dircache_entry index to the given path.
+ * called by file code when a file or directory is removed
*/
-static int dircache_get_entry_id_ex(const char *filename, bool go_down)
+void dircache_fileop_remove(struct file_base_binding *bindp)
{
- if (!dircache_initialized || filename == NULL)
- return -1;
-
- struct dircache_entry* res = dircache_get_entry(filename, go_down);
- return res ? res - dircache_root : -1;
-}
+ /* requires write exclusion */
+ logf("dc remove: %u\n", (unsigned int)bindp->info.dcfile.serialnum);
-int dircache_get_entry_id(const char* filename)
-{
- return dircache_get_entry_id_ex(filename, false);
+ if (!bindp->info.dcfile.serialnum)
+ return; /* no binding yet */
+
+ free_file_entry(&bindp->info);
+
+ /* if binding was resolved; it should now be queued via above call */
}
/**
- * Internal: Get the startcluster for the index
+ * called by file code when a file is renamed
*/
-long _dircache_get_entry_startcluster(int id)
+void dircache_fileop_rename(struct file_base_info *dirinfop,
+ struct file_base_binding *bindp,
+ const char *basename)
{
- return get_entry(id)->startcluster;
+ /* requires write exclusion */
+ logf("dc rename: %u \"%s\"",
+ (unsigned int)bindp->info.dcfile.serialnum, basename);
+
+ if (!dirinfop->dcfile.serialnum)
+ {
+ /* new parent directory not cached; there is nowhere to put it so
+ nuke it */
+ if (bindp->info.dcfile.serialnum)
+ free_file_entry(&bindp->info);
+ /* else no entry anyway */
+
+ return;
+ }
+
+ if (!bindp->info.dcfile.serialnum)
+ {
+ /* binding not resolved on the old file but it's going into a resolved
+ parent which means the parent would be missing an entry in the cache;
+ downgrade the parent */
+ establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED);
+ return;
+ }
+
+ /* unlink the entry but keep it; it needs to be re-sorted since the
+ underlying FS probably changed the order */
+ struct dircache_entry *ce = remove_file_entry(&bindp->info);
+
+#ifdef DIRCACHE_NATIVE
+ /* update other name-related information before inserting */
+ ce->direntry = bindp->info.fatfile.e.entry;
+ ce->direntries = bindp->info.fatfile.e.entries;
+#endif
+
+ /* place it into its new home */
+ insert_file_entry(dirinfop, ce);
+
+ /* lastly, update the entry name itself */
+ if (entry_reassign_name(ce, basename))
+ {
+ /* it's not really the same one now so re-stamp it */
+ dc_serial_t serialnum = next_serialnum();
+ ce->serialnum = serialnum;
+ bindp->info.dcfile.serialnum = serialnum;
+ }
+ else
+ {
+ /* it cannot be kept around without a valid name */
+ free_file_entry(&bindp->info);
+ establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED);
+ }
}
/**
- * Internal: Get the struct dirinfo for the index
+ * called by file code to synchronize file entry information
*/
-struct dirinfo* _dircache_get_entry_dirinfo(int id)
+void dircache_fileop_sync(struct file_base_binding *bindp,
+ const struct dirinfo_native *dinp)
{
- return &get_entry(id)->info;
+ /* requires write exclusion */
+ struct file_base_info *infop = &bindp->info;
+ logf("dc sync: %u\n", (unsigned int)infop->dcfile.serialnum);
+
+ if (!infop->dcfile.serialnum)
+ return; /* binding unresolved */
+
+ struct dircache_entry *ce = get_entry(infop->dcfile.idx);
+ if (!ce)
+ {
+ logf(" bad index %d", infop->dcfile.idx);
+ return; /* a root (should never be called for this) */
+ }
+
+#ifdef DIRCACHE_NATIVE
+ ce->firstcluster = infop->fatfile.firstcluster;
+ ce->wrtdate = dinp->wrtdate;
+ ce->wrttime = dinp->wrttime;
+#else
+ ce->mtime = dinp->mtime;
+#endif
+ ce->attr = dinp->attr;
+ if (!(dinp->attr & ATTR_DIRECTORY))
+ ce->filesize = dinp->size;
}
-/*
- * build a path from an entry upto the root using recursion
- *
- * it appends '/' after strlcat, therefore buf[0] needs to be prepared with '/'
- * and it will leave a trailing '/'
- *
- * returns the position of that trailing '/' so it can be deleted afterwards
- * (or, in case of truncation, the position of the nul byte */
-static size_t copy_path_helper(const struct dircache_entry *entry, char *buf, size_t size)
+
+/** Dircache paths and files **/
+
+#ifdef DIRCACHE_DUMPSTER
+/* helper for dircache_get_path() */
+static ssize_t get_path_sub(int idx, char *buf, size_t size)
{
- int offset = 1;
- /* has parent? */
- if (entry->up)
- offset += copy_path_helper(entry->up, buf, size);
+ if (idx == 0)
+ return -2; /* entry is an orphan split from any root */
- size_t len = strlcpy(buf+offset, entry->d_name, size - offset) + offset;
- if (len < size)
+ ssize_t offset;
+ char *cename;
+
+ if (idx > 0)
{
- buf[len++] = '/';
- buf[len] = '\0';
+ /* go all the way up then move back down from the root */
+ struct dircache_entry *ce = get_entry(idx);
+ offset = get_path_sub(ce->up, buf, size) - 1;
+ if (offset < 0)
+ return -3;
+
+ cename = alloca(MAX_NAME + 1);
+ entry_name_copy(cename, ce);
}
- return len-1;
+ else /* idx < 0 */
+ {
+ offset = 0;
+ cename = "";
+
+ #ifdef HAVE_MULTIVOLUME
+ int volume = IF_MV_VOL(-idx - 1);
+ if (volume > 0)
+ {
+ /* prepend the volume specifier for volumes > 0 */
+ cename = alloca(VOL_MAX_LEN+1);
+ get_volume_name(volume, cename);
+ }
+ #endif /* HAVE_MULTIVOLUME */
+ }
+
+ return offset + path_append(buf + offset, PA_SEP_HARD, cename,
+ size > (size_t)offset ? size - offset : 0);
}
+#endif /* DIRCACHE_DUMPSTER */
+
+#if 0
+
/**
- * Function to copy the full absolute path from dircache to the given buffer
- * using the given dircache_entry pointer.
- *
- * Returns the size of the resulting string, or 0 if an error occured
+ * retrieve and validate the file's entry/binding serial number
+ * the dircache file's serial number must match the indexed entry's or the
+ * file reference is stale
*/
-size_t dircache_copy_path(int index, char *buf, size_t size)
+static dc_serial_t get_file_serialnum(const struct dircache_file *dcfilep)
{
- if (!size || !buf || index < 0)
+ int idx = dcfilep->idx;
+
+ if (idx == 0 || idx < -NUM_VOLUMES)
return 0;
- buf[0] = '/';
- size_t res = copy_path_helper(&dircache_root[index], buf, size - 1);
- /* fixup trailing '/' */
- buf[res] = '\0';
- return res;
+ dc_serial_t serialnum;
+
+ if (idx > 0)
+ {
+ struct dircache_entry *ce = get_entry(idx);
+ serialnum = ce ? ce->serialnum : 0;
+ }
+ else
+ {
+ serialnum = get_idx_dcvolp(idx)->serialnum;
+ }
+
+ return serialnum == dcfilep->serialnum ? serialnum : 0;
}
-/* --- Directory cache live updating functions --- */
-static int block_until_ready(void)
+/**
+ * usermode function to construct a full absolute path from dircache into the
+ * given buffer given the dircache file info
+ *
+ * returns:
+ * success - the length of the string, not including the trailing null
+ * failure - a negative value
+ *
+ * successful return value is as strlcpy()
+ *
+ * errors:
+ * ENOENT - the file or directory does not exist
+ */
+ssize_t dircache_get_path(const struct dircache_file *dcfilep, char *buf,
+ size_t size)
{
- /* Block until dircache has been built. */
- while (!dircache_initialized && dircache_is_initializing())
- sleep(1);
-
- if (!dircache_initialized)
- return -1;
-
- return 0;
+ /* if missing buffer space, still return what's needed a la strlcpy */
+ if (!buf)
+ size = 0;
+ else if (size)
+ *buf = '\0';
+
+ ssize_t len = -1;
+
+ dircache_lock();
+
+ /* first and foremost, there must be a cache and the serial number must
+ check out */
+ if (dircache_runinfo.handle && get_file_serialnum(dcfilep))
+ len = get_path_sub(dcfilep->idx, buf, size);
+
+ if (len < 0)
+ errno = ENOENT;
+
+ dircache_unlock();
+ return len;
}
-static struct dircache_entry* dircache_new_entry(const char *path, int attribute)
-{
- struct dircache_entry *entry;
- char basedir[MAX_PATH*2];
- char *new;
- long last_cache_size = dircache_size;
+/**
+ * searches the sublist starting at 'idx' for the named component
+ */
- strlcpy(basedir, path, sizeof(basedir));
- new = strrchr(basedir, '/');
- if (new == NULL)
+/* helper for get_file_sub() */
+static struct dircache_entry *
+get_file_sub_scan(int idx, const char *name, size_t length, int *idxp)
+{
+ struct dircache_entry *ce = get_entry(idx);
+ if (ce)
{
- logf("error occurred");
- dircache_initialized = false;
- return NULL;
+ char entname[MAX_NAME+1];
+ name = strmemdupa(name, length);
+
+ do
+ {
+ entry_name_copy(entname, ce);
+ if (!strcasecmp(entname, name))
+ {
+ *idxp = idx;
+ break;
+ }
+
+ idx = ce->next;
+ }
+ while ((ce = get_entry(idx)));
}
- *new = '\0';
- new++;
+ return ce;
+}
+
+/**
+ * searches for the subcomponent of *pathp
+ */
+
+/* helper for dircache_get_file() */
+static int get_file_sub(const char **pathp, int *downp, int *idxp)
+{
+ int rc;
+ const char *name;
+ rc = parse_path_component(pathp, &name, false);
+ if (rc <= 0)
+ return rc;
+ else if (rc >= MAX_PATH)
+ return ENAMETOOLONG; /* that's just unpossible, man */
+
+ struct dircache_entry *ce = get_file_sub_scan(*downp, name, rc, idxp);
- entry = dircache_get_entry(basedir, true);
- if (entry == NULL)
+ if (!ce)
+ rc = RC_NOT_FOUND; /* not there; tellibry solly */
+ else if (!*pathp)
+ rc = RC_PATH_ENDED; /* done */
+ else if (!(ce->attr & ATTR_DIRECTORY))
+ rc = ENOTDIR; /* a parent component must be a directory */
+ else
+ while ((rc = get_file_sub(pathp, &ce->down, idxp)) == RC_CONTINUE);
+
+ switch (rc)
{
- logf("basedir not found!");
- logf("%s", basedir);
- dircache_initialized = false;
- return NULL;
+ case RC_GO_UP: /* hit ".."; drop to previous level */
+ return RC_CONTINUE;
+ case RC_PATH_ENDED: /* success! */
+ return RC_FOUND;
+ default: /* component not found or error */
+ return rc;
}
+}
- if (reserve_used + 2*sizeof(struct dircache_entry) + strlen(new)+1
- >= DIRCACHE_RESERVE)
+/**
+ * usermode function to return dircache file info for the given path
+ *
+ * returns:
+ * success: the volume number that is specified for the file
+ * failure: a negative value
+ *
+ * errors:
+ * ENOENT - the file or directory does not exist or path is empty
+ * ENAMETOOLONG - a component of the path is too long
+ * ENOTDIR - a component of the path is not a directory
+ */
+int dircache_get_file(const char *path, struct dircache_file *dcfilep)
+{
+ if (!path_is_absolute(path) || !dcfilep)
{
- logf("not enough space");
- dircache_initialized = false;
- return NULL;
+ errno = ENOENT;
+ return -1;
}
-
- while (entry->next != NULL)
- entry = entry->next;
- if (entry->d_name != NULL)
+ dircache_lock();
+
+ if (!dircache_runinfo.handle)
{
- entry = dircache_gen_next(entry);
- if (entry == NULL)
+ dircache_unlock();
+ errno = ENOENT;
+ return -2;
+ }
+
+ int volume = 0;
+ int idx = 0;
+ dc_serial_t serialnum = 0;
+ struct dircache_volume *dcvolp = NULL;
+ struct dircache_entry *ce = NULL;
+
+ int rc = RC_GO_UP;
+
+ while (rc == RC_CONTINUE || rc == RC_GO_UP)
+ {
+ #ifdef HAVE_MULTIVOLUME
+ if (rc == RC_GO_UP)
{
- dircache_initialized = false;
- return NULL;
+ volume = path_strip_volume(path, &path, false);
+ if (!CHECK_VOL(volume))
+ {
+ rc = ENXIO;
+ break;
+ }
}
- }
+ #endif /* HAVE_MULTIVOLUME */
+
+ dcvolp = DCVOL(volume);
- size_t size = strlen(new) + 1;
- entry->d_name = (d_names_start -= size);
- entry->startcluster = 0;
- memset(&entry->info, 0, sizeof(entry->info));
- entry->info.attribute = attribute;
+ int *downp = &dcvolp->root_down;
+ if (*downp <= 0)
+ {
+ rc = ENXIO;
+ break;
+ }
- strcpy(entry->d_name, new);
- dircache_size += size;
+ rc = get_file_sub(&path, downp, &idx);
+ }
- if (attribute & ATTR_DIRECTORY)
+ switch (rc)
{
- logf("gen_down");
- dircache_gen_down(entry);
+ case RC_FOUND: /* hit: component found */
+ serialnum = ce->serialnum;
+ rc = volume;
+ break;
+ case RC_PATH_ENDED: /* hit: it's a root (volume or system) */
+ idx = -volume - 1;
+ serialnum = dcvolp->serialnum;
+ rc = volume;
+ break;
+ case RC_NOT_FOUND: /* miss */
+ rc = ENOENT;
+ default:
+ idx = 0;
+ errno = rc;
+ rc = -3;
+ break;
}
-
- reserve_used += dircache_size - last_cache_size;
- return entry;
+ dcfilep->idx = idx;
+ dcfilep->serialnum = serialnum;
+
+ dircache_unlock();
+ return rc;
}
+#endif /* 0 */
+
-void dircache_bind(int fd, const char *path)
+/** Debug screen/info stuff **/
+
+/**
+ * return cache state parameters
+ */
+void dircache_get_info(struct dircache_info *info)
{
- struct dircache_entry *entry;
-
- /* Queue requests until dircache has been built. */
- if (!dircache_initialized && dircache_is_initializing())
+ static const char * const status_descriptions[] =
+ {
+ [DIRCACHE_IDLE] = "Idle",
+ [DIRCACHE_SCANNING] = "Scanning",
+ [DIRCACHE_READY] = "Ready",
+ };
+
+ if (!info)
+ return;
+
+ memset(info, 0, sizeof (*info));
+
+ dircache_lock();
+
+ enum dircache_status status = DIRCACHE_IDLE;
+
+ for (unsigned int volume = 0; volume < NUM_VOLUMES; volume++)
{
- if (fdbind_idx >= MAX_PENDING_BINDINGS)
- return ;
- strlcpy(fdbind_cache[fdbind_idx].path, path,
- sizeof(fdbind_cache[fdbind_idx].path));
- fdbind_cache[fdbind_idx].fd = fd;
- fdbind_idx++;
- return ;
+ struct dircache_volume *dcvolp = DCVOL(volume);
+ enum dircache_status volstatus = dcvolp->status;
+
+ switch (volstatus)
+ {
+ case DIRCACHE_SCANNING:
+ /* if any one is scanning then overall status is "scanning" */
+ status = volstatus;
+
+ /* sum the time the scanning has taken so far */
+ info->build_ticks += current_tick - dcvolp->start_tick;
+ break;
+ case DIRCACHE_READY:
+ /* if all the rest are idle and at least one is ready, then
+ status is "ready". */
+ if (status == DIRCACHE_IDLE)
+ status = DIRCACHE_READY;
+
+ /* sum the build ticks of all "ready" volumes */
+ info->build_ticks += dcvolp->build_ticks;
+ break;
+ case DIRCACHE_IDLE:
+ /* if all are idle; then the whole cache is "idle" */
+ break;
+ }
}
-
- if (!dircache_initialized)
- return ;
- logf("bind: %d/%s", fd, path);
- entry = dircache_get_entry(path, false);
- if (entry == NULL)
+ info->status = status;
+ info->statusdesc = status_descriptions[status];
+ info->last_size = dircache.last_size;
+ info->size_limit = DIRCACHE_LIMIT;
+ info->reserve = DIRCACHE_RESERVE;
+
+ /* report usage only if there is something ready or being built */
+ if (status != DIRCACHE_IDLE)
{
- logf("not found!");
- dircache_initialized = false;
- return ;
+ info->reserve_used = reserve_buf_used();
+ info->size = dircache.size;
+ info->sizeused = dircache.sizeused;
+ info->entry_count = dircache.numentries;
}
- fd_bindings[fd] = entry;
+ dircache_unlock();
}
-void dircache_update_filesize(int fd, long newsize, long startcluster)
+#ifdef DIRCACHE_DUMPSTER
+/**
+ * dump RAW binary of buffer and CSV of all valid paths and volumes to disk
+ */
+void dircache_dump(void)
{
- if (!dircache_initialized || fd < 0)
- return ;
-
- if (fd_bindings[fd] == NULL)
+ /* open both now so they're in the cache */
+ int fdbin = open(DIRCACHE_DUMPSTER_BIN, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ int fdcsv = open(DIRCACHE_DUMPSTER_CSV, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ if (fdbin < 0 || fdcsv < 0)
{
- logf("dircache fd(%d) access error", fd);
- dircache_initialized = false;
- return ;
+ close(fdbin);
+ return;
}
-
- fd_bindings[fd]->info.size = newsize;
- fd_bindings[fd]->startcluster = startcluster;
-}
-void dircache_update_filetime(int fd)
-{
-#if CONFIG_RTC == 0
- (void)fd;
-#else
- short year;
- struct tm *now = get_time();
- if (!dircache_initialized || fd < 0)
- return ;
-
- if (fd_bindings[fd] == NULL)
- {
- logf("dircache fd access error");
- dircache_initialized = false;
- return ;
- }
- year = now->tm_year+1900-1980;
- fd_bindings[fd]->info.wrtdate = (((year)&0x7f)<<9) |
- (((now->tm_mon+1)&0xf)<<5) |
- (((now->tm_mday)&0x1f));
- fd_bindings[fd]->info.wrttime = (((now->tm_hour)&0x1f)<<11) |
- (((now->tm_min)&0x3f)<<5) |
- (((now->tm_sec/2)&0x1f));
-#endif
-}
-void dircache_mkdir(const char *path)
-{ /* Test ok. */
- if (block_until_ready())
- return ;
-
-
- logf("mkdir: %s", path);
- dircache_new_entry(path, ATTR_DIRECTORY);
-}
-
-void dircache_rmdir(const char *path)
-{ /* Test ok. */
- struct dircache_entry *entry;
-
- if (block_until_ready())
- return ;
-
- logf("rmdir: %s", path);
- entry = dircache_get_entry(path, false);
- if (entry == NULL || entry->down == NULL)
- {
- logf("not found or not a directory!");
- dircache_initialized = false;
- return ;
- }
-
- entry->down = NULL;
- entry->d_name = NULL;
-}
-
-/* Remove a file from cache */
-void dircache_remove(const char *name)
-{ /* Test ok. */
- struct dircache_entry *entry;
-
- if (block_until_ready())
- return ;
-
- logf("remove: %s", name);
-
- entry = dircache_get_entry(name, false);
-
- if (entry == NULL)
- {
- logf("not found!");
- dircache_initialized = false;
- return ;
- }
-
- entry->d_name = NULL;
-}
-
-void dircache_rename(const char *oldpath, const char *newpath)
-{ /* Test ok. */
- struct dircache_entry *entry, *newentry;
- struct dircache_entry oldentry;
- char absolute_path[MAX_PATH*2];
- char *p;
-
- if (block_until_ready())
- return ;
-
- logf("rename: %s->%s", oldpath, newpath);
-
- entry = dircache_get_entry(oldpath, false);
- if (entry == NULL)
- {
- logf("not found!");
- dircache_initialized = false;
- return ;
- }
-
- /* Delete the old entry. */
- entry->d_name = NULL;
-
- /** If we rename the same filename twice in a row, we need to
- * save the data, because the entry will be re-used. */
- oldentry = *entry;
-
- /* Generate the absolute path for destination if necessary. */
- if (newpath[0] != '/')
- {
- strlcpy(absolute_path, oldpath, sizeof(absolute_path));
- p = strrchr(absolute_path, '/');
- if (!p)
+ trigger_cpu_boost();
+ dircache_lock();
+
+ if (dircache_runinfo.handle)
+ {
+ buffer_lock();
+
+ /* bin */
+ write(fdbin, dircache_runinfo.p + ENTRYSIZE,
+ dircache_runinfo.bufsize + 1);
+
+ /* CSV */
+ fdprintf(fdcsv, "\"Index\",\"Serialnum\","
+ "\"Path\",\"Frontier\","
+ "\"Attribute\",\"File Size\","
+ "\"Mod Date\",\"Mod Time\"\n");
+
+ FOR_EACH_VOLUME(-1, volume)
{
- logf("Invalid path");
- dircache_initialized = false;
- return ;
+ struct dircache_volume *dcvolp = DCVOL(volume);
+ if (dcvolp->status == DIRCACHE_IDLE)
+ continue;
+
+ #ifdef HAVE_MULTIVOLUME
+ char name[VOL_MAX_LEN+1];
+ get_volume_name(volume, name);
+ #endif
+ fdprintf(fdcsv,
+ "%d,%lu,"
+ "\"%c" IF_MV("%s") "\",%u,"
+ "0x%08X,0,"
+ "\"\",\"\"\n",
+ -volume-1, dcvolp->serialnum,
+ PATH_SEPCH, IF_MV(name,) dcvolp->frontier,
+ ATTR_DIRECTORY | ATTR_VOLUME);
}
-
- *p = '\0';
- strlcpy(p, absolute_path, sizeof(absolute_path)-strlen(p));
- newpath = absolute_path;
- }
-
- newentry = dircache_new_entry(newpath, entry->info.attribute);
- if (newentry == NULL)
- {
- dircache_initialized = false;
- return ;
+
+ FOR_EACH_CACHE_ENTRY(ce)
+ {
+ #ifdef DIRCACHE_NATIVE
+ time_t mtime = fattime_mktime(ce->wrtdate, ce->wrttime);
+ #else
+ time_t mtime = ce->mtime;
+ #endif
+ struct tm tm;
+ gmtime_r(&mtime, &tm);
+
+ char buf[DC_MAX_NAME + 2];
+ *buf = '\0';
+ int idx = get_index(ce);
+ get_path_sub(idx, buf, sizeof (buf));
+
+ fdprintf(fdcsv,
+ "%d,%lu,"
+ "\"%s\",%u,"
+ "0x%08X,%lu,"
+ "%04d/%02d/%02d,"
+ "%02d:%02d:%02d\n",
+ idx, ce->serialnum,
+ buf, ce->frontier,
+ ce->attr, (ce->attr & ATTR_DIRECTORY) ?
+ 0ul : (unsigned long)ce->filesize,
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ }
+
+ buffer_unlock();
}
- newentry->down = oldentry.down;
- newentry->startcluster = oldentry.startcluster;
- newentry->info.size = oldentry.info.size;
- newentry->info.wrtdate = oldentry.info.wrtdate;
- newentry->info.wrttime = oldentry.info.wrttime;
+ dircache_unlock();
+ cancel_cpu_boost();
+
+ close(fdbin);
+ close(fdcsv);
}
+#endif /* DIRCACHE_DUMPSTER */
+
-void dircache_add_file(const char *path, long startcluster)
+/** Misc. stuff **/
+
+/**
+ * set the dircache file to initial values
+ */
+void dircache_dcfile_init(struct dircache_file *dcfilep)
{
- struct dircache_entry *entry;
-
- if (block_until_ready())
- return ;
-
- logf("add file: %s", path);
- entry = dircache_new_entry(path, 0);
- if (entry == NULL)
- return ;
-
- entry->startcluster = startcluster;
+ dcfilep->idx = 0;
+ dcfilep->serialnum = 0;
}
-static bool is_disable_msg_pending(void)
+#ifdef HAVE_EEPROM_SETTINGS
+
+#ifdef HAVE_HOTSWAP
+/* NOTE: This is hazardous to the filesystem of any sort of removable
+ storage unless it may be determined that the filesystem from save
+ to load is identical. If it's not possible to do so in a timely
+ manner, it's not worth persisting the cache. */
+ #warning "Don't do this; you'll find the consequences unpleasant."
+#endif
+
+/* dircache persistence file header magic */
+#define DIRCACHE_MAGIC 0x00d0c0a1
+
+/* dircache persistence file header */
+struct dircache_maindata
{
- return check_event_queue();
+ uint32_t magic; /* DIRCACHE_MAGIC */
+ struct dircache dircache; /* metadata of the cache! */
+ uint32_t datacrc; /* CRC32 of data */
+ uint32_t hdrcrc; /* CRC32 of header through datacrc */
+} __attribute__((packed, aligned (4)));
+
+/**
+ * verify that the clean status is A-ok
+ */
+static bool dircache_is_clean(bool saving)
+{
+ if (saving)
+ return dircache.dcvol[0].status == DIRCACHE_READY;
+ else
+ {
+ return dircache.dcvol[0].status == DIRCACHE_IDLE &&
+ !dircache_runinfo.enabled;
+ }
}
-DIR_CACHED* opendir_cached(const char* name)
+/**
+ * function to load the internal cache structure from disk to initialize
+ * the dircache really fast with little disk access.
+ */
+int dircache_load(void)
{
- int dd;
- DIR_CACHED* pdir = opendirs;
+ logf("Loading directory cache");
+ int fd = open_dircache_file(O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ int rc = -1;
+
+ ssize_t size;
+ struct dircache_maindata maindata;
+ uint32_t crc;
+ int handle = 0;
+ bool hasbuffer = false;
- if ( name[0] != '/' )
+ size = sizeof (maindata);
+ if (read(fd, &maindata, size) != size)
{
- DEBUGF("Only absolute paths supported right now\n");
- return NULL;
+ logf("dircache: header read failed");
+ goto error_nolock;
}
- /* find a free dir descriptor */
- for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++)
- if ( !pdir->busy )
- break;
+ /* sanity check the header */
+ if (maindata.magic != DIRCACHE_MAGIC)
+ {
+ logf("dircache: invalid header magic");
+ goto error_nolock;
+ }
- if ( dd == MAX_OPEN_DIRS )
+ crc = crc_32(&maindata, offsetof(struct dircache_maindata, hdrcrc),
+ 0xffffffff);
+ if (crc != maindata.hdrcrc)
{
- DEBUGF("Too many dirs open\n");
- errno = EMFILE;
- return NULL;
+ logf("dircache: invalid header CRC32");
+ goto error_nolock;
}
-
- pdir->busy = true;
- if (!dircache_initialized || is_disable_msg_pending())
+ if (maindata.dircache.size !=
+ maindata.dircache.sizeentries + maindata.dircache.sizenames ||
+ ALIGN_DOWN(maindata.dircache.size, ENTRYSIZE) != maindata.dircache.size ||
+ filesize(fd) - sizeof (maindata) != maindata.dircache.size)
{
- pdir->internal_entry = -1;
- pdir->regulardir = opendir_uncached(name);
+ logf("dircache: file header error");
+ goto error_nolock;
}
- else
+
+ /* allocate so that exactly the reserve size remains */
+ size_t bufsize = maindata.dircache.size + DIRCACHE_RESERVE + 1;
+ handle = alloc_cache(bufsize);
+ if (handle <= 0)
+ {
+ logf("dircache: failed load allocation");
+ goto error_nolock;
+ }
+
+ dircache_lock();
+ buffer_lock();
+
+ if (!dircache_is_clean(false))
+ goto error;
+
+ /* from this point on, we're actually dealing with the cache in RAM */
+ dircache = maindata.dircache;
+
+ set_buffer(handle, bufsize);
+ hasbuffer = true;
+
+ /* convert back to in-RAM representation */
+ dircache.numentries = maindata.dircache.sizeentries / ENTRYSIZE;
+
+ /* read the dircache file into memory; start with the entries */
+ size = maindata.dircache.sizeentries;
+ if (read(fd, dircache_runinfo.pentry + 1, size) != size)
{
- pdir->regulardir = NULL;
- pdir->internal_entry = dircache_get_entry_id_ex(name, true);
- pdir->theent.info.attribute = -1; /* used to make readdir_cached aware of the first call */
+ logf("dircache read failed #1");
+ goto error;
}
- if (pdir->internal_entry == -1 && pdir->regulardir == NULL)
+ crc = crc_32(dircache_runinfo.pentry + 1, size, 0xffffffff);
+
+ /* continue with the names; fix up indexes to them if needed */
+ dircache.names -= maindata.dircache.sizenames;
+ *get_name(dircache.names - 1) = 0;
+
+ size = maindata.dircache.sizenames;
+ if (read(fd, get_name(dircache.names), size) != size)
{
- pdir->busy = false;
- return NULL;
+ logf("dircache read failed #2");
+ goto error;
}
- return pdir;
-}
+ crc = crc_32(get_name(dircache.names), size, crc);
+ if (crc != maindata.datacrc)
+ {
+ logf("dircache: data failed CRC32");
+ goto error;
+ }
-struct dirent_cached* readdir_cached(DIR_CACHED* dir)
-{
- struct dircache_entry *ce = get_entry(dir->internal_entry);
- struct dirent_uncached *regentry;
-
- if (!dir->busy)
- return NULL;
+ /* only names will be changed in relative position so fix up those
+ references */
+ ssize_t offset = dircache.names - maindata.dircache.names;
+ if (offset != 0)
+ {
+ /* nothing should be open besides the dircache file itself therefore
+ no bindings need be resolved; the cache will have its own entry
+ but that should get cleaned up when removing the file */
+ FOR_EACH_CACHE_ENTRY(ce)
+ {
+ if (!ce->tinyname)
+ ce->name += offset;
+ }
+ }
- if (dir->regulardir != NULL)
- {
- regentry = readdir_uncached(dir->regulardir);
- if (regentry == NULL)
- return NULL;
-
- strlcpy(dir->theent.d_name, regentry->d_name, MAX_PATH);
- dir->theent.startcluster = regentry->startcluster;
- dir->theent.info = regentry->info;
-
- return &dir->theent;
- }
-
- /* if theent.attribute=-1 then this is the first call */
- /* otherwise, this is is not so we first take the entry's ->next */
- /* NOTE: normal file can't have attribute=-1 */
- if(dir->theent.info.attribute != -1)
- ce = ce->next;
- /* skip unused entries */
- while(ce != NULL && ce->d_name == NULL)
- ce = ce->next;
-
- if (ce == NULL)
- return NULL;
+ dircache.reserve_used = 0;
+
+ /* enable the cache but do not try to build it */
+ dircache_enable_internal(false);
+
+ /* cache successfully loaded */
+ logf("Done, %ld KiB used", dircache.size / 1024);
+ rc = 0;
+error:
+ if (rc < 0 && hasbuffer)
+ reset_buffer();
- strlcpy(dir->theent.d_name, ce->d_name, MAX_PATH);
- /* Can't do `dir->theent = *ce`
- because that modifies the d_name pointer. */
- dir->theent.startcluster = ce->startcluster;
- dir->theent.info = ce->info;
- dir->internal_entry = ce - dircache_root;
+ buffer_unlock();
+ dircache_unlock();
- //logf("-> %s", ce->d_name);
- return &dir->theent;
+error_nolock:
+ if (rc < 0 && handle > 0)
+ core_free(handle);
+
+ if (fd >= 0)
+ close(fd);
+
+ remove_dircache_file();
+ return rc;
}
-int closedir_cached(DIR_CACHED* dir)
+/**
+ * function to save the internal cache stucture to disk for fast loading
+ * on boot
+ */
+int dircache_save(void)
{
- if (!dir->busy)
+ logf("Saving directory cache");
+
+ int fd = open_dircache_file(O_WRONLY|O_CREAT|O_TRUNC|O_APPEND);
+ if (fd < 0)
return -1;
-
- dir->busy=false;
- if (dir->regulardir != NULL)
- return closedir_uncached(dir->regulardir);
-
- return 0;
-}
-int mkdir_cached(const char *name)
-{
- int rc=mkdir_uncached(name);
- if (rc >= 0)
- dircache_mkdir(name);
- return(rc);
+ dircache_lock();
+ buffer_lock();
+
+ int rc = -1;
+
+ if (!dircache_is_clean(true))
+ goto error;
+
+ /* save the header structure along with the cache metadata */
+ ssize_t size;
+ uint32_t crc;
+ struct dircache_maindata maindata =
+ {
+ .magic = DIRCACHE_MAGIC,
+ .dircache = dircache,
+ };
+
+ /* store the size since it better detects an invalid header */
+ maindata.dircache.sizeentries = maindata.dircache.numentries * ENTRYSIZE;
+
+ /* write the template header */
+ size = sizeof (maindata);
+ if (write(fd, &maindata, size) != size)
+ {
+ logf("dircache: write failed #1");
+ goto error;
+ }
+
+ /* write the dircache entries */
+ size = maindata.dircache.sizeentries;
+ if (write(fd, dircache_runinfo.pentry + 1, size) != size)
+ {
+ logf("dircache: write failed #2");
+ goto error;
+ }
+
+ crc = crc_32(dircache_runinfo.pentry + 1, size, 0xffffffff);
+
+ /* continue with the names */
+ size = maindata.dircache.sizenames;
+ if (write(fd, get_name(dircache.names), size) != size)
+ {
+ logf("dircache: write failed #3");
+ goto error;
+ }
+
+ crc = crc_32(get_name(dircache.names), size, crc);
+ maindata.datacrc = crc;
+
+ /* rewrite the header with CRC info */
+ if (lseek(fd, 0, SEEK_SET) != 0)
+ {
+ logf("dircache: seek failed");
+ goto error;
+ }
+
+ crc = crc_32(&maindata, offsetof(struct dircache_maindata, hdrcrc),
+ 0xffffffff);
+ maindata.hdrcrc = crc;
+
+ if (write(fd, &maindata, sizeof (maindata)) != sizeof (maindata))
+ {
+ logf("dircache: write failed #4");
+ goto error;
+ }
+
+ /* as of now, no changes to the volumes should be allowed at all since
+ that makes what was saved completely invalid */
+ rc = 0;
+error:
+ buffer_unlock();
+ dircache_unlock();
+
+ if (rc < 0)
+ remove_dircache_file();
+
+ close(fd);
+ return rc;
}
+#endif /* HAVE_EEPROM_SETTINGS */
-int rmdir_cached(const char* name)
+/**
+ * main one-time initialization function that must be called before any other
+ * operations within the dircache
+ */
+void dircache_init(size_t last_size)
{
- int rc=rmdir_uncached(name);
- if(rc >= 0)
- dircache_rmdir(name);
- return(rc);
+ queue_init(&dircache_queue, false);
+
+ dircache.last_size = MIN(last_size, DIRCACHE_LIMIT);
+
+ struct dircache_runinfo *dcrip = &dircache_runinfo;
+ dcrip->suspended = 1;
+ dcrip->thread_done = true;
+ dcrip->ops.move_callback = move_callback;
}
diff --git a/firmware/common/disk.c b/firmware/common/disk.c
index 5a55a3b6ac..3a2d27e0d7 100644
--- a/firmware/common/disk.c
+++ b/firmware/common/disk.c
@@ -19,14 +19,25 @@
*
****************************************************************************/
#include <stdio.h>
+#include <string.h>
+#include "config.h"
#include "kernel.h"
#include "storage.h"
#include "debug.h"
-#include "fat.h"
-#include "dir.h" /* for release_dirs() */
-#include "file.h" /* for release_files() */
+#include "disk_cache.h"
+#include "fileobj_mgr.h"
+#include "dir.h"
+#include "dircache_redirect.h"
#include "disk.h"
-#include <string.h>
+
+#ifndef CONFIG_DEFAULT_PARTNUM
+#define CONFIG_DEFAULT_PARTNUM 0
+#endif
+
+#define disk_reader_lock() file_internal_lock_READER()
+#define disk_reader_unlock() file_internal_unlock_READER()
+#define disk_writer_lock() file_internal_lock_WRITER()
+#define disk_writer_unlock() file_internal_unlock_WRITER()
/* Partition table entry layout:
-----------------------
@@ -42,11 +53,18 @@
12-15: nr of sectors in partition
*/
-#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 BYTES2INT16(array, pos) \
+ (((uint32_t)array[pos+0] << 0) | \
+ ((uint32_t)array[pos+1] << 8))
-static const unsigned char fat_partition_types[] = {
+static const unsigned char fat_partition_types[] =
+{
0x0b, 0x1b, /* FAT32 + hidden variant */
0x0c, 0x1c, /* FAT32 (LBA) + hidden variant */
#ifdef HAVE_FAT16SUPPORT
@@ -56,159 +74,135 @@ static const unsigned char fat_partition_types[] = {
#endif
};
-static struct partinfo part[NUM_DRIVES*4]; /* space for 4 partitions on 2 drives */
-static int vol_drive[NUM_VOLUMES]; /* mounted to which drive (-1 if none) */
-static struct mutex disk_mutex;
+/* space for 4 partitions on 2 drives */
+static struct partinfo part[NUM_DRIVES*4];
+/* mounted to which drive (-1 if none) */
+static int vol_drive[NUM_VOLUMES];
+
+static int get_free_volume(void)
+{
+ for (int i = 0; i < NUM_VOLUMES; i++)
+ {
+ if (vol_drive[i] == -1) /* unassigned? */
+ return i;
+ }
+
+ return -1; /* none found */
+}
#ifdef MAX_LOG_SECTOR_SIZE
-static int disk_sector_multiplier[NUM_DRIVES] = {[0 ... NUM_DRIVES-1] = 1};
+static int disk_sector_multiplier[NUM_DRIVES] =
+ { [0 ... NUM_DRIVES-1] = 1 };
int disk_get_sector_multiplier(IF_MD_NONVOID(int drive))
{
- #ifdef HAVE_MULTIDRIVE
- return disk_sector_multiplier[drive];
- #else
- return disk_sector_multiplier[0];
- #endif
+ if (!CHECK_DRV(drive))
+ return 0;
+
+ disk_reader_lock();
+ int multiplier = disk_sector_multiplier[IF_MD_DRV(drive)];
+ disk_reader_unlock();
+ return multiplier;
}
-#endif
+#endif /* MAX_LOG_SECTOR_SIZE */
-struct partinfo* disk_init(IF_MD_NONVOID(int drive))
+bool disk_init(IF_MD_NONVOID(int drive))
{
- int i;
-#ifdef HAVE_MULTIDRIVE
- /* For each drive, start at a different position, in order not to destroy
- the first entry of drive 0.
- That one is needed to calculate config sector position. */
- struct partinfo* pinfo = &part[drive*4];
- if ((size_t)drive >= sizeof(part)/sizeof(*part)/4)
- return NULL; /* out of space in table */
-#else
- struct partinfo* pinfo = part;
- const int drive = 0;
- (void)drive;
-#endif
+ if (!CHECK_DRV(drive))
+ return false; /* out of space in table */
- unsigned char* sector = fat_get_sector_buffer();
- storage_read_sectors(IF_MD(drive,) 0,1, sector);
- /* check that the boot sector is initialized */
- if ( (sector[510] != 0x55) ||
- (sector[511] != 0xaa)) {
- fat_release_sector_buffer();
- DEBUGF("Bad boot sector signature\n");
- return NULL;
- }
+ unsigned char *sector = dc_get_buffer();
+ if (!sector)
+ return false;
- /* parse partitions */
- for ( i=0; i<4; i++ ) {
- unsigned char* ptr = sector + 0x1be + 16*i;
- pinfo[i].type = ptr[4];
- pinfo[i].start = BYTES2INT32(ptr, 8);
- pinfo[i].size = BYTES2INT32(ptr, 12);
+ memset(sector, 0, SECTOR_SIZE);
+ storage_read_sectors(IF_MD(drive,) 0, 1, sector);
- DEBUGF("Part%d: Type %02x, start: %08lx size: %08lx\n",
- i,pinfo[i].type,pinfo[i].start,pinfo[i].size);
+ bool init = false;
- /* extended? */
- if ( pinfo[i].type == 5 ) {
- /* not handled yet */
- }
- }
- fat_release_sector_buffer();
- return pinfo;
-}
+ /* check that the boot sector is initialized */
+ if (BYTES2INT16(sector, 510) == 0xaa55)
+ {
+ /* For each drive, start at a different position, in order not to
+ destroy the first entry of drive 0. That one is needed to calculate
+ config sector position. */
+ struct partinfo *pinfo = &part[IF_MD_DRV(drive)*4];
-struct partinfo* disk_partinfo(int partition)
-{
- return &part[partition];
-}
+ disk_writer_lock();
-void disk_init_subsystem(void)
-{
- mutex_init(&disk_mutex);
-}
+ /* parse partitions */
+ for (int i = 0; i < 4; i++)
+ {
+ unsigned char* ptr = sector + 0x1be + 16*i;
+ pinfo[i].type = ptr[4];
+ pinfo[i].start = BYTES2INT32(ptr, 8);
+ pinfo[i].size = BYTES2INT32(ptr, 12);
-int disk_mount_all(void)
-{
- int mounted=0;
- int i;
-
-#ifdef HAVE_HOTSWAP
- mutex_lock(&disk_mutex);
-#endif
+ DEBUGF("Part%d: Type %02x, start: %08lx size: %08lx\n",
+ i,pinfo[i].type,pinfo[i].start,pinfo[i].size);
- fat_init(); /* reset all mounted partitions */
- for (i=0; i<NUM_VOLUMES; i++)
- vol_drive[i] = -1; /* mark all as unassigned */
+ /* extended? */
+ if ( pinfo[i].type == 5 )
+ {
+ /* not handled yet */
+ }
+ }
-#ifndef HAVE_MULTIDRIVE
- mounted = disk_mount(0);
-#else
- for(i=0;i<NUM_DRIVES;i++)
+ disk_writer_unlock();
+
+ init = true;
+ }
+ else
{
-#ifdef HAVE_HOTSWAP
- if (storage_present(i))
-#endif
- mounted += disk_mount(i);
+ DEBUGF("Bad boot sector signature\n");
}
-#endif
-#ifdef HAVE_HOTSWAP
- mutex_unlock(&disk_mutex);
-#endif
- return mounted;
+ dc_release_buffer(sector);
+ return init;
}
-static int get_free_volume(void)
+bool disk_partinfo(int partition, struct partinfo *info)
{
- int i;
- for (i=0; i<NUM_VOLUMES; i++)
- {
- if (vol_drive[i] == -1) /* unassigned? */
- return i;
- }
+ if (partition < 0 || partition >= (int)ARRAYLEN(part) || !info)
+ return false;
- return -1; /* none found */
+ disk_reader_lock();
+ *info = part[partition];
+ disk_reader_unlock();
+ return true;
}
int disk_mount(int drive)
{
int mounted = 0; /* reset partition-on-drive flag */
- int volume;
- struct partinfo* pinfo;
-#ifdef HAVE_HOTSWAP
- mutex_lock(&disk_mutex);
-#endif
+ disk_writer_lock();
- volume = get_free_volume();
- pinfo = disk_init(IF_MD(drive));
-#ifdef MAX_LOG_SECTOR_SIZE
- disk_sector_multiplier[drive] = 1;
-#endif
+ int volume = get_free_volume();
- if (pinfo == NULL)
+ if (!disk_init(IF_MD(drive)))
{
-#ifdef HAVE_HOTSWAP
- mutex_unlock(&disk_mutex);
-#endif
+ disk_writer_unlock();
return 0;
}
-#if defined(TOSHIBA_GIGABEAT_S)
- int i = 1; /* For the Gigabeat S, we mount the second partition */
-#else
- int i = 0;
+
+ struct partinfo *pinfo = &part[IF_MD_DRV(drive)*4];
+#ifdef MAX_LOG_SECTOR_SIZE
+ disk_sector_multiplier[IF_MD_DRV(drive)] = 1;
#endif
- for (; volume != -1 && i<4 && mounted<NUM_VOLUMES_PER_DRIVE; i++)
+
+ for (int i = CONFIG_DEFAULT_PARTNUM;
+ volume != -1 && i < 4 && mounted < NUM_VOLUMES_PER_DRIVE;
+ i++)
{
if (memchr(fat_partition_types, pinfo[i].type,
sizeof(fat_partition_types)) == NULL)
continue; /* not an accepted partition type */
-#ifdef MAX_LOG_SECTOR_SIZE
- int j;
-
- for (j = 1; j <= (MAX_LOG_SECTOR_SIZE/SECTOR_SIZE); j <<= 1)
+ bool success = false;
+
+ #ifdef MAX_LOG_SECTOR_SIZE
+ for (int j = 1; j <= (MAX_LOG_SECTOR_SIZE/SECTOR_SIZE); j <<= 1)
{
if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start * j))
{
@@ -218,93 +212,242 @@ int disk_mount(int drive)
vol_drive[volume] = drive; /* remember the drive for this volume */
volume = get_free_volume(); /* prepare next entry */
disk_sector_multiplier[drive] = j;
+ success = true;
break;
}
}
-#else
+ #else /* ndef MAX_LOG_SECTOR_SIZE */
if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start))
{
mounted++;
vol_drive[volume] = drive; /* remember the drive for this volume */
volume = get_free_volume(); /* prepare next entry */
+ success = true;
}
-#endif
+ #endif /* MAX_LOG_SECTOR_SIZE */
+
+ if (success)
+ volume_onmount_internal(IF_MV(volume));
}
if (mounted == 0 && volume != -1) /* none of the 4 entries worked? */
{ /* try "superfloppy" mode */
DEBUGF("No partition found, trying to mount sector 0.\n");
+
if (!fat_mount(IF_MV(volume,) IF_MD(drive,) 0))
{
-#ifdef MAX_LOG_SECTOR_SIZE
- disk_sector_multiplier[drive] = fat_get_bytes_per_sector(IF_MV(volume))/SECTOR_SIZE;
-#endif
+ #ifdef MAX_LOG_SECTOR_SIZE
+ disk_sector_multiplier[drive] =
+ fat_get_bytes_per_sector(IF_MV(volume)) / SECTOR_SIZE;
+ #endif
mounted = 1;
vol_drive[volume] = drive; /* remember the drive for this volume */
+ volume_onmount_internal(IF_MV(volume));
}
}
-#ifdef HAVE_HOTSWAP
- mutex_unlock(&disk_mutex);
-#endif
+
+ disk_writer_unlock();
+ return mounted;
+}
+
+int disk_mount_all(void)
+{
+ int mounted = 0;
+
+ disk_writer_lock();
+
+ /* reset all mounted partitions */
+ volume_onunmount_internal(IF_MV(-1));
+ fat_init();
+
+ for (int i = 0; i < NUM_VOLUMES; i++)
+ vol_drive[i] = -1; /* mark all as unassigned */
+
+ for (int i = 0; i < NUM_DRIVES; i++)
+ {
+ #ifdef HAVE_HOTSWAP
+ if (storage_present(i))
+ #endif
+ mounted += disk_mount(i);
+ }
+
+ disk_writer_unlock();
return mounted;
}
int disk_unmount(int drive)
{
+ if (!CHECK_DRV(drive))
+ return 0;
+
int unmounted = 0;
- int i;
-#ifdef HAVE_HOTSWAP
- mutex_lock(&disk_mutex);
-#endif
- for (i=0; i<NUM_VOLUMES; i++)
+
+ disk_writer_lock();
+
+ for (int i = 0; i < NUM_VOLUMES; i++)
{
if (vol_drive[i] == drive)
{ /* force releasing resources */
vol_drive[i] = -1; /* mark unused */
+
+ volume_onunmount_internal(IF_MV(i));
+ fat_unmount(IF_MV(i));
+
unmounted++;
- release_files(i);
- release_dirs(i);
- fat_unmount(i, false);
}
}
-#ifdef HAVE_HOTSWAP
- mutex_unlock(&disk_mutex);
-#endif
+ disk_writer_unlock();
return unmounted;
}
int disk_unmount_all(void)
{
-#ifndef HAVE_MULTIDRIVE
- return disk_unmount(0);
-#else /* HAVE_MULTIDRIVE */
int unmounted = 0;
- int i;
- for (i = 0; i < NUM_DRIVES; i++)
+
+ disk_writer_lock();
+
+ volume_onunmount_internal(IF_MV(-1));
+
+ for (int i = 0; i < NUM_DRIVES; i++)
{
-#ifdef HAVE_HOTSWAP
+ #ifdef HAVE_HOTSWAP
if (storage_present(i))
-#endif
+ #endif
unmounted += disk_unmount(i);
}
+ disk_writer_unlock();
return unmounted;
-#endif /* HAVE_MULTIDRIVE */
+}
+
+bool disk_present(IF_MD_NONVOID(int drive))
+{
+ int rc = -1;
+
+ if (CHECK_DRV(drive))
+ {
+ void *sector = dc_get_buffer();
+ if (sector)
+ {
+ rc = storage_read_sectors(IF_MD(drive,) 0, 1, sector);
+ dc_release_buffer(sector);
+ }
+ }
+
+ return rc == 0;
+}
+
+
+/** Volume-centric functions **/
+
+void volume_recalc_free(IF_MV_NONVOID(int volume))
+{
+ if (!CHECK_VOL(volume))
+ return;
+
+ /* FIXME: this is crummy but the only way to ensure a correct freecount
+ if other threads are writing and changing the fsinfo; it is possible
+ to get multiple threads calling here and also writing and get correct
+ freespace counts, however a bit complicated to do; if thou desireth I
+ shall implement the concurrent version -- jethead71 */
+ disk_writer_lock();
+ fat_recalc_free(IF_MV(volume));
+ disk_writer_unlock();
+}
+
+unsigned int volume_get_cluster_size(IF_MV_NONVOID(int volume))
+{
+ if (!CHECK_VOL(volume))
+ return 0;
+
+ disk_reader_lock();
+ unsigned int clustersize = fat_get_cluster_size(IF_MV(volume));
+ disk_reader_unlock();
+ return clustersize;
+}
+
+void volume_size(IF_MV(int volume,) unsigned long *sizep, unsigned long *freep)
+{
+ disk_reader_lock();
+
+ if (!CHECK_VOL(volume) || !fat_size(IF_MV(volume,) sizep, freep))
+ {
+ if (freep) *sizep = 0;
+ if (freep) *freep = 0;
+ }
+
+ disk_reader_unlock();
+}
+
+#if defined (HAVE_HOTSWAP) || defined (HAVE_MULTIDRIVE) \
+ || defined (HAVE_DIRCACHE)
+enum volume_info_type
+{
+#ifdef HAVE_HOTSWAP
+ VP_REMOVABLE,
+ VP_PRESENT,
+#endif
+#if defined (HAVE_MULTIDRIVE) || defined (HAVE_DIRCACHE)
+ VP_DRIVE,
+#endif
+};
+
+static int volume_properties(int volume, enum volume_info_type infotype)
+{
+ int res = -1;
+
+ disk_reader_lock();
+
+ if (CHECK_VOL(volume))
+ {
+ int vd = vol_drive[volume];
+ switch (infotype)
+ {
+ #ifdef HAVE_HOTSWAP
+ case VP_REMOVABLE:
+ res = storage_removable(vd) ? 1 : 0;
+ break;
+ case VP_PRESENT:
+ res = storage_present(vd) ? 1 : 0;
+ break;
+ #endif
+ #if defined(HAVE_MULTIDRIVE) || defined(HAVE_DIRCACHE)
+ case VP_DRIVE:
+ res = vd;
+ break;
+ #endif
+ }
+ }
+
+ disk_reader_unlock();
+ return res;
}
#ifdef HAVE_HOTSWAP
bool volume_removable(int volume)
{
- if(vol_drive[volume] == -1)
- return false;
- return storage_removable(vol_drive[volume]);
+ return volume_properties(volume, VP_REMOVABLE) > 0;
}
bool volume_present(int volume)
{
- if(vol_drive[volume] == -1)
- return false;
- return storage_present(vol_drive[volume]);
+ return volume_properties(volume, VP_PRESENT) > 0;
}
-#endif
+#endif /* HAVE_HOTSWAP */
+
+#ifdef HAVE_MULTIDRIVE
+int volume_drive(int volume)
+{
+ return volume_properties(volume, VP_DRIVE);
+}
+#endif /* HAVE_MULTIDRIVE */
+
+#ifdef HAVE_DIRCACHE
+bool volume_ismounted(IF_MV_NONVOID(int volume))
+{
+ return volume_properties(IF_MV_VOL(volume), VP_DRIVE) >= 0;
+}
+#endif /* HAVE_DIRCACHE */
+
+#endif /* HAVE_HOTSWAP || HAVE_MULTIDRIVE || HAVE_DIRCACHE */
diff --git a/firmware/common/disk_cache.c b/firmware/common/disk_cache.c
new file mode 100644
index 0000000000..0e842e7796
--- /dev/null
+++ b/firmware/common/disk_cache.c
@@ -0,0 +1,343 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "config.h"
+#include "debug.h"
+#include "system.h"
+#include "linked_list.h"
+#include "disk_cache.h"
+#include "fat.h" /* for SECTOR_SIZE */
+#include "bitarray.h"
+
+/* Cache: LRU cache with separately-chained hashtable
+ *
+ * Each entry of the map is the mapped location of the hashed sector value
+ * where each bit in each map entry indicates which corresponding cache
+ * entries are occupied by sector values that collide in that map entry.
+ *
+ * Each volume is given its own bit map.
+ *
+ * To probe for a specific key, each bit in the map entry must be examined,
+ * its position used as an index into the cache_entry array and the actual
+ * sector information compared for that cache entry. If the search exhausts
+ * all bits, the sector is not cached.
+ *
+ * To avoid long chains, the map entry count should be much greater than the
+ * number of cache entries. Since the cache is an LRU design, no buffer entry
+ * in the array is intrinsically associated with any particular sector number
+ * or volume.
+ *
+ * Example 6-sector cache with 8-entry map:
+ * cache entry 543210
+ * cache map 100000 <- sector number hashes into map
+ * 000000
+ * 000100
+ * 000000
+ * 010000
+ * 000000
+ * 001001 <- collision
+ * 000000
+ * volume map 111101 <- entry usage by the volume (OR of all map entries)
+ */
+
+enum dce_flags /* flags for each cache entry */
+{
+ DCE_INUSE = 0x01, /* entry in use and valid */
+ DCE_DIRTY = 0x02, /* entry is dirty in need of writeback */
+ DCE_BUF = 0x04, /* entry is being used as a general buffer */
+};
+
+struct disk_cache_entry
+{
+ struct lldc_node node; /* LRU list links */
+ unsigned char flags; /* entry flags */
+#ifdef HAVE_MULTIVOLUME
+ unsigned char volume; /* volume of sector */
+#endif
+ unsigned long sector; /* cached disk sector number */
+};
+
+BITARRAY_TYPE_DECLARE(cache_map_entry_t, cache_map, DC_NUM_ENTRIES)
+
+static inline unsigned int map_sector(unsigned long sector)
+{
+ /* keep sector hash simple for now */
+ return sector % DC_MAP_NUM_ENTRIES;
+}
+
+static struct lldc_head cache_lru; /* LRU cache list (head = LRU item) */
+static struct disk_cache_entry cache_entry[DC_NUM_ENTRIES];
+static cache_map_entry_t cache_map_entry[NUM_VOLUMES][DC_MAP_NUM_ENTRIES];
+static cache_map_entry_t cache_vol_map[NUM_VOLUMES] IBSS_ATTR;
+static uint8_t cache_buffer[DC_NUM_ENTRIES][DC_CACHE_BUFSIZE] CACHEALIGN_ATTR;
+struct mutex disk_cache_mutex SHAREDBSS_ATTR;
+
+#define CACHE_MAP_ENTRY(volume, mapnum) \
+ cache_map_entry[IF_MV_VOL(volume)][mapnum]
+#define CACHE_VOL_MAP(volume) \
+ cache_vol_map[IF_MV_VOL(volume)]
+
+#define DCE_LRU() ((struct disk_cache_entry *)cache_lru.head)
+#define DCE_NEXT(fce) ((struct disk_cache_entry *)(fce)->node.next)
+#define NODE_DCE(node) ((struct disk_cache_entry *)(node))
+
+/* get the cache index from a pointer to a buffer */
+#define DCIDX_FROM_BUF(buf) \
+ ((uint8_t (*)[DC_CACHE_BUFSIZE])(buf) - cache_buffer)
+
+#define DCIDX_FROM_DCE(dce) \
+ ((dce) - cache_entry)
+
+/* set the in-use bit in the map */
+static inline void cache_bitmap_set_bit(int volume, unsigned int mapnum,
+ unsigned int bitnum)
+{
+ cache_map_set_bit(&CACHE_MAP_ENTRY(volume, mapnum), bitnum);
+ cache_map_set_bit(&CACHE_VOL_MAP(volume), bitnum);
+ (void)volume;
+}
+
+/* clear the in-use bit in the map */
+static inline void cache_bitmap_clear_bit(int volume, unsigned int mapnum,
+ unsigned int bitnum)
+{
+ cache_map_clear_bit(&CACHE_MAP_ENTRY(volume, mapnum), bitnum);
+ cache_map_clear_bit(&CACHE_VOL_MAP(volume), bitnum);
+ (void)volume;
+}
+
+/* make entry MRU by moving it to the list tail */
+static inline void touch_cache_entry(struct disk_cache_entry *which)
+{
+ struct lldc_node *lru = cache_lru.head;
+ struct lldc_node *node = &which->node;
+
+ if (node == lru->prev) /* already MRU */
+ ; /**/
+ else if (node == lru) /* is the LRU? just rotate list */
+ cache_lru.head = lru->next;
+ else /* somewhere else; move it */
+ {
+ lldc_remove(&cache_lru, node);
+ lldc_insert_last(&cache_lru, node);
+ }
+}
+
+/* remove LRU entry from the cache list to use as a buffer */
+static struct disk_cache_entry * cache_remove_lru_entry(void)
+{
+ struct lldc_node *lru = cache_lru.head;
+
+ /* at least one is reserved for client */
+ if (lru == lru->next)
+ return NULL;
+
+ /* remove it; next-LRU becomes the LRU */
+ lldc_remove(&cache_lru, lru);
+ return NODE_DCE(lru);
+}
+
+/* return entry to the cache list and set it LRU */
+static void cache_return_lru_entry(struct disk_cache_entry *fce)
+{
+ lldc_insert_first(&cache_lru, &fce->node);
+}
+
+/* discard the entry's data and mark it unused */
+static inline void cache_discard_entry(struct disk_cache_entry *dce,
+ unsigned int index)
+{
+ cache_bitmap_clear_bit(IF_MV_VOL(dce->volume), map_sector(dce->sector),
+ index);
+ dce->flags = 0;
+}
+
+/* search the cache for the specified sector, returning a buffer, either
+ to the specified sector, if it exists, or a new/evicted entry that must
+ be filled */
+void * dc_cache_probe(IF_MV(int volume,) unsigned long sector,
+ unsigned int *flagsp)
+{
+ unsigned int mapnum = map_sector(sector);
+
+ FOR_EACH_BITARRAY_SET_BIT(&CACHE_MAP_ENTRY(volume, mapnum), index)
+ {
+ struct disk_cache_entry *dce = &cache_entry[index];
+
+ if (dce->sector == sector)
+ {
+ *flagsp = DCE_INUSE;
+ touch_cache_entry(dce);
+ return cache_buffer[index];
+ }
+ }
+
+ /* sector not found so the LRU is the victim */
+ struct disk_cache_entry *dce = DCE_LRU();
+ cache_lru.head = dce->node.next;
+
+ unsigned int index = DCIDX_FROM_DCE(dce);
+ void *buf = cache_buffer[index];
+ unsigned int old_flags = dce->flags;
+
+ if (old_flags)
+ {
+ int old_volume = IF_MV_VOL(dce->volume);
+ unsigned long sector = dce->sector;
+ unsigned int old_mapnum = map_sector(sector);
+
+ if (old_flags & DCE_DIRTY)
+ dc_writeback_callback(IF_MV(old_volume,) sector, buf);
+
+ if (mapnum == old_mapnum IF_MV( && volume == old_volume ))
+ goto finish_setup;
+
+ cache_bitmap_clear_bit(old_volume, old_mapnum, index);
+ }
+
+ cache_bitmap_set_bit(IF_MV_VOL(volume), mapnum, index);
+
+finish_setup:
+ dce->flags = DCE_INUSE;
+#ifdef HAVE_MULTIVOLUME
+ dce->volume = volume;
+#endif
+ dce->sector = sector;
+
+ *flagsp = 0;
+ return buf;
+}
+
+/* mark in-use cache entry as dirty by buffer */
+void dc_dirty_buf(void *buf)
+{
+ unsigned int index = DCIDX_FROM_BUF(buf);
+
+ if (index >= DC_NUM_ENTRIES)
+ return;
+
+ /* dirt remains, sticky until flushed */
+ struct disk_cache_entry *fce = &cache_entry[index];
+ if (fce->flags & DCE_INUSE)
+ fce->flags |= DCE_DIRTY;
+}
+
+/* discard in-use cache entry by buffer */
+void dc_discard_buf(void *buf)
+{
+ unsigned int index = DCIDX_FROM_BUF(buf);
+
+ if (index >= DC_NUM_ENTRIES)
+ return;
+
+ struct disk_cache_entry *dce = &cache_entry[index];
+ if (dce->flags & DCE_INUSE)
+ cache_discard_entry(dce, index);
+}
+
+/* commit all dirty cache entries to storage for a specified volume */
+void dc_commit_all(IF_MV_NONVOID(int volume))
+{
+ DEBUGF("dc_commit_all()\n");
+
+ FOR_EACH_BITARRAY_SET_BIT(&CACHE_VOL_MAP(volume), index)
+ {
+ struct disk_cache_entry *dce = &cache_entry[index];
+ unsigned int flags = dce->flags;
+
+ if (flags & DCE_DIRTY)
+ {
+ dc_writeback_callback(IF_MV(volume,) dce->sector,
+ cache_buffer[index]);
+ dce->flags = flags & ~DCE_DIRTY;
+ }
+ }
+}
+
+/* discard all cache entries from the specified volume */
+void dc_discard_all(IF_MV_NONVOID(int volume))
+{
+ DEBUGF("dc_discard_all()\n");
+
+ FOR_EACH_BITARRAY_SET_BIT(&CACHE_VOL_MAP(volume), index)
+ cache_discard_entry(&cache_entry[index], index);
+}
+
+/* expropriate a buffer from the cache */
+void * dc_get_buffer(void)
+{
+ dc_lock_cache();
+
+ void *buf = NULL;
+ struct disk_cache_entry *dce = cache_remove_lru_entry();
+
+ if (dce)
+ {
+ unsigned int index = DCIDX_FROM_DCE(dce);
+ unsigned int flags = dce->flags;
+
+ buf = cache_buffer[index];
+
+ if (flags)
+ {
+ /* must first commit this sector if dirty */
+ if (flags & DCE_DIRTY)
+ dc_writeback_callback(IF_MV(dce->volume,) dce->sector, buf);
+
+ cache_discard_entry(dce, index);
+ }
+
+ dce->flags = DCE_BUF;
+ }
+ /* cache is out of buffers */
+
+ dc_unlock_cache();
+ return buf;
+}
+
+/* return buffer to the cache by buffer */
+void dc_release_buffer(void *buf)
+{
+ unsigned int index = DCIDX_FROM_BUF(buf);
+
+ if (index >= DC_NUM_ENTRIES)
+ return;
+
+ dc_lock_cache();
+
+ struct disk_cache_entry *dce = &cache_entry[index];
+
+ if (dce->flags & DCE_BUF)
+ {
+ dce->flags = 0;
+ cache_return_lru_entry(dce);
+ }
+
+ dc_unlock_cache();
+}
+
+/* one-time init at startup */
+void dc_init(void)
+{
+ mutex_init(&disk_cache_mutex);
+ lldc_init(&cache_lru);
+ for (unsigned int i = 0; i < DC_NUM_ENTRIES; i++)
+ lldc_insert_last(&cache_lru, &cache_entry[i].node);
+}
diff --git a/firmware/common/file.c b/firmware/common/file.c
index 920eada84e..7d3b5092ae 100644
--- a/firmware/common/file.c
+++ b/firmware/common/file.c
@@ -8,6 +8,7 @@
* $Id$
*
* Copyright (C) 2002 by Björn Stenberg
+ * 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,819 +19,1201 @@
* KIND, either express or implied.
*
****************************************************************************/
+#define RB_FILESYSTEM_OS
+#include "config.h"
+#include "system.h"
#include <string.h>
#include <errno.h>
-#include <stdbool.h>
-#include "file.h"
-#include "fat.h"
-#include "dir_uncached.h"
#include "debug.h"
-#include "dircache.h"
-#include "filefuncs.h"
-#include "system.h"
+#include "file.h"
+#include "fileobj_mgr.h"
+#include "disk_cache.h"
+#include "dircache_redirect.h"
+#include "string-extra.h"
+
+/**
+ * These functions provide a roughly POSIX-compatible file I/O API.
+ */
+
+/* structure used for open file descriptors */
+static struct filestr_desc
+{
+ struct filestr_base stream; /* basic stream info (first!) */
+ file_size_t offset; /* current offset for stream */
+ file_size_t *sizep; /* shortcut to file size in fileobj */
+} open_streams[MAX_OPEN_FILES];
-/*
- These functions provide a roughly POSIX-compatible file IO API.
+/* check and return a struct filestr_desc* from a file descriptor number */
+static struct filestr_desc * get_filestr(int fildes)
+{
+ struct filestr_desc *file = &open_streams[fildes];
- Since the fat32 driver only manages sectors, we maintain a one-sector
- cache for each open file. This way we can provide byte access without
- having to re-read the sector each time.
- The penalty is the RAM used for the cache and slightly more complex code.
-*/
+ if ((unsigned int)fildes >= MAX_OPEN_FILES)
+ file = NULL;
+ else if (file->stream.flags & FDO_BUSY)
+ return file;
-struct filedesc {
- unsigned char cache[SECTOR_SIZE] CACHEALIGN_ATTR;
- int cacheoffset; /* invariant: 0 <= cacheoffset <= SECTOR_SIZE */
- long fileoffset;
- long size;
- int attr;
- struct fat_file fatfile;
- bool busy;
- bool write;
- bool dirty;
- bool trunc;
-} CACHEALIGN_ATTR;
+ DEBUGF("fildes %d: bad file number\n", fildes);
+ errno = (file && file->stream.flags == FV_NONEXIST) ? ENXIO : EBADF;
+ return NULL;
+}
-static struct filedesc openfiles[MAX_OPEN_FILES] CACHEALIGN_ATTR;
+#define GET_FILESTR(type, fildes) \
+ ({ \
+ file_internal_lock_##type(); \
+ struct filestr_desc * _file = get_filestr(fildes); \
+ if (_file) \
+ FILESTR_LOCK(type, &_file->stream); \
+ else \
+ file_internal_unlock_##type(); \
+ _file; \
+ })
+
+/* release the lock on the filestr_desc* */
+#define RELEASE_FILESTR(type, file) \
+ ({ \
+ FILESTR_UNLOCK(type, &(file)->stream); \
+ file_internal_unlock_##type(); \
+ })
+
+/* find a free file descriptor */
+static int alloc_filestr(struct filestr_desc **filep)
+{
+ for (int fildes = 0; fildes < MAX_OPEN_FILES; fildes++)
+ {
+ struct filestr_desc *file = &open_streams[fildes];
+ if (!file->stream.flags)
+ {
+ *filep = file;
+ return fildes;
+ }
+ }
-static int flush_cache(int fd);
+ DEBUGF("Too many files open\n");
+ return -1;
+}
-int file_creat(const char *pathname)
+/* return the file size in sectors */
+static inline unsigned long filesize_sectors(file_size_t size)
{
- return open(pathname, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ /* overflow proof whereas "(x + y - 1) / y" is not */
+ unsigned long numsectors = size / SECTOR_SIZE;
+
+ if (size % SECTOR_SIZE)
+ numsectors++;
+
+ return numsectors;
}
-static int open_internal(const char* pathname, int flags, bool use_cache)
+/* flush a dirty cache buffer */
+static int flush_cache(struct filestr_desc *file)
{
- DIR_UNCACHED* dir;
- struct dirent_uncached* entry;
- int fd;
- int pathnamesize = strlen(pathname) + 1;
- char pathnamecopy[pathnamesize];
- char* name;
- struct filedesc* file = NULL;
int rc;
-#ifndef HAVE_DIRCACHE
- (void)use_cache;
-#endif
+ struct filestr_cache *cachep = file->stream.cachep;
- LDEBUGF("open(\"%s\",%d)\n",pathname,flags);
+ DEBUGF("Flushing dirty sector cache (%lu)\n", cachep->sector);
- if ( pathname[0] != '/' ) {
- DEBUGF("'%s' is not an absolute path.\n",pathname);
- DEBUGF("Only absolute pathnames supported at the moment\n");
- errno = EINVAL;
- return -1;
+ if (fat_query_sectornum(&file->stream.fatstr) != cachep->sector)
+ {
+ /* get on the correct sector */
+ rc = fat_seek(&file->stream.fatstr, cachep->sector);
+ if (rc < 0)
+ FILE_ERROR(EIO, rc * 10 - 1);
}
- /* find a free file descriptor */
- for ( fd=0; fd<MAX_OPEN_FILES; fd++ )
- if ( !openfiles[fd].busy )
- break;
-
- if ( fd == MAX_OPEN_FILES ) {
- DEBUGF("Too many files open\n");
- errno = EMFILE;
- return -2;
+ rc = fat_readwrite(&file->stream.fatstr, 1, cachep->buffer, true);
+ if (rc < 0)
+ {
+ if (rc == FAT_RC_ENOSPC)
+ FILE_ERROR(ENOSPC, RC);
+ else
+ FILE_ERROR(EIO, rc * 10 - 2);
}
- file = &openfiles[fd];
- memset(file, 0, sizeof(struct filedesc));
+ cachep->flags = 0;
+ return 1;
+file_error:
+ DEBUGF("Failed flushing cache: %d\n", rc);
+ return rc;
+}
+
+static void discard_cache(struct filestr_desc *file)
+{
+ struct filestr_cache *const cachep = file->stream.cachep;
+ cachep->flags = 0;
+}
- if (flags & (O_RDWR | O_WRONLY)) {
- file->write = true;
+/* set the file pointer */
+static off_t lseek_internal(struct filestr_desc *file, off_t offset,
+ int whence)
+{
+ off_t rc;
+ file_size_t pos;
- if (flags & O_TRUNC)
- file->trunc = true;
- }
- file->busy = true;
+ file_size_t size = MIN(*file->sizep, FILE_SIZE_MAX);
-#ifdef HAVE_DIRCACHE
- if (dircache_is_enabled() && !file->write && use_cache)
+ switch (whence)
{
-# ifdef HAVE_MULTIVOLUME
- int volume = strip_volume(pathname, pathnamecopy);
-# endif
+ case SEEK_SET:
+ if (offset < 0 || (file_size_t)offset > size)
+ FILE_ERROR(EINVAL, -1);
- int ce = dircache_get_entry_id(pathname);
- if (ce < 0)
- {
- errno = ENOENT;
- file->busy = false;
- return -7;
- }
+ pos = offset;
+ break;
- long startcluster = _dircache_get_entry_startcluster(ce);
- fat_open(IF_MV(volume,)
- startcluster,
- &(file->fatfile),
- NULL);
- struct dirinfo *info = _dircache_get_entry_dirinfo(ce);
- file->size = info->size;
- file->attr = info->attribute;
- file->cacheoffset = -1;
- file->fileoffset = 0;
+ case SEEK_CUR:
+ if ((offset < 0 && (file_size_t)-offset > file->offset) ||
+ (offset > 0 && (file_size_t)offset > size - file->offset))
+ FILE_ERROR(EINVAL, -1);
- return fd;
- }
-#endif
+ pos = file->offset + offset;
+ break;
- strlcpy(pathnamecopy, pathname, pathnamesize);
+ case SEEK_END:
+ if (offset > 0 || (file_size_t)-offset > size)
+ FILE_ERROR(EINVAL, -1);
- /* locate filename */
- name=strrchr(pathnamecopy+1,'/');
- if ( name ) {
- *name = 0;
- dir = opendir_uncached(pathnamecopy);
- *name = '/';
- name++;
- }
- else {
- dir = opendir_uncached("/");
- name = pathnamecopy+1;
- }
- if (!dir) {
- DEBUGF("Failed opening dir\n");
- errno = EIO;
- file->busy = false;
- return -4;
- }
-
- if(name[0] == 0) {
- DEBUGF("Empty file name\n");
- errno = EINVAL;
- file->busy = false;
- closedir_uncached(dir);
- return -5;
- }
-
- /* scan dir for name */
- while ((entry = readdir_uncached(dir))) {
- if ( !strcasecmp(name, entry->d_name) ) {
- fat_open(IF_MV(dir->fatdir.file.volume,)
- entry->startcluster,
- &(file->fatfile),
- &(dir->fatdir));
- file->size = file->trunc ? 0 : entry->info.size;
- file->attr = entry->info.attribute;
- break;
- }
+ pos = size + offset;
+ break;
+
+ default:
+ FILE_ERROR(EINVAL, -1);
}
- if ( !entry ) {
- LDEBUGF("Didn't find file %s\n",name);
- if ( file->write && (flags & O_CREAT) ) {
- rc = fat_create_file(name,
- &(file->fatfile),
- &(dir->fatdir));
- if (rc < 0) {
- DEBUGF("Couldn't create %s in %s\n",name,pathnamecopy);
- errno = EIO;
- file->busy = false;
- closedir_uncached(dir);
- return rc * 10 - 6;
+ file->offset = pos;
+
+ return pos;
+file_error:
+ return rc;
+}
+
+/* callback for each file stream to make sure all data is in sync with new
+ size */
+void ftruncate_internal_callback(struct filestr_base *stream,
+ struct filestr_base *s)
+{
+ struct filestr_desc *file = (struct filestr_desc *)s;
+ file_size_t size = *file->sizep;
+
+ /* caches with data beyond new extents are invalid */
+ unsigned long sector = file->stream.cachep->sector;
+ if (sector != INVALID_SECNUM && sector >= filesize_sectors(size))
+ filestr_discard_cache(&file->stream);
+
+ /* keep all positions within bounds */
+ if (file->offset > size)
+ file->offset = size;
+
+ (void)stream;
+}
+
+/* truncate the file to the specified length */
+static int ftruncate_internal(struct filestr_desc *file, file_size_t size,
+ bool write_now)
+{
+ int rc = 0, rc2 = 1;
+
+ file_size_t cursize = *file->sizep;
+ file_size_t truncsize = MIN(size, cursize);
+
+ if (write_now)
+ {
+ unsigned long sector = filesize_sectors(truncsize);
+ struct filestr_cache *const cachep = file->stream.cachep;
+
+ if (cachep->flags == (FSC_NEW|FSC_DIRTY) &&
+ cachep->sector + 1 == sector)
+ {
+ /* sector created but may have never been added to the cluster
+ chain; flush it now or the subsequent may fail */
+ rc2 = flush_cache(file);
+ if (rc2 == FAT_RC_ENOSPC)
+ {
+ /* no space left on device; further truncation needed */
+ discard_cache(file);
+ truncsize = ALIGN_DOWN(truncsize - 1, SECTOR_SIZE);
+ sector--;
+ rc = rc2;
}
-#ifdef HAVE_DIRCACHE
- dircache_add_file(pathname, file->fatfile.firstcluster);
-#endif
- file->size = 0;
- file->attr = 0;
- }
- else {
- DEBUGF("Couldn't find %s in %s\n",name,pathnamecopy);
- errno = ENOENT;
- file->busy = false;
- closedir_uncached(dir);
- return -7;
- }
- } else {
- if(file->write && (file->attr & FAT_ATTR_DIRECTORY)) {
- errno = EISDIR;
- file->busy = false;
- closedir_uncached(dir);
- return -8;
+ else if (rc2 < 0)
+ FILE_ERROR(ERRNO, rc2 * 10 - 1);
}
- }
- closedir_uncached(dir);
- file->cacheoffset = -1;
- file->fileoffset = 0;
+ rc2 = fat_seek(&file->stream.fatstr, sector);
+ if (rc2 < 0)
+ FILE_ERROR(EIO, rc2 * 10 - 2);
- if (file->write && (flags & O_APPEND)) {
- rc = lseek(fd,0,SEEK_END);
- if (rc < 0 )
- return rc * 10 - 9;
+ rc2 = fat_truncate(&file->stream.fatstr);
+ if (rc2 < 0)
+ FILE_ERROR(EIO, rc2 * 10 - 3);
}
+ /* else just change the cached file size */
-#ifdef HAVE_DIRCACHE
- if (file->write)
- dircache_bind(fd, pathname);
-#endif
+ if (truncsize < cursize)
+ {
+ *file->sizep = truncsize;
+ fileop_ontruncate_internal(&file->stream);
+ }
- return fd;
-}
+ /* if truncation was partially successful, it effectively destroyed
+ everything after the truncation point; still, indicate failure
+ after adjusting size */
+ if (rc2 == 0)
+ FILE_ERROR(EIO, -4);
+ else if (rc2 < 0)
+ FILE_ERROR(ERRNO, rc2);
-int file_open(const char* pathname, int flags)
-{
- /* By default, use the dircache if available. */
- return open_internal(pathname, flags, true);
+file_error:
+ return rc;
}
-int close(int fd)
+/* flush back all outstanding writes to the file */
+static int fsync_internal(struct filestr_desc *file)
{
- struct filedesc* file = &openfiles[fd];
+ /* call only when holding WRITER lock (updates directory entries) */
int rc = 0;
- LDEBUGF("close(%d)\n", fd);
+ file_size_t size = *file->sizep;
+ unsigned int foflags = fileobj_get_flags(&file->stream);
+
+ /* flush sector cache? */
+ struct filestr_cache *const cachep = file->stream.cachep;
+ if (cachep->flags & FSC_DIRTY)
+ {
+ int rc2 = flush_cache(file);
+ if (rc2 == FAT_RC_ENOSPC && (cachep->flags & FSC_NEW))
+ {
+ /* no space left on device so this must be dropped */
+ discard_cache(file);
+ size = ALIGN_DOWN(size - 1, SECTOR_SIZE);
+ foflags |= FO_TRUNC;
+ rc = rc2;
+ }
+ else if (rc2 < 0)
+ FILE_ERROR(ERRNO, rc2 * 10 - 1);
+ }
+
+ /* truncate? */
+ if (foflags & FO_TRUNC)
+ {
+ int rc2 = ftruncate_internal(file, size, rc == 0);
+ if (rc2 < 0)
+ FILE_ERROR(ERRNO, rc2 * 10 - 2);
- if (fd < 0 || fd > MAX_OPEN_FILES-1) {
- errno = EINVAL;
- return -1;
+ /* never needs to be done this way again since any data beyond the
+ cached size is now gone */
+ fileobj_change_flags(&file->stream, 0, FO_TRUNC);
}
- if (!file->busy) {
- errno = EBADF;
- return -2;
+
+file_error:;
+ /* tie up all loose ends (try to close the file even if failing) */
+ int rc2 = fat_closewrite(&file->stream.fatstr, size,
+ get_dir_fatent_dircache());
+ if (rc2 >= 0)
+ fileop_onsync_internal(&file->stream); /* dir_fatent is implicit arg */
+
+ if (rc2 < 0 && rc >= 0)
+ {
+ errno = EIO;
+ rc = rc2 * 10 - 3;
}
- if (file->write) {
- rc = fsync(fd);
+
+ return rc;
+}
+
+/* finish with the file and free resources */
+static int close_internal(struct filestr_desc *file)
+{
+ /* call only when holding WRITER lock (updates directory entries) */
+ int rc;
+
+ if ((file->stream.flags & FD_WRITE) &&
+ !(fileobj_get_flags(&file->stream) & FO_REMOVED))
+ {
+ rc = fsync_internal(file);
if (rc < 0)
- return rc * 10 - 3;
-#ifdef HAVE_DIRCACHE
- dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
- dircache_update_filetime(fd);
-#endif
+ FILE_ERROR(ERRNO, rc * 10 - 1);
}
- file->busy = false;
- return 0;
+ rc = 0;
+file_error:;
+ int rc2 = close_stream_internal(&file->stream);
+ if (rc2 < 0 && rc >= 0)
+ rc = rc2 * 10 - 2;
+ return rc;
}
-int fsync(int fd)
+/* actually do the open gruntwork */
+static int open_internal_inner2(const char *path,
+ struct filestr_desc *file,
+ unsigned int callflags)
{
- struct filedesc* file = &openfiles[fd];
- int rc = 0;
-
- LDEBUGF("fsync(%d)\n", fd);
+ int rc;
- if (fd < 0 || fd > MAX_OPEN_FILES-1) {
- errno = EINVAL;
- return -1;
- }
- if (!file->busy) {
- errno = EBADF;
- return -2;
+ struct path_component_info compinfo;
+ rc = open_stream_internal(path, callflags, &file->stream, &compinfo);
+ if (rc < 0)
+ {
+ DEBUGF("Open failed: %d\n", rc);
+ FILE_ERROR_RETURN(ERRNO, rc * 10 - 1);
}
- if (file->write) {
- /* flush sector cache */
- if ( file->dirty ) {
- rc = flush_cache(fd);
- if (rc < 0)
- {
- /* when failing, try to close the file anyway */
- fat_closewrite(&(file->fatfile), file->size, file->attr);
- return rc * 10 - 3;
- }
+
+ bool created = false;
+
+ if (rc > 0)
+ {
+ if (callflags & FF_EXCL)
+ {
+ DEBUGF("File exists\n");
+ FILE_ERROR(EEXIST, -2);
}
- /* truncate? */
- if (file->trunc) {
- rc = ftruncate(fd, file->size);
- if (rc < 0)
+ if (compinfo.attr & ATTR_DIRECTORY)
+ {
+ if ((callflags & FD_WRITE) || !(callflags & FF_ANYTYPE))
{
- /* when failing, try to close the file anyway */
- fat_closewrite(&(file->fatfile), file->size, file->attr);
- return rc * 10 - 4;
+ DEBUGF("File is a directory\n");
+ FILE_ERROR(EISDIR, -3);
}
+
+ compinfo.filesize = MAX_DIRECTORY_SIZE; /* allow file ops */
+ }
+ }
+ else if (callflags & FF_CREAT)
+ {
+ if (compinfo.attr & ATTR_DIRECTORY)
+ {
+ DEBUGF("File is a directory\n");
+ FILE_ERROR(EISDIR, -5);
}
- /* tie up all loose ends */
- rc = fat_closewrite(&(file->fatfile), file->size, file->attr);
+ /* not found; try to create it */
+
+ callflags &= ~FO_TRUNC;
+ rc = create_stream_internal(&compinfo.parentinfo, compinfo.name,
+ compinfo.length, ATTR_NEW_FILE, callflags,
+ &file->stream);
if (rc < 0)
- return rc * 10 - 5;
- }
- return 0;
-}
+ FILE_ERROR(ERRNO, rc * 10 - 6);
-int remove(const char* name)
-{
- int rc;
- struct filedesc* file;
- /* Can't use dircache now, because we need to access the fat structures. */
- int fd = open_internal(name, O_WRONLY, false);
- if ( fd < 0 )
- return fd * 10 - 1;
-
- file = &openfiles[fd];
-#ifdef HAVE_DIRCACHE
- dircache_remove(name);
-#endif
- rc = fat_remove(&(file->fatfile));
- if ( rc < 0 ) {
- DEBUGF("Failed removing file: %d\n", rc);
- errno = EIO;
- return rc * 10 - 3;
+ created = true;
}
+ else
+ {
+ DEBUGF("File not found\n");
+ FILE_ERROR(ENOENT, -7);
+ }
+
+ fat_rewind(&file->stream.fatstr);
+ file->sizep = fileobj_get_sizep(&file->stream);
+ file->offset = 0;
+
+ if (!created)
+ {
+ /* size from storage applies to first stream only otherwise it's
+ already up to date */
+ const bool first = fileobj_get_flags(&file->stream) & FO_SINGLE;
+ if (first)
+ *file->sizep = compinfo.filesize;
- file->size = 0;
+ if (callflags & FO_TRUNC)
+ {
+ /* if the file is kind of "big" then free some space now */
+ rc = ftruncate_internal(file, 0, *file->sizep >= O_TRUNC_THRESH);
+ if (rc < 0)
+ {
+ DEBUGF("O_TRUNC failed: %d\n", rc);
+ FILE_ERROR(ERRNO, rc * 10 - 4);
+ }
+ }
+ }
- rc = close(fd);
- if (rc<0)
- return rc * 10 - 4;
+ rc = 0;
+file_error:
+ if (rc < 0)
+ close_stream_internal(&file->stream);
- return 0;
+ return rc;
}
-int rename(const char* path, const char* newpath)
+/* allocate a file descriptor, if needed, assemble stream flags and open
+ a new stream */
+static int open_internal_inner1(const char *path, int oflag,
+ unsigned int callflags)
{
- int rc, fd;
- DIR_UNCACHED* dir;
- char* nameptr;
- char* dirptr;
- struct filedesc* file;
- char newpath2[MAX_PATH];
+ DEBUGF("%s(path=\"%s\",oflag=%X,callflags=%X)\n", __func__,
+ path, oflag, callflags);
- /* verify new path does not already exist */
- /* If it is a directory, errno == EISDIR if the name exists */
- fd = open(newpath, O_RDONLY);
- if ( fd >= 0 || errno == EISDIR) {
- close(fd);
- errno = EBUSY;
- return -1;
- }
- close(fd);
+ int rc;
- fd = open_internal(path, O_RDONLY, false);
- if ( fd < 0 ) {
- errno = EIO;
- return fd * 10 - 2;
- }
+ struct filestr_desc *file;
+ int fildes = alloc_filestr(&file);
+ if (fildes < 0)
+ FILE_ERROR(EMFILE, -1);
- /* extract new file name */
- nameptr = strrchr(newpath,'/');
- if (nameptr)
- nameptr++;
- else {
- close(fd);
- return - 3;
- }
+ callflags &= ~FDO_MASK;
- /* Extract new path */
- strcpy(newpath2, newpath);
+ if (oflag & O_ACCMODE)
+ {
+ callflags |= FD_WRITE;
- dirptr = strrchr(newpath2,'/');
- if(dirptr)
- *dirptr = 0;
- else {
- close(fd);
- return - 4;
- }
+ if ((oflag & O_ACCMODE) == O_WRONLY)
+ callflags |= FD_WRONLY;
- dirptr = newpath2;
+ if (oflag & O_APPEND)
+ callflags |= FD_APPEND;
- if(strlen(dirptr) == 0) {
- dirptr = "/";
+ if (oflag & O_TRUNC)
+ callflags |= FO_TRUNC;
}
-
- dir = opendir_uncached(dirptr);
- if(!dir) {
- close(fd);
- return - 5;
+ else if (oflag & O_TRUNC)
+ {
+ /* O_TRUNC requires write mode */
+ DEBUGF("No write mode but have O_TRUNC\n");
+ FILE_ERROR(EINVAL, -2);
}
- file = &openfiles[fd];
+ /* O_CREAT and O_APPEND are fine without write mode
+ * for the former, an empty file is created but no data may be written
+ * for the latter, no append will be allowed anyway */
+ if (oflag & O_CREAT)
+ {
+ callflags |= FF_CREAT;
- rc = fat_rename(&file->fatfile, &dir->fatdir, nameptr,
- file->size, file->attr);
-#ifdef HAVE_MULTIVOLUME
- if ( rc == -1) {
- close(fd);
- closedir_uncached(dir);
- DEBUGF("Failed renaming file across volumnes: %d\n", rc);
- errno = EXDEV;
- return -6;
- }
-#endif
- if ( rc < 0 ) {
- close(fd);
- closedir_uncached(dir);
- DEBUGF("Failed renaming file: %d\n", rc);
- errno = EIO;
- return rc * 10 - 7;
+ if (oflag & O_EXCL)
+ callflags |= FF_EXCL;
}
-#ifdef HAVE_DIRCACHE
- dircache_rename(path, newpath);
-#endif
+ rc = open_internal_inner2(path, file, callflags);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 3);
- rc = close(fd);
- if (rc<0) {
- closedir_uncached(dir);
- errno = EIO;
- return rc * 10 - 8;
- }
+ return fildes;
- rc = closedir_uncached(dir);
- if (rc<0) {
- errno = EIO;
- return rc * 10 - 9;
- }
+file_error:
+ return rc;
+}
- return 0;
+static int open_internal_locked(const char *path, int oflag,
+ unsigned int callflags)
+{
+ file_internal_lock_WRITER();
+ int rc = open_internal_inner1(path, oflag, callflags);
+ file_internal_unlock_WRITER();
+ return rc;
}
-int ftruncate(int fd, off_t size)
+/* fill a cache buffer with a new sector */
+static int readwrite_fill_cache(struct filestr_desc *file, unsigned long sector,
+ unsigned long filesectors, bool write)
{
- int rc, sector;
- struct filedesc* file = &openfiles[fd];
+ /* sector != cachep->sector should have been checked by now */
- sector = size / SECTOR_SIZE;
- if (size % SECTOR_SIZE)
- sector++;
+ int rc;
+ struct filestr_cache *cachep = filestr_get_cache(&file->stream);
- rc = fat_seek(&(file->fatfile), sector);
- if (rc < 0) {
- errno = EIO;
- return rc * 10 - 1;
+ if (cachep->flags & FSC_DIRTY)
+ {
+ rc = flush_cache(file);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 1);
}
- rc = fat_truncate(&(file->fatfile));
- if (rc < 0) {
- errno = EIO;
- return rc * 10 - 2;
+ if (fat_query_sectornum(&file->stream.fatstr) != sector)
+ {
+ /* get on the correct sector */
+ rc = fat_seek(&file->stream.fatstr, sector);
+ if (rc < 0)
+ FILE_ERROR(EIO, rc * 10 - 2);
}
- file->size = size;
-#ifdef HAVE_DIRCACHE
- dircache_update_filesize(fd, size, file->fatfile.firstcluster);
-#endif
+ if (!write || sector < filesectors)
+ {
+ /* only reading or this sector would have been flushed if the cache
+ was previously needed for a different sector */
+ rc = fat_readwrite(&file->stream.fatstr, 1, cachep->buffer, false);
+ if (rc < 0)
+ FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 3);
+ }
+ else
+ {
+ /* create a fresh, shiny, new sector with that new sector smell */
+ cachep->flags = FSC_NEW;
+ }
- return 0;
+ cachep->sector = sector;
+ return 1;
+file_error:
+ DEBUGF("Failed caching sector: %d\n", rc);
+ return rc;
}
-static int flush_cache(int fd)
+/* read or write to part or all of the cache buffer */
+static inline void readwrite_cache(struct filestr_cache *cachep, void *buf,
+ unsigned long secoffset, size_t nbyte,
+ bool write)
{
- int rc;
- struct filedesc* file = &openfiles[fd];
- long sector = file->fileoffset / SECTOR_SIZE;
-
- DEBUGF("Flushing dirty sector cache\n");
+ void *dst, *cbufp = cachep->buffer + secoffset;
- /* make sure we are on correct sector */
- rc = fat_seek(&(file->fatfile), sector);
- if ( rc < 0 )
- return rc * 10 - 3;
-
- rc = fat_readwrite(&(file->fatfile), 1, file->cache, true );
+ if (write)
+ {
+ dst = cbufp;
+ cachep->flags |= FSC_DIRTY;
+ }
+ else
+ {
+ dst = buf;
+ buf = cbufp;
+ }
- if ( rc < 0 ) {
- if(file->fatfile.eof)
- errno = ENOSPC;
+ memcpy(dst, buf, nbyte);
+}
- return rc * 10 - 2;
+/* read or write a partial sector using the file's cache */
+static inline ssize_t readwrite_partial(struct filestr_desc *file,
+ struct filestr_cache *cachep,
+ unsigned long sector,
+ unsigned long secoffset,
+ void *buf,
+ size_t nbyte,
+ unsigned long filesectors,
+ unsigned int flags)
+{
+ if (sector != cachep->sector)
+ {
+ /* wrong sector in buffer */
+ int rc = readwrite_fill_cache(file, sector, filesectors, flags);
+ if (rc <= 0)
+ return rc;
}
- file->dirty = false;
-
- return 0;
+ readwrite_cache(cachep, buf, secoffset, nbyte, flags);
+ return nbyte;
}
-static int readwrite(int fd, void* buf, long count, bool write)
+/* read from or write to the file; back end to read() and write() */
+static ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte,
+ bool write)
{
- long sectors;
- long nread=0;
- struct filedesc* file;
- int rc;
-#ifdef STORAGE_NEEDS_ALIGN
- long i;
- int rc2;
-#endif
+ DEBUGF("readwrite(%p,%lx,%ld,%s)\n",
+ file, (long)buf, nbyte, write ? "write" : "read");
- if (fd < 0 || fd > MAX_OPEN_FILES-1) {
- errno = EINVAL;
- return -1;
- }
+ const file_size_t size = *file->sizep;
+ file_size_t filerem;
- file = &openfiles[fd];
+ if (write)
+ {
+ /* if opened in append mode, move pointer to end */
+ if (file->stream.flags & FD_APPEND)
+ file->offset = MIN(size, FILE_SIZE_MAX);
- if ( !file->busy ) {
- errno = EBADF;
- return -1;
+ filerem = FILE_SIZE_MAX - file->offset;
+ }
+ else
+ {
+ /* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */
+ filerem = MIN(size, FILE_SIZE_MAX) - file->offset;
}
- if(file->attr & FAT_ATTR_DIRECTORY) {
- errno = EISDIR;
- return -1;
+ if (nbyte > filerem)
+ {
+ nbyte = filerem;
+ if (nbyte > 0)
+ {}
+ else if (write)
+ FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */
+ else if (file->offset >= FILE_SIZE_MAX)
+ FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */
}
- LDEBUGF( "readwrite(%d,%lx,%ld,%s)\n",
- fd,(long)buf,count,write?"write":"read");
+ if (nbyte == 0)
+ return 0;
- /* attempt to read past EOF? */
- if (!write && count > file->size - file->fileoffset)
- count = file->size - file->fileoffset;
+ int rc = 0;
+
+ struct filestr_cache * const cachep = file->stream.cachep;
+ void * const bufstart = buf;
+
+ const unsigned long filesectors = filesize_sectors(size);
+ unsigned long sector = file->offset / SECTOR_SIZE;
+ unsigned long sectoroffs = file->offset % SECTOR_SIZE;
/* any head bytes? */
- if ( file->cacheoffset != -1 ) {
- int offs = file->cacheoffset;
- int headbytes = MIN(count, SECTOR_SIZE - offs);
+ if (sectoroffs)
+ {
+ size_t headbytes = MIN(nbyte, SECTOR_SIZE - sectoroffs);
+ rc = readwrite_partial(file, cachep, sector, sectoroffs, buf, headbytes,
+ filesectors, write);
+ if (rc <= 0)
+ {
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 3);
- if (write) {
- memcpy( file->cache + offs, buf, headbytes );
- file->dirty = true;
+ nbyte = 0; /* eof, skip the rest */
}
- else {
- memcpy( buf, file->cache + offs, headbytes );
+ else
+ {
+ buf += rc;
+ nbyte -= rc;
+ sector++; /* if nbyte goes to 0, the rest is skipped anyway */
}
+ }
- if (offs + headbytes == SECTOR_SIZE) {
- if (file->dirty) {
- rc = flush_cache(fd);
- if ( rc < 0 ) {
- errno = EIO;
- return rc * 10 - 2;
- }
- }
- file->cacheoffset = -1;
- }
- else {
- file->cacheoffset += headbytes;
- }
+ /* read/write whole sectors right into/from the supplied buffer */
+ unsigned long sectorcount = nbyte / SECTOR_SIZE;
- nread = headbytes;
- count -= headbytes;
- }
+ while (sectorcount)
+ {
+ unsigned long runlen = sectorcount;
- /* If the buffer has been modified, either it has been flushed already
- * (if (offs+headbytes == SECTOR_SIZE)...) or does not need to be (no
- * more data to follow in this call). Do NOT flush here. */
+ /* if a cached sector is inside the transfer range, split the transfer
+ into two parts and use the cache for that sector to keep it coherent
+ without writeback */
+ if (UNLIKELY(cachep->sector >= sector &&
+ cachep->sector < sector + sectorcount))
+ {
+ runlen = cachep->sector - sector;
+ }
- /* read/write whole sectors right into/from the supplied buffer */
- sectors = count / SECTOR_SIZE;
- rc = 0;
- if ( sectors ) {
-#ifdef STORAGE_NEEDS_ALIGN
- if (((uint32_t)buf + nread) & (CACHEALIGN_SIZE - 1))
- for (i = 0; i < sectors; i++)
+ if (runlen)
+ {
+ if (fat_query_sectornum(&file->stream.fatstr) != sector)
{
- if (write) memcpy(file->cache, buf+nread+i*SECTOR_SIZE, SECTOR_SIZE);
- rc2 = fat_readwrite(&(file->fatfile), 1, file->cache, write );
- if (rc2 < 0)
+ /* get on the correct sector */
+ rc = 0;
+
+ /* If the dirty bit isn't set, we're somehow beyond the file
+ size and you can't explain _that_ */
+ if (sector >= filesectors && cachep->flags == (FSC_NEW|FSC_DIRTY))
{
- rc = rc2;
- break;
+ rc = flush_cache(file);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 4);
+
+ if (cachep->sector + 1 == sector)
+ rc = 1; /* if now ok, don't seek */
+ }
+
+ if (rc == 0)
+ {
+ rc = fat_seek(&file->stream.fatstr, sector);
+ if (rc < 0)
+ FILE_ERROR(EIO, rc * 10 - 5);
}
- else rc += rc2;
- if (!write) memcpy(buf+nread+i*SECTOR_SIZE, file->cache, SECTOR_SIZE);
- }
- else
-#endif
- rc = fat_readwrite(&(file->fatfile), sectors, (unsigned char*)buf+nread, write );
- if ( rc < 0 ) {
- DEBUGF("Failed read/writing %ld sectors\n",sectors);
- errno = EIO;
- if(write && file->fatfile.eof) {
- DEBUGF("No space left on device\n");
- errno = ENOSPC;
- } else {
- file->fileoffset += nread;
}
- file->cacheoffset = -1;
- /* adjust file size to length written */
- if ( write && file->fileoffset > file->size )
+
+ rc = fat_readwrite(&file->stream.fatstr, runlen, buf, write);
+ if (rc < 0)
{
- file->size = file->fileoffset;
-#ifdef HAVE_DIRCACHE
- dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
-#endif
+ DEBUGF("I/O error %sing %ld sectors\n", sectors,
+ write ? "writ" : "read");
+ FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO,
+ rc * 10 - 6);
}
- return nread ? nread : rc * 10 - 4;
- }
- else {
- if ( rc > 0 ) {
- nread += rc * SECTOR_SIZE;
- count -= sectors * SECTOR_SIZE;
+ else
+ {
+ buf += rc * SECTOR_SIZE;
+ nbyte -= rc * SECTOR_SIZE;
+ sector += rc;
+ sectorcount -= rc;
/* if eof, skip tail bytes */
- if ( rc < sectors )
- count = 0;
- }
- else {
- /* eof */
- count=0;
+ if ((unsigned long)rc < runlen)
+ nbyte = 0;
+
+ if (!nbyte)
+ break;
}
+ }
- file->cacheoffset = -1;
+ if (UNLIKELY(sectorcount && sector == cachep->sector))
+ {
+ /* do this one sector with the cache */
+ readwrite_cache(cachep, buf, 0, SECTOR_SIZE, write);
+ buf += SECTOR_SIZE;
+ nbyte -= SECTOR_SIZE;
+ sector++;
+ sectorcount--;
}
}
/* any tail bytes? */
- if ( count ) {
- if (write) {
- if ( file->fileoffset + nread < file->size ) {
- /* sector is only partially filled. copy-back from disk */
- LDEBUGF("Copy-back tail cache\n");
- rc = fat_readwrite(&(file->fatfile), 1, file->cache, false );
- if ( rc < 0 ) {
- DEBUGF("Failed writing\n");
- errno = EIO;
- file->fileoffset += nread;
- file->cacheoffset = -1;
- /* adjust file size to length written */
- if ( file->fileoffset > file->size )
- {
- file->size = file->fileoffset;
-#ifdef HAVE_DIRCACHE
- dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
-#endif
- }
- return nread ? nread : rc * 10 - 5;
- }
- /* seek back one sector to put file position right */
- rc = fat_seek(&(file->fatfile),
- (file->fileoffset + nread) /
- SECTOR_SIZE);
- if ( rc < 0 ) {
- DEBUGF("fat_seek() failed\n");
- errno = EIO;
- file->fileoffset += nread;
- file->cacheoffset = -1;
- /* adjust file size to length written */
- if ( file->fileoffset > file->size )
- {
- file->size = file->fileoffset;
-#ifdef HAVE_DIRCACHE
- dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
-#endif
- }
- return nread ? nread : rc * 10 - 6;
- }
- }
- memcpy( file->cache, (unsigned char*)buf + nread, count );
- file->dirty = true;
- }
- else {
- rc = fat_readwrite(&(file->fatfile), 1, file->cache,false);
- if (rc < 1 ) {
- DEBUGF("Failed caching sector\n");
- errno = EIO;
- file->fileoffset += nread;
- file->cacheoffset = -1;
- return nread ? nread : rc * 10 - 7;
- }
- memcpy( (unsigned char*)buf + nread, file->cache, count );
- }
+ if (nbyte)
+ {
+ /* tail bytes always start at sector offset 0 */
+ rc = readwrite_partial(file, cachep, sector, 0, buf, nbyte,
+ filesectors, write);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 7);
- nread += count;
- file->cacheoffset = count;
+ buf += rc;
}
- file->fileoffset += nread;
- LDEBUGF("fileoffset: %ld\n", file->fileoffset);
+file_error:;
+#ifdef DEBUG
+ if (errno == ENOSPC)
+ DEBUGF("No space left on device\n");
+#endif
- /* adjust file size to length written */
- if ( write && file->fileoffset > file->size )
+ size_t done = buf - bufstart;
+ if (done)
{
- file->size = file->fileoffset;
-#ifdef HAVE_DIRCACHE
- dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
-#endif
+ /* error or not, update the file offset and size if anything was
+ transferred */
+ file->offset += done;
+ DEBUGF("file offset: %ld\n", file->offset);
+
+ /* adjust file size to length written */
+ if (write && file->offset > size)
+ *file->sizep = file->offset;
+
+ if (rc > 0)
+ return done;
}
- return nread;
+ return rc;
}
-ssize_t write(int fd, const void* buf, size_t count)
+
+/** Internal interface **/
+
+/* open a file without codepage conversion during the directory search;
+ required to avoid any reentrancy when opening codepages and when scanning
+ directories internally, which could infinitely recurse and would corrupt
+ the static data */
+int open_noiso_internal(const char *path, int oflag)
{
- if (!openfiles[fd].write) {
- errno = EACCES;
- return -1;
- }
- return readwrite(fd, (void *)buf, count, true);
+ return open_internal_locked(path, oflag, FF_ANYTYPE | FF_NOISO);
}
-ssize_t read(int fd, void* buf, size_t count)
+
+/** POSIX **/
+
+/* open a file */
+int open(const char *path, int oflag)
{
- return readwrite(fd, buf, count, false);
+ DEBUGF("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag);
+ return open_internal_locked(path, oflag, FF_ANYTYPE);
}
+/* create a new file or rewrite an existing one */
+int creat(const char *path)
+{
+ DEBUGF("creat(path=\"%s\")\n", path);
+ return open_internal_locked(path, O_WRONLY|O_CREAT|O_TRUNC, FF_ANYTYPE);
+}
-off_t lseek(int fd, off_t offset, int whence)
+/* close a file descriptor */
+int close(int fildes)
{
- off_t pos;
- long newsector;
- long oldsector;
- int sectoroffset;
+ DEBUGF("close(fd=%d)\n", fildes);
+
int rc;
- struct filedesc* file = &openfiles[fd];
- LDEBUGF("lseek(%d,%ld,%d)\n",fd,offset,whence);
+ file_internal_lock_WRITER();
- if (fd < 0 || fd > MAX_OPEN_FILES-1) {
- errno = EINVAL;
- return -1;
- }
- if ( !file->busy ) {
- errno = EBADF;
- return -1;
+ /* needs to work even if marked "nonexistant" */
+ struct filestr_desc *file = &open_streams[fildes];
+ if ((unsigned int)fildes >= MAX_OPEN_FILES || !file->stream.flags)
+ {
+ DEBUGF("filedes %d not open\n", fildes);
+ FILE_ERROR(EBADF, -2);
}
- switch ( whence ) {
- case SEEK_SET:
- pos = offset;
- break;
+ rc = close_internal(file);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 3);
- case SEEK_CUR:
- pos = file->fileoffset + offset;
- break;
+file_error:
+ file_internal_unlock_WRITER();
+ return rc;
+}
+
+/* truncate a file to a specified length */
+int ftruncate(int fildes, off_t length)
+{
+ DEBUGF("ftruncate(fd=%d,len=%ld)\n", fildes, (long)length);
- case SEEK_END:
- pos = file->size + offset;
- break;
+ struct filestr_desc * const file = GET_FILESTR(READER, fildes);
+ if (!file)
+ FILE_ERROR_RETURN(ERRNO, -1);
- default:
- errno = EINVAL;
- return -2;
+ int rc;
+
+ if (!(file->stream.flags & FD_WRITE))
+ {
+ DEBUGF("Descriptor is read-only mode\n");
+ FILE_ERROR(EBADF, -2);
}
- if ((pos < 0) || (pos > file->size)) {
- errno = EINVAL;
- return -3;
+
+ if (length < 0)
+ {
+ DEBUGF("Length %ld is invalid\n", (long)length);
+ FILE_ERROR(EINVAL, -3);
}
- /* new sector? */
- newsector = pos / SECTOR_SIZE;
- oldsector = file->fileoffset / SECTOR_SIZE;
- sectoroffset = pos % SECTOR_SIZE;
+ rc = ftruncate_internal(file, length, true);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 4);
- if ( (newsector != oldsector) ||
- ((file->cacheoffset==-1) && sectoroffset) ) {
+file_error:
+ RELEASE_FILESTR(READER, file);
+ return rc;
+}
- if ( newsector != oldsector ) {
- if (file->dirty) {
- rc = flush_cache(fd);
- if (rc < 0)
- return rc * 10 - 5;
- }
+/* synchronize changes to a file */
+int fsync(int fildes)
+{
+ DEBUGF("fsync(fd=%d)\n", fildes);
- rc = fat_seek(&(file->fatfile), newsector);
- if ( rc < 0 ) {
- errno = EIO;
- return rc * 10 - 4;
- }
- }
- if ( sectoroffset ) {
- rc = fat_readwrite(&(file->fatfile), 1, file->cache ,false);
- if ( rc < 0 ) {
- errno = EIO;
- return rc * 10 - 6;
- }
- file->cacheoffset = sectoroffset;
- }
- else
- file->cacheoffset = -1;
+ struct filestr_desc * const file = GET_FILESTR(WRITER, fildes);
+ if (!file)
+ FILE_ERROR_RETURN(ERRNO, -1);
+
+ int rc;
+
+ if (!(file->stream.flags & FD_WRITE))
+ {
+ DEBUGF("Descriptor is read-only mode\n", fd);
+ FILE_ERROR(EINVAL, -2);
}
- else
- if ( file->cacheoffset != -1 )
- file->cacheoffset = sectoroffset;
- file->fileoffset = pos;
+ rc = fsync_internal(file);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 3);
- return pos;
+file_error:
+ RELEASE_FILESTR(WRITER, file);
+ return rc;
}
-off_t filesize(int fd)
+/* move the read/write file offset */
+off_t lseek(int fildes, off_t offset, int whence)
{
- struct filedesc* file = &openfiles[fd];
+ DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence);
- if (fd < 0 || fd > MAX_OPEN_FILES-1) {
- errno = EINVAL;
- return -1;
+ struct filestr_desc * const file = GET_FILESTR(READER, fildes);
+ if (!file)
+ FILE_ERROR_RETURN(ERRNO, -1);
+
+ off_t rc = lseek_internal(file, offset, whence);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 2);
+
+file_error:
+ RELEASE_FILESTR(READER, file);
+ return rc;
+}
+
+/* read from a file */
+ssize_t read(int fildes, void *buf, size_t nbyte)
+{
+ struct filestr_desc * const file = GET_FILESTR(READER, fildes);
+ if (!file)
+ FILE_ERROR_RETURN(ERRNO, -1);
+
+ ssize_t rc;
+
+ if (file->stream.flags & FD_WRONLY)
+ {
+ DEBUGF("read(fd=%d,buf=%p,nb=%lu) - "
+ "descriptor is write-only mode\n", fildes, buf, nbyte);
+ FILE_ERROR(EBADF, -2);
}
- if ( !file->busy ) {
- errno = EBADF;
- return -1;
+
+ rc = readwrite(file, buf, nbyte, false);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 3);
+
+file_error:
+ RELEASE_FILESTR(READER, file);
+ return rc;
+}
+
+/* write on a file */
+ssize_t write(int fildes, const void *buf, size_t nbyte)
+{
+ struct filestr_desc * const file = GET_FILESTR(READER, fildes);
+ if (!file)
+ FILE_ERROR_RETURN(ERRNO, -1);
+
+ ssize_t rc;
+
+ if (!(file->stream.flags & FD_WRITE))
+ {
+ DEBUGF("write(fd=%d,buf=%p,nb=%lu) - "
+ "descriptor is read-only mode\n", fildes, buf, nbyte);
+ FILE_ERROR(EBADF, -2);
}
- return file->size;
+ rc = readwrite(file, (void *)buf, nbyte, true);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 3);
+
+file_error:
+ RELEASE_FILESTR(READER, file);
+ return rc;
}
+/* remove a file */
+int remove(const char *path)
+{
+ DEBUGF("remove(path=\"%s\")\n", path);
+
+ file_internal_lock_WRITER();
+ int rc = remove_stream_internal(path, NULL, FF_FILE);
+ file_internal_unlock_WRITER();
+ return rc;
+}
-/* release all file handles on a given volume "by force", to avoid leaks */
-int release_files(int volume)
+/* rename a file */
+int rename(const char *old, const char *new)
{
- struct filedesc* pfile = openfiles;
- int fd;
- int closed = 0;
- for ( fd=0; fd<MAX_OPEN_FILES; fd++, pfile++)
+ DEBUGF("rename(old=\"%s\",new=\"%s\")\n", old, new);
+
+ int rc, open1rc = -1, open2rc = -1;
+ struct filestr_base oldstr, newstr;
+ struct path_component_info oldinfo, newinfo;
+
+ file_internal_lock_WRITER();
+
+ /* open 'old'; it must exist */
+ open1rc = open_stream_internal(old, FF_ANYTYPE, &oldstr, &oldinfo);
+ if (open1rc <= 0)
{
+ DEBUGF("Failed opening old: %d\n", rc);
+ if (open1rc == 0)
+ FILE_ERROR(ENOENT, -1);
+ else
+ FILE_ERROR(ERRNO, open1rc * 10 - 1);
+ }
+
+ /* if 'old' is a directory then 'new' is also required to be one if 'new'
+ is to be overwritten */
+ const bool are_dirs = oldinfo.attr & ATTR_DIRECTORY;
+
+ /* open new (may or may not exist) */
+ unsigned int callflags = FF_FILE;
+ if (are_dirs)
+ {
+ /* if 'old' is found while parsing the new directory components then
+ 'new' contains path prefix that names 'old'; if new and old are in
+ the same directory, this tests positive but that is checked later */
+ callflags = FF_DIR | FF_CHECKPREFIX;
+ newinfo.prefixp = oldstr.infop;
+ }
+
+ open2rc = open_stream_internal(new, callflags, &newstr, &newinfo);
+ if (open2rc < 0)
+ {
+ DEBUGF("Failed opening new file: %d\n", rc);
+ FILE_ERROR(ERRNO, open2rc * 10 - 2);
+ }
+
#ifdef HAVE_MULTIVOLUME
- if (pfile->fatfile.volume == volume)
-#else
- (void)volume;
-#endif
+ if (oldinfo.parentinfo.volume != newinfo.parentinfo.volume)
+ {
+ DEBUGF("Cross-device link\n");
+ FILE_ERROR(EXDEV, -3);
+ }
+#endif /* HAVE_MULTIVOLUME */
+
+ /* if the parent is changing then this is a move, not a simple rename */
+ const bool is_move = !fat_file_is_same(&oldinfo.parentinfo.fatfile,
+ &newinfo.parentinfo.fatfile);
+ /* prefix found and moving? */
+ if (is_move && (newinfo.attr & ATTR_PREFIX))
+ {
+ DEBUGF("New contains prefix that names old\n");
+ FILE_ERROR(EINVAL, -4);
+ }
+
+ const char * const oldname = strmemdupa(oldinfo.name, oldinfo.length);
+ const char * const newname = strmemdupa(newinfo.name, newinfo.length);
+ bool is_overwrite = false;
+
+ if (open2rc > 0)
+ {
+ /* new name exists in parent; check if 'old' is overwriting 'new';
+ if it's the very same file, then it's just a rename */
+ is_overwrite = oldstr.bindp != newstr.bindp;
+
+ if (is_overwrite)
+ {
+ if (are_dirs)
+ {
+ /* the directory to be overwritten must be empty */
+ rc = test_dir_empty_internal(&newstr);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 5);
+ }
+ }
+ else if (!strcmp(newname, oldname)) /* case-only is ok */
+ {
+ DEBUGF("No name change (success)\n");
+ rc = 0;
+ FILE_ERROR(ERRNO, RC);
+ }
+ }
+ else if (!are_dirs && (newinfo.attr & ATTR_DIRECTORY))
+ {
+ /* even if new doesn't exist, canonical path type must match
+ (ie. a directory path such as "/foo/bar/" when old names a file) */
+ DEBUGF("New path is a directory\n");
+ FILE_ERROR(EISDIR, -6);
+ }
+
+ /* first, create the new entry so that there's never a time that the
+ victim's data has no reference in the directory tree, that is, until
+ everything else first succeeds */
+ struct file_base_info old_fileinfo = *oldstr.infop;
+ rc = fat_rename(&newinfo.parentinfo.fatfile, &oldstr.infop->fatfile,
+ newname);
+ if (rc < 0)
+ {
+ DEBUGF("I/O error renaming file: %d\n", rc);
+ FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 7);
+ }
+
+ if (is_overwrite)
+ {
+ /* 'new' would have been assigned its own directory entry and
+ succeeded so at this point it is treated like a remove() call
+ on the victim which preserves data until the last reference is
+ closed */
+ rc = remove_stream_internal(NULL, &newstr, callflags);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 8);
+ }
+
+ fileop_onrename_internal(&oldstr, is_move ? &old_fileinfo : NULL,
+ &newinfo.parentinfo, newname);
+
+file_error:
+ /* for now, there is nothing to fail upon closing the old stream */
+ if (open1rc >= 0)
+ close_stream_internal(&oldstr);
+
+ /* the 'new' stream could fail to close cleanly because it became
+ impossible to remove its data if this was an overwrite operation */
+ if (open2rc >= 0)
+ {
+ int rc2 = close_stream_internal(&newstr);
+ if (rc2 < 0 && rc >= 0)
{
- pfile->busy = false; /* mark as available, no further action */
- closed++;
+ DEBUGF("Success but failed closing new: %d\n", rc2);
+ rc = rc2 * 10 - 9;
}
}
- return closed; /* return how many we did */
+
+ file_internal_unlock_WRITER();
+ return rc;
+}
+
+
+/** Extensions **/
+
+/* get the binary size of a file (in bytes) */
+off_t filesize(int fildes)
+{
+ struct filestr_desc * const file = GET_FILESTR(READER, fildes);
+ if (!file)
+ FILE_ERROR_RETURN(ERRNO, -1);
+
+ off_t rc;
+ file_size_t size = *file->sizep;
+
+ if (size > FILE_SIZE_MAX)
+ FILE_ERROR(EOVERFLOW, -2);
+
+ rc = (off_t)size;
+file_error:
+ RELEASE_FILESTR(READER, file);
+ return rc;
+}
+
+/* test if two file descriptors refer to the same file */
+int fsamefile(int fildes1, int fildes2)
+{
+ struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1);
+ if (!file1)
+ FILE_ERROR_RETURN(ERRNO, -1);
+
+ int rc = -2;
+
+ struct filestr_desc * const file2 = get_filestr(fildes2);
+ if (file2)
+ rc = file1->stream.bindp == file2->stream.bindp ? 1 : 0;
+
+ RELEASE_FILESTR(WRITER, file1);
+ return rc;
+}
+
+/* tell the relationship of path1 to path2 */
+int relate(const char *path1, const char *path2)
+{
+ /* this is basically what rename() does but reduced to the relationship
+ determination */
+ DEBUGF("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2);
+
+ int rc, open1rc = -1, open2rc = -1;
+ struct filestr_base str1, str2;
+ struct path_component_info info1, info2;
+
+ file_internal_lock_WRITER();
+
+ open1rc = open_stream_internal(path1, FF_ANYTYPE, &str1, &info1);
+ if (open1rc <= 0)
+ {
+ DEBUGF("Failed opening path1: %d\n", rc);
+ if (open1rc < 0)
+ FILE_ERROR(ERRNO, open1rc * 10 - 1);
+ else
+ FILE_ERROR(ENOENT, -1);
+ }
+
+ info2.prefixp = str1.infop;
+ open2rc = open_stream_internal(path2, FF_ANYTYPE | FF_CHECKPREFIX,
+ &str2, &info2);
+ if (open2rc < 0)
+ {
+ DEBUGF("Failed opening path2: %d\n", rc);
+ FILE_ERROR(ERRNO, open2rc * 10 - 2);
+ }
+
+ rc = RELATE_DIFFERENT;
+
+ if (open2rc > 0)
+ {
+ if (str1.bindp == str2.bindp)
+ rc = RELATE_SAME;
+ else if (info2.attr & ATTR_PREFIX)
+ rc = RELATE_PREFIX;
+ }
+ else /* open2rc == 0 */
+ {
+ /* path1 existing and path2's final part not can only be a prefix or
+ different */
+ if (info2.attr & ATTR_PREFIX)
+ rc = RELATE_PREFIX;
+ }
+
+file_error:
+ if (open1rc >= 0)
+ close_stream_internal(&str1);
+
+ if (open2rc >= 0)
+ close_stream_internal(&str2);
+
+ file_internal_unlock_WRITER();
+ return rc;
+}
+
+/* test file or directory existence */
+bool file_exists(const char *path)
+{
+ file_internal_lock_WRITER();
+ bool rc = test_stream_exists_internal(path, FF_ANYTYPE) > 0;
+ file_internal_unlock_WRITER();
+ return rc;
}
diff --git a/firmware/common/file_internal.c b/firmware/common/file_internal.c
new file mode 100644
index 0000000000..ebe77f0c9f
--- /dev/null
+++ b/firmware/common/file_internal.c
@@ -0,0 +1,776 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "config.h"
+#include <errno.h>
+#include "system.h"
+#include "debug.h"
+#include "panic.h"
+#include "pathfuncs.h"
+#include "disk_cache.h"
+#include "fileobj_mgr.h"
+#include "dir.h"
+#include "dircache_redirect.h"
+#include "dircache.h"
+#include "string-extra.h"
+#include "rbunicode.h"
+
+/** Internal common filesystem service functions **/
+
+/* for internal functions' scanning use to save quite a bit of stack space -
+ access must be serialized by the writer lock */
+#if defined(CPU_SH) || defined(IAUDIO_M5)
+/* otherwise, out of IRAM */
+struct fat_direntry dir_fatent;
+#else
+struct fat_direntry dir_fatent IBSS_ATTR;
+#endif
+
+struct mrsw_lock file_internal_mrsw SHAREDBSS_ATTR;
+
+
+/** File stream sector caching **/
+
+/* initialize a new cache structure */
+void file_cache_init(struct filestr_cache *cachep)
+{
+ cachep->buffer = NULL;
+ cachep->sector = INVALID_SECNUM;
+ cachep->flags = 0;
+}
+
+/* discard and mark the cache buffer as unused */
+void file_cache_reset(struct filestr_cache *cachep)
+{
+ cachep->sector = INVALID_SECNUM;
+ cachep->flags = 0;
+}
+
+/* allocate resources attached to the cache */
+void file_cache_alloc(struct filestr_cache *cachep)
+{
+ /* if this fails, it is a bug; check for leaks and that the cache has
+ enough buffers for the worst case */
+ if (!cachep->buffer && !(cachep->buffer = dc_get_buffer()))
+ panicf("file_cache_alloc - OOM");
+}
+
+/* free resources attached to the cache */
+void file_cache_free(struct filestr_cache *cachep)
+{
+ if (cachep && cachep->buffer)
+ {
+ dc_release_buffer(cachep->buffer);
+ cachep->buffer = NULL;
+ }
+
+ file_cache_reset(cachep);
+}
+
+
+/** Stream base APIs **/
+
+static inline void filestr_clear(struct filestr_base *stream)
+{
+ stream->flags = 0;
+ stream->bindp = NULL;
+#if 0
+ stream->mtx = NULL;
+#endif
+}
+
+/* actually late-allocate the assigned cache */
+void filestr_alloc_cache(struct filestr_base *stream)
+{
+ file_cache_alloc(stream->cachep);
+}
+
+/* free the stream's cache buffer if it's its own */
+void filestr_free_cache(struct filestr_base *stream)
+{
+ if (stream->cachep == &stream->cache)
+ file_cache_free(stream->cachep);
+}
+
+/* assign a cache to the stream */
+void filestr_assign_cache(struct filestr_base *stream,
+ struct filestr_cache *cachep)
+{
+ if (cachep)
+ {
+ filestr_free_cache(stream);
+ stream->cachep = cachep;
+ }
+ else /* assign own cache */
+ {
+ file_cache_reset(&stream->cache);
+ stream->cachep = &stream->cache;
+ }
+}
+
+/* duplicate a cache into a stream's local cache */
+void filestr_copy_cache(struct filestr_base *stream,
+ struct filestr_cache *cachep)
+{
+ stream->cachep = &stream->cache;
+ stream->cache.sector = cachep->sector;
+ stream->cache.flags = cachep->flags;
+
+ if (cachep->buffer)
+ {
+ file_cache_alloc(&stream->cache);
+ memcpy(stream->cache.buffer, cachep->buffer, DC_CACHE_BUFSIZE);
+ }
+ else
+ {
+ file_cache_free(&stream->cache);
+ }
+}
+
+/* discard cache contents and invalidate it */
+void filestr_discard_cache(struct filestr_base *stream)
+{
+ file_cache_reset(stream->cachep);
+}
+
+/* Initialize the base descriptor */
+void filestr_base_init(struct filestr_base *stream)
+{
+ filestr_clear(stream);
+ file_cache_init(&stream->cache);
+ stream->cachep = &stream->cache;
+}
+
+/* free base descriptor resources */
+void filestr_base_destroy(struct filestr_base *stream)
+{
+ filestr_clear(stream);
+ filestr_free_cache(stream);
+}
+
+
+/** Internal directory service functions **/
+
+/* read the next directory entry and return its FS info */
+int uncached_readdir_internal(struct filestr_base *stream,
+ struct file_base_info *infop,
+ struct fat_direntry *fatent)
+{
+ return fat_readdir(&stream->fatstr, &infop->fatfile.e,
+ filestr_get_cache(stream), fatent);
+}
+
+/* rewind the FS directory to the beginning */
+void uncached_rewinddir_internal(struct file_base_info *infop)
+{
+ fat_rewinddir(&infop->fatfile.e);
+}
+
+/* check if the directory is empty (ie. only "." and/or ".." entries
+ exist at most) */
+int test_dir_empty_internal(struct filestr_base *stream)
+{
+ int rc;
+
+ struct file_base_info info;
+ fat_rewind(&stream->fatstr);
+ rewinddir_internal(&info);
+
+ while ((rc = readdir_internal(stream, &info, &dir_fatent)) > 0)
+ {
+ /* no OEM decoding is recessary for this simple check */
+ if (!is_dotdir_name(dir_fatent.name))
+ {
+ DEBUGF("Directory not empty\n");
+ FILE_ERROR_RETURN(ENOTEMPTY, -1);
+ }
+ }
+
+ if (rc < 0)
+ {
+ DEBUGF("I/O error checking directory: %d\n", rc);
+ FILE_ERROR_RETURN(EIO, rc * 10 - 2);
+ }
+
+ return 0;
+}
+
+/* iso decode the name to UTF-8 */
+void iso_decode_d_name(char *d_name)
+{
+ if (is_dotdir_name(d_name))
+ return;
+
+ char shortname[13];
+ size_t len = strlcpy(shortname, d_name, sizeof (shortname));
+ /* This MUST be the default codepage thus not something that could be
+ loaded on call */
+ iso_decode(shortname, d_name, -1, len + 1);
+}
+
+#ifdef HAVE_DIRCACHE
+/* nullify all the fields of the struct dirent */
+void empty_dirent(struct dirent *entry)
+{
+ entry->d_name[0] = '\0';
+ entry->info.attr = 0;
+ entry->info.size = 0;
+ entry->info.wrtdate = 0;
+ entry->info.wrttime = 0;
+}
+
+/* fill the native dirinfo from the static dir_fatent */
+void fill_dirinfo_native(struct dirinfo_native *dinp)
+{
+ struct fat_direntry *fatent = get_dir_fatent();
+ dinp->attr = fatent->attr;
+ dinp->size = fatent->filesize;
+ dinp->wrtdate = fatent->wrtdate;
+ dinp->wrttime = fatent->wrttime;
+}
+#endif /* HAVE_DIRCACHE */
+
+int uncached_readdir_dirent(struct filestr_base *stream,
+ struct dirscan_info *scanp,
+ struct dirent *entry)
+{
+ struct fat_direntry fatent;
+ int rc = fat_readdir(&stream->fatstr, &scanp->fatscan,
+ filestr_get_cache(stream), &fatent);
+
+ /* FAT driver clears the struct fat_dirent if nothing is returned */
+ strcpy(entry->d_name, fatent.name);
+ entry->info.attr = fatent.attr;
+ entry->info.size = fatent.filesize;
+ entry->info.wrtdate = fatent.wrtdate;
+ entry->info.wrttime = fatent.wrttime;
+
+ return rc;
+}
+
+/* rewind the FS directory pointer */
+void uncached_rewinddir_dirent(struct dirscan_info *scanp)
+{
+ fat_rewinddir(&scanp->fatscan);
+}
+
+
+/** open_stream_internal() helpers and types **/
+
+struct pathwalk
+{
+ const char *path; /* current location in input path */
+ unsigned int callflags; /* callflags parameter */
+ struct path_component_info *compinfo; /* compinfo parameter */
+ file_size_t filesize; /* size of the file */
+};
+
+struct pathwalk_component
+{
+ struct file_base_info info; /* basic file information */
+ const char *name; /* component name location in path */
+ uint16_t length; /* length of name of component */
+ uint16_t attr; /* attributes of this component */
+ struct pathwalk_component *nextp; /* parent if in use else next free */
+};
+
+#define WALK_RC_NOT_FOUND 0 /* successfully not found */
+#define WALK_RC_FOUND 1 /* found and opened */
+#define WALK_RC_FOUND_ROOT 2 /* found and opened sys/volume root */
+#define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */
+
+/* return another struct pathwalk_component from the pool, or NULL if the
+ pool is completely used */
+static void * pathwalk_comp_alloc_(struct pathwalk_component *parentp)
+{
+ /* static pool that goes to a depth of STATIC_COMP_NUM before allocating
+ elements from the stack */
+ static struct pathwalk_component aux_pathwalk[STATIC_PATHCOMP_NUM];
+ struct pathwalk_component *compp = NULL;
+
+ if (!parentp)
+ compp = &aux_pathwalk[0]; /* root */
+ else if (PTR_IN_ARRAY(aux_pathwalk, parentp, STATIC_PATHCOMP_NUM-1))
+ compp = parentp + 1;
+
+ return compp;
+}
+
+/* allocates components from the pool or stack depending upon the depth */
+#define pathwalk_comp_alloc(parentp) \
+ ({ \
+ void *__c = pathwalk_comp_alloc_(parentp); \
+ if (!__builtin_constant_p(parentp) && !__c) \
+ __c = alloca(sizeof (struct pathwalk_component)); \
+ (struct pathwalk_component *)__c; \
+ })
+
+/* fill in the details of the struct path_component_info for caller */
+static int fill_path_compinfo(struct pathwalk *walkp,
+ struct pathwalk_component *compp,
+ int rc)
+{
+ if (rc == -ENOENT)
+ {
+ /* this component wasn't found; see if more of them exist or path
+ has trailing separators; if it does, this component should be
+ interpreted as a directory even if it doesn't exist and it's the
+ final one; also, this has to be the last part or it's an error*/
+ const char *p = GOBBLE_PATH_SEPCH(walkp->path);
+ if (!*p)
+ {
+ if (p > walkp->path)
+ compp->attr |= ATTR_DIRECTORY;
+
+ rc = WALK_RC_NOT_FOUND; /* successfully not found */
+ }
+ }
+
+ if (rc >= 0)
+ {
+ struct path_component_info *compinfo = walkp->compinfo;
+ compinfo->name = compp->name;
+ compinfo->length = compp->length;
+ compinfo->attr = compp->attr;
+ compinfo->filesize = walkp->filesize;
+ compinfo->parentinfo = (compp->nextp ?: compp)->info;
+ }
+
+ return rc;
+}
+
+/* open the final stream itself, if found */
+static int walk_open_info(struct pathwalk *walkp,
+ struct pathwalk_component *compp,
+ int rc,
+ struct filestr_base *stream)
+{
+ /* this may make adjustments to things; do it first */
+ if (walkp->compinfo)
+ rc = fill_path_compinfo(walkp, compp, rc);
+
+ if (rc < 0 || rc == WALK_RC_NOT_FOUND)
+ return rc;
+
+ unsigned int callflags = walkp->callflags;
+ bool isdir = compp->attr & ATTR_DIRECTORY;
+
+ /* type must match what is called for */
+ switch (callflags & FF_TYPEMASK)
+ {
+ case FF_FILE:
+ if (!isdir) break;
+ DEBUGF("File is a directory\n");
+ return -EISDIR;
+ case FF_DIR:
+ if (isdir) break;
+ DEBUGF("File is not a directory\n");
+ return -ENOTDIR;
+ /* FF_ANYTYPE: basically, ignore FF_FILE/FF_DIR */
+ }
+
+ /* FO_DIRECTORY must match type */
+ if (isdir)
+ callflags |= FO_DIRECTORY;
+ else
+ callflags &= ~FO_DIRECTORY;
+
+ fileop_onopen_internal(stream, &compp->info, callflags);
+ return compp->nextp ? WALK_RC_FOUND : WALK_RC_FOUND_ROOT;
+}
+
+/* check the component against the prefix test info */
+static void walk_check_prefix(struct pathwalk *walkp,
+ struct pathwalk_component *compp)
+{
+ if (compp->attr & ATTR_PREFIX)
+ return;
+
+ if (!fat_file_is_same(&compp->info.fatfile,
+ &walkp->compinfo->prefixp->fatfile))
+ return;
+
+ compp->attr |= ATTR_PREFIX;
+}
+
+/* opens the component named by 'comp' in the directory 'parent' */
+static NO_INLINE int open_path_component(struct pathwalk *walkp,
+ struct pathwalk_component *compp,
+ struct filestr_base *stream)
+{
+ int rc;
+
+ /* create a null-terminated copy of the component name */
+ char *compname = strmemdupa(compp->name, compp->length);
+
+ unsigned int callflags = walkp->callflags;
+ struct pathwalk_component *parentp = compp->nextp;
+
+ /* children inherit the prefix coloring from the parent */
+ compp->attr = parentp->attr & ATTR_PREFIX;
+
+ /* most of the next would be abstracted elsewhere if doing other
+ filesystems */
+
+ /* scan parent for name; stream is converted to this parent */
+ file_cache_reset(stream->cachep);
+ stream->infop = &parentp->info;
+ fat_filestr_init(&stream->fatstr, &parentp->info.fatfile);
+ rewinddir_internal(&compp->info);
+
+ while ((rc = readdir_internal(stream, &compp->info, &dir_fatent)) > 0)
+ {
+ if (rc > 1 && !(callflags & FF_NOISO))
+ iso_decode_d_name(dir_fatent.name);
+
+ if (!strcasecmp(compname, dir_fatent.name))
+ break;
+ }
+
+ if (rc == 0)
+ {
+ DEBUGF("File/directory not found\n");
+ return -ENOENT;
+ }
+ else if (rc < 0)
+ {
+ DEBUGF("I/O error reading directory %d\n", rc);
+ return -EIO;
+ }
+
+ rc = fat_open(stream->fatstr.fatfilep, dir_fatent.firstcluster,
+ &compp->info.fatfile);
+ if (rc < 0)
+ {
+ DEBUGF("I/O error opening file/directory %s (%d)\n",
+ compname, rc);
+ return -EIO;
+ }
+
+ walkp->filesize = dir_fatent.filesize;
+ compp->attr |= dir_fatent.attr;
+
+ if (callflags & FF_CHECKPREFIX)
+ walk_check_prefix(walkp, compp);
+
+ return WALK_RC_FOUND;
+}
+
+/* parse a path component, open it and process the next */
+static NO_INLINE int
+walk_path(struct pathwalk *walkp, struct pathwalk_component *compp,
+ struct filestr_base *stream)
+{
+ int rc = WALK_RC_FOUND;
+
+ if (walkp->callflags & FF_CHECKPREFIX)
+ walk_check_prefix(walkp, compp);
+
+ /* alloca is used in a loop, but we reuse any blocks previously allocated
+ if we went up then back down; if the path takes us back to the root, then
+ everything is cleaned automatically */
+ struct pathwalk_component *freep = NULL;
+
+ const char *name;
+ ssize_t len;
+
+ while ((len = parse_path_component(&walkp->path, &name)))
+ {
+ /* whatever is to be a parent must be a directory */
+ if (!(compp->attr & ATTR_DIRECTORY))
+ return -ENOTDIR;
+
+ switch (len)
+ {
+ case 1:
+ case 2:
+ /* check for "." and ".." */
+ if (name[0] == '.')
+ {
+ if (len == 2 && name[1] == '.')
+ {
+ struct pathwalk_component *parentp = compp->nextp;
+ if (!parentp)
+ return WALK_RC_CONT_AT_ROOT;
+
+ compp->nextp = freep;
+ freep = compp;
+ compp = parentp;
+ }
+
+ break;
+ }
+
+ /* fallthrough */
+ default:
+ if (len >= MAX_NAME)
+ return -ENAMETOOLONG;
+
+ struct pathwalk_component *newp = freep;
+ if (!newp)
+ newp = pathwalk_comp_alloc(compp);
+ else
+ freep = freep->nextp;
+
+ newp->nextp = compp;
+ compp = newp;
+
+ compp->name = name;
+ compp->length = len;
+
+ rc = open_path_component(walkp, compp, stream);
+ if (rc < 0)
+ break;
+ }
+ }
+
+ return walk_open_info(walkp, compp, rc, stream);
+}
+
+/* open a stream given a path to the resource */
+int open_stream_internal(const char *path, unsigned int callflags,
+ struct filestr_base *stream,
+ struct path_component_info *compinfo)
+{
+ DEBUGF("%s(path=\"%s\",flg=%X,str=%p,compinfo=%p)\n", path, callflags,
+ stream, compinfo);
+ int rc;
+
+ filestr_base_init(stream);
+
+ if (!path_is_absolute(path))
+ {
+ /* while this supports relative components, there is currently no
+ current working directory concept at this level by which to
+ fully qualify the path (though that would not be excessively
+ difficult to add) */
+ DEBUGF("\"%s\" is not an absolute path\n"
+ "Only absolute paths currently supported.\n", path);
+ FILE_ERROR(path ? ENOENT : EFAULT, -1);
+ }
+
+ /* if !compinfo, then the result of this check is not visible anyway */
+ if (!compinfo)
+ callflags &= ~FF_CHECKPREFIX;
+
+ struct pathwalk walk;
+ walk.path = path;
+ walk.callflags = callflags;
+ walk.compinfo = compinfo;
+ walk.filesize = 0;
+
+ struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL);
+ rootp->nextp = NULL;
+ rootp->attr = ATTR_DIRECTORY;
+
+#ifdef HAVE_MULTIVOLUME
+ int volume = 0, rootrc = WALK_RC_FOUND;
+#endif /* HAVE_MULTIVOLUME */
+
+ while (1)
+ {
+ const char *pathptr = walk.path;
+
+ #ifdef HAVE_MULTIVOLUME
+ /* this seamlessly integrates secondary filesystems into the
+ root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */
+ const char *p;
+ volume = path_strip_volume(pathptr, &p, false);
+ if (!CHECK_VOL(volume))
+ {
+ DEBUGF("No such device or address: %d\n", volume);
+ FILE_ERROR(ENXIO, -2);
+ }
+
+ /* the root of this subpath is the system root? */
+ rootrc = p == pathptr ? WALK_RC_FOUND_ROOT : WALK_RC_FOUND;
+ walk.path = p;
+ #endif /* HAVE_MULTIVOLUME */
+
+ /* set name to start at last leading separator; names of volume
+ specifiers will be returned as "/<fooN>" */
+ rootp->name = GOBBLE_PATH_SEPCH(pathptr) - 1;
+ rootp->length =
+ IF_MV( rootrc == WALK_RC_FOUND ? p - rootp->name : ) 1;
+
+ rc = fat_open_rootdir(IF_MV(volume,) &rootp->info.fatfile);
+ if (rc < 0)
+ {
+ /* not mounted */
+ DEBUGF("No such device or address: %d\n", IF_MV_VOL(volume));
+ rc = -ENXIO;
+ break;
+ }
+
+ get_rootinfo_internal(&rootp->info);
+ rc = walk_path(&walk, rootp, stream);
+ if (rc != WALK_RC_CONT_AT_ROOT)
+ break;
+ }
+
+ switch (rc)
+ {
+ case WALK_RC_FOUND_ROOT:
+ IF_MV( rc = rootrc; )
+ case WALK_RC_NOT_FOUND:
+ case WALK_RC_FOUND:
+ break;
+
+ default: /* utter, abject failure :`( */
+ DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno);
+ filestr_base_destroy(stream);
+ FILE_ERROR(-rc, -2);
+ }
+
+ file_cache_reset(stream->cachep);
+
+file_error:
+ return rc;
+}
+
+/* close the stream referenced by 'stream' */
+int close_stream_internal(struct filestr_base *stream)
+{
+ int rc;
+ unsigned int foflags = fileobj_get_flags(stream);
+
+ if ((foflags & (FO_SINGLE|FO_REMOVED)) == (FO_SINGLE|FO_REMOVED))
+ {
+ /* nothing is referencing it so now remove the file's data */
+ rc = fat_remove(&stream->infop->fatfile, FAT_RM_DATA);
+ if (rc < 0)
+ {
+ DEBUGF("I/O error removing file data: %d\n", rc);
+ FILE_ERROR(EIO, rc * 10 - 1);
+ }
+ }
+
+ rc = 0;
+file_error:
+ /* close no matter what */
+ fileop_onclose_internal(stream);
+ return rc;
+}
+
+/* create a new stream in the parent directory */
+int create_stream_internal(struct file_base_info *parentinfop,
+ const char *basename, size_t length,
+ unsigned int attr, unsigned int callflags,
+ struct filestr_base *stream)
+{
+ /* assumes an attempt was made beforehand to open *stream with
+ open_stream_internal() which returned zero (successfully not found),
+ so does not initialize it here */
+ const char * const name = strmemdupa(basename, length);
+ DEBUGF("Creating \"%s\"\n", name);
+
+ struct file_base_info info;
+ int rc = fat_create_file(&parentinfop->fatfile, name, attr,
+ &info.fatfile, get_dir_fatent_dircache());
+ if (rc < 0)
+ {
+ DEBUGF("Create failed: %d\n", rc);
+ FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 1);
+ }
+
+ /* dir_fatent is implicit arg */
+ fileop_oncreate_internal(stream, &info, callflags, parentinfop, name);
+ rc = 0;
+file_error:
+ return rc;
+}
+
+/* removes files and directories - back-end to remove() and rmdir() */
+int remove_stream_internal(const char *path, struct filestr_base *stream,
+ unsigned int callflags)
+{
+ /* Only FF_* flags should be in callflags */
+ int rc;
+
+ struct filestr_base opened_stream;
+ if (!stream)
+ stream = &opened_stream;
+
+ if (stream == &opened_stream)
+ {
+ /* no stream provided so open local one */
+ rc = open_stream_internal(path, callflags, stream, NULL);
+ if (rc < 0)
+ {
+ DEBUGF("Failed opening path: %d\n", rc);
+ FILE_ERROR(ERRNO, rc * 10 - 1);
+ }
+ }
+ /* else ignore the 'path' argument */
+
+ if (callflags & FF_DIR)
+ {
+ /* directory to be removed must be empty */
+ rc = test_dir_empty_internal(stream);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, rc * 10 - 2);
+ }
+
+ /* save old info since fat_remove() will destroy the dir info */
+ struct file_base_info oldinfo = *stream->infop;
+ rc = fat_remove(&stream->infop->fatfile, FAT_RM_DIRENTRIES);
+ if (rc < 0)
+ {
+ DEBUGF("I/O error removing dir entries: %d\n", rc);
+ FILE_ERROR(EIO, rc * 10 - 3);
+ }
+
+ fileop_onremove_internal(stream, &oldinfo);
+
+ rc = 0;
+file_error:
+ if (stream == &opened_stream)
+ {
+ /* will do removal of data below if this is the only reference */
+ int rc2 = close_stream_internal(stream);
+ if (rc2 < 0 && rc >= 0)
+ {
+ rc = rc2 * 10 - 4;
+ DEBUGF("Success but failed closing stream: %d\n", rc);
+ }
+ }
+
+ return rc;
+}
+
+/* test file/directory existence with constraints */
+int test_stream_exists_internal(const char *path, unsigned int callflags)
+{
+ /* only FF_* flags should be in callflags */
+ struct filestr_base stream;
+ int rc = open_stream_internal(path, callflags, &stream, NULL);
+ if (rc > 0)
+ close_stream_internal(&stream);
+
+ return rc;
+}
+
+/* one-time init at startup */
+void filesystem_init(void)
+{
+ mrsw_init(&file_internal_mrsw);
+ dc_init();
+ fileobj_mgr_init();
+}
diff --git a/firmware/common/filefuncs.c b/firmware/common/filefuncs.c
deleted file mode 100644
index 16f8d88684..0000000000
--- a/firmware/common/filefuncs.c
+++ /dev/null
@@ -1,102 +0,0 @@
-/***************************************************************************
- * __________ __ ___.
- * Open \______ \ ____ ____ | | _\_ |__ _______ ___
- * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
- * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
- * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
- * \/ \/ \/ \/ \/
- * $Id$
- *
- * Copyright (C) 2002 by Björn Stenberg
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- ****************************************************************************/
-#include "config.h"
-#include "dir.h"
-#include "stdlib.h"
-#include "string.h"
-#include "debug.h"
-#include "file.h"
-#include "filefuncs.h"
-#include "string-extra.h"
-
-#ifndef __PCTOOL__
-#ifdef HAVE_MULTIVOLUME
-
-/* returns on which volume this is, and copies the reduced name
- (sortof a preprocessor for volume-decorated pathnames) */
-int strip_volume(const char* name, char* namecopy)
-{
- int volume = 0;
- const char *temp = name;
-
- while (*temp == '/') /* skip all leading slashes */
- ++temp;
-
- if (*temp && !strncmp(temp, VOL_NAMES, VOL_ENUM_POS))
- {
- temp += VOL_ENUM_POS; /* behind special name */
- volume = atoi(temp); /* number is following */
- temp = strchr(temp, '/'); /* search for slash behind */
- if (temp != NULL)
- name = temp; /* use the part behind the volume */
- else
- name = "/"; /* else this must be the root dir */
- }
-
- strlcpy(namecopy, name, MAX_PATH);
-
- return volume;
-}
-#endif /* #ifdef HAVE_MULTIVOLUME */
-
-#endif /* __PCTOOL__ */
-/* Test file existence, using dircache of possible */
-bool file_exists(const char *file)
-{
- int fd;
-
-#ifdef DEBUG
- if (!file || !*file)
- {
- DEBUGF("%s(%p): Invalid parameter!\n", __func__, file);
- return false;
- }
-#endif
-
-#ifdef HAVE_DIRCACHE
- if (dircache_is_enabled())
- return (dircache_get_entry_id(file) >= 0);
-#endif
-
- fd = open(file, O_RDONLY);
- if (fd < 0)
- return false;
- close(fd);
- return true;
-}
-
-bool dir_exists(const char *path)
-{
- DIR* d = opendir(path);
- if (!d)
- return false;
- closedir(d);
- return true;
-}
-
-
-#if (CONFIG_PLATFORM & PLATFORM_NATIVE) || defined(SIMULATOR)
-struct dirinfo dir_get_info(DIR* parent, struct dirent *entry)
-{
- (void)parent;
- return entry->info;
-}
-#endif
diff --git a/firmware/common/fileobj_mgr.c b/firmware/common/fileobj_mgr.c
new file mode 100644
index 0000000000..8e7831d36c
--- /dev/null
+++ b/firmware/common/fileobj_mgr.c
@@ -0,0 +1,396 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "config.h"
+#include "system.h"
+#include "debug.h"
+#include "file.h"
+#include "dir.h"
+#include "disk_cache.h"
+#include "fileobj_mgr.h"
+#include "dircache_redirect.h"
+
+/**
+ * Manages file and directory streams on all volumes
+ *
+ * Intended for internal use by disk, file and directory code
+ */
+
+
+/* there will always be enough of these for all user handles, thus these
+ functions don't return failure codes */
+#define MAX_FILEOBJS (MAX_OPEN_HANDLES + AUX_FILEOBJS)
+
+/* describes the file as an image on the storage medium */
+static struct fileobj_binding
+{
+ struct file_base_binding bind; /* base info list item (first!) */
+ uint16_t flags; /* F(D)(O)_* bits of this file/dir */
+ uint16_t writers; /* number of writer streams */
+ struct filestr_cache cache; /* write mode shared cache */
+ file_size_t size; /* size of this file */
+ struct ll_head list; /* open streams for this file/dir */
+} fobindings[MAX_FILEOBJS];
+static struct mutex stream_mutexes[MAX_FILEOBJS] SHAREDBSS_ATTR;
+static struct ll_head free_bindings;
+static struct ll_head busy_bindings[NUM_VOLUMES];
+
+#define BUSY_BINDINGS(volume) \
+ (&busy_bindings[IF_MV_VOL(volume)])
+
+#define BASEBINDING_LIST(bindp) \
+ (BUSY_BINDINGS(BASEBINDING_VOL(bindp)))
+
+#define FREE_BINDINGS() \
+ (&free_bindings)
+
+#define BINDING_FIRST(type, volume...) \
+ ((struct fileobj_binding *)type##_BINDINGS(volume)->head)
+
+#define BINDING_NEXT(fobp) \
+ ((struct fileobj_binding *)(fobp)->bind.node.next)
+
+#define FOR_EACH_BINDING(volume, fobp) \
+ for (struct fileobj_binding *fobp = BINDING_FIRST(BUSY, volume); \
+ fobp; fobp = BINDING_NEXT(fobp))
+
+#define STREAM_FIRST(fobp) \
+ ((struct filestr_base *)(fobp)->list.head)
+
+#define STREAM_NEXT(s) \
+ ((struct filestr_base *)(s)->node.next)
+
+#define STREAM_THIS(s) \
+ (s)
+
+#define FOR_EACH_STREAM(what, start, s) \
+ for (struct filestr_base *s = STREAM_##what(start); \
+ s; s = STREAM_NEXT(s))
+
+
+/* syncs information for the stream's old and new parent directory if any are
+ currently opened */
+static void fileobj_sync_parent(const struct file_base_info *infop[],
+ int count)
+{
+ FOR_EACH_BINDING(infop[0]->volume, fobp)
+ {
+ if ((fobp->flags & (FO_DIRECTORY|FO_REMOVED)) != FO_DIRECTORY)
+ continue; /* not directory or removed can't be parent of anything */
+
+ struct filestr_base *parentstrp = STREAM_FIRST(fobp);
+ struct fat_file *parentfilep = &parentstrp->infop->fatfile;
+
+ for (int i = 0; i < count; i++)
+ {
+ if (!fat_dir_is_parent(parentfilep, &infop[i]->fatfile))
+ continue;
+
+ /* discard scan/read caches' parent dir info */
+ FOR_EACH_STREAM(THIS, parentstrp, s)
+ filestr_discard_cache(s);
+ }
+ }
+}
+
+/* see if this file has open streams and return that fileobj_binding if so,
+ else grab a new one from the free list; returns true if this stream is
+ the only open one */
+static bool binding_assign(const struct file_base_info *srcinfop,
+ struct fileobj_binding **fobpp)
+{
+ FOR_EACH_BINDING(srcinfop->fatfile.volume, fobp)
+ {
+ if (fobp->flags & FO_REMOVED)
+ continue;
+
+ if (fat_file_is_same(&srcinfop->fatfile, &fobp->bind.info.fatfile))
+ {
+ /* already has open streams */
+ *fobpp = fobp;
+ return false;
+ }
+ }
+
+ /* not found - allocate anew */
+ *fobpp = BINDING_FIRST(FREE);
+ ll_remove_first(FREE_BINDINGS());
+ ll_init(&(*fobpp)->list);
+ return true;
+}
+
+/* mark descriptor as unused and return to the free list */
+static void binding_add_to_free_list(struct fileobj_binding *fobp)
+{
+ fobp->flags = 0;
+ ll_insert_last(FREE_BINDINGS(), &fobp->bind.node);
+}
+
+/** File and directory internal interface **/
+
+void file_binding_insert_last(struct file_base_binding *bindp)
+{
+ ll_insert_last(BASEBINDING_LIST(bindp), &bindp->node);
+}
+
+void file_binding_remove(struct file_base_binding *bindp)
+{
+ ll_remove(BASEBINDING_LIST(bindp), &bindp->node);
+}
+
+#ifdef HAVE_DIRCACHE
+void file_binding_insert_first(struct file_base_binding *bindp)
+{
+ ll_insert_first(BASEBINDING_LIST(bindp), &bindp->node);
+}
+
+void file_binding_remove_next(struct file_base_binding *prevp,
+ struct file_base_binding *bindp)
+{
+ ll_remove_next(BASEBINDING_LIST(bindp), &prevp->node);
+ (void)bindp;
+}
+#endif /* HAVE_DIRCACHE */
+
+/* opens the file object for a new stream and sets up the caches;
+ * the stream must already be opened at the FS driver level and *stream
+ * initialized.
+ *
+ * NOTE: switches stream->infop to the one kept in common for all streams of
+ * the same file, making a copy for only the first stream
+ */
+void fileobj_fileop_open(struct filestr_base *stream,
+ const struct file_base_info *srcinfop,
+ unsigned int callflags)
+{
+ struct fileobj_binding *fobp;
+ bool first = binding_assign(srcinfop, &fobp);
+
+ /* add stream to this file's list */
+ ll_insert_last(&fobp->list, &stream->node);
+
+ /* initiate the new stream into the enclave */
+ stream->flags = FDO_BUSY | (callflags & (FD_WRITE|FD_WRONLY|FD_APPEND));
+ stream->infop = &fobp->bind.info;
+ stream->fatstr.fatfilep = &fobp->bind.info.fatfile;
+ stream->bindp = &fobp->bind;
+ stream->mtx = &stream_mutexes[fobp - fobindings];
+
+ if (first)
+ {
+ /* first stream for file */
+ fobp->bind.info = *srcinfop;
+ fobp->flags = FDO_BUSY | FO_SINGLE |
+ (callflags & (FO_DIRECTORY|FO_TRUNC));
+ fobp->writers = 0;
+ fobp->size = 0;
+
+ if (callflags & FD_WRITE)
+ {
+ /* first one is a writer */
+ fobp->writers = 1;
+ file_cache_init(&fobp->cache);
+ filestr_assign_cache(stream, &fobp->cache);
+ }
+
+ fileobj_bind_file(&fobp->bind);
+ }
+ else
+ {
+ /* additional stream for file */
+ fobp->flags &= ~FO_SINGLE;
+ fobp->flags |= callflags & FO_TRUNC;
+
+ /* once a file/directory, always a file/directory; such a change
+ is a bug */
+ if ((callflags ^ fobp->flags) & FO_DIRECTORY)
+ {
+ DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n",
+ __func__, stream, callflags);
+ }
+
+ if (fobp->writers)
+ {
+ /* already writers present */
+ fobp->writers++;
+ filestr_assign_cache(stream, &fobp->cache);
+ }
+ else if (callflags & FD_WRITE)
+ {
+ /* first writer */
+ fobp->writers = 1;
+ file_cache_init(&fobp->cache);
+ FOR_EACH_STREAM(FIRST, fobp, s)
+ filestr_assign_cache(s, &fobp->cache);
+ }
+ /* else another reader */
+ }
+}
+
+/* close the stream and free associated resources */
+void fileobj_fileop_close(struct filestr_base *stream)
+{
+ switch (stream->flags)
+ {
+ case 0: /* not added to manager */
+ case FV_NONEXIST: /* forced-closed by unmounting */
+ filestr_base_destroy(stream);
+ return;
+ }
+
+ struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp;
+ unsigned int foflags = fobp->flags;
+
+ ll_remove(&fobp->list, &stream->node);
+
+ if ((foflags & FO_SINGLE) || fobp->writers == 0)
+ {
+ if (foflags & FO_SINGLE)
+ {
+ /* last stream for file; close everything */
+ fileobj_unbind_file(&fobp->bind);
+
+ if (fobp->writers)
+ file_cache_free(&fobp->cache);
+
+ binding_add_to_free_list(fobp);
+ }
+ }
+ else if ((stream->flags & FD_WRITE) && --fobp->writers == 0)
+ {
+ /* only readers remain; switch back to stream-local caching */
+ FOR_EACH_STREAM(FIRST, fobp, s)
+ filestr_copy_cache(s, &fobp->cache);
+
+ file_cache_free(&fobp->cache);
+ }
+
+ if (!(foflags & FO_SINGLE) && fobp->list.head == fobp->list.tail)
+ fobp->flags |= FO_SINGLE; /* only one open stream remaining */
+
+ filestr_base_destroy(stream);
+}
+
+/* informs manager that file has been created */
+void fileobj_fileop_create(struct filestr_base *stream,
+ const struct file_base_info *srcinfop,
+ unsigned int callflags)
+{
+ fileobj_fileop_open(stream, srcinfop, callflags);
+ fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1);
+}
+
+/* informs manager that file has been removed */
+void fileobj_fileop_remove(struct filestr_base *stream,
+ const struct file_base_info *oldinfop)
+{
+ ((struct fileobj_binding *)stream->bindp)->flags |= FO_REMOVED;
+ fileobj_sync_parent((const struct file_base_info *[]){ oldinfop }, 1);
+}
+
+/* informs manager that file has been renamed */
+void fileobj_fileop_rename(struct filestr_base *stream,
+ const struct file_base_info *oldinfop)
+{
+ /* if there is old info then this was a move and the old parent has to be
+ informed */
+ int count = oldinfop ? 2 : 1;
+ fileobj_sync_parent(&(const struct file_base_info *[])
+ { oldinfop, stream->infop }[2 - count],
+ count);
+}
+
+/* informs manager than directory entries have been updated */
+void fileobj_fileop_sync(struct filestr_base *stream)
+{
+ fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1);
+}
+
+/* inform manager that file has been truncated */
+void fileobj_fileop_truncate(struct filestr_base *stream)
+{
+ /* let caller update internal info */
+ FOR_EACH_STREAM(FIRST, (struct fileobj_binding *)stream->bindp, s)
+ ftruncate_internal_callback(stream, s);
+}
+
+/* query for the pointer to the size storage for the file object */
+file_size_t * fileobj_get_sizep(const struct filestr_base *stream)
+{
+ if (!stream->bindp)
+ return NULL;
+
+ return &((struct fileobj_binding *)stream->bindp)->size;
+}
+
+/* query manager bitflags for the file object */
+unsigned int fileobj_get_flags(const struct filestr_base *stream)
+{
+ if (!stream->bindp)
+ return 0;
+
+ return ((struct fileobj_binding *)stream->bindp)->flags;
+}
+
+/* change manager bitflags for the file object */
+void fileobj_change_flags(struct filestr_base *stream,
+ unsigned int flags, unsigned int mask)
+{
+ struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp;
+ if (fobp)
+ fobp->flags = (fobp->flags & ~mask) | (flags & mask);
+}
+
+/* mark all open streams on a device as "nonexistant" */
+void fileobj_mgr_unmount(IF_MV_NONVOID(int volume))
+{
+ /* right now, there is nothing else to be freed when marking a descriptor
+ as "nonexistant" but a callback could be added if that changes */
+ FOR_EACH_VOLUME(volume, v)
+ {
+ struct fileobj_binding *fobp;
+ while ((fobp = BINDING_FIRST(BUSY, v)))
+ {
+ struct filestr_base *s;
+ while ((s = STREAM_FIRST(fobp)))
+ {
+ /* keep it "busy" to avoid races; any valid file/directory
+ descriptor returned by an open call should always be
+ closed by whomever opened it (of course!) */
+ fileop_onclose_internal(s);
+ s->flags = FV_NONEXIST;
+ }
+ }
+ }
+}
+
+/* one-time init at startup */
+void fileobj_mgr_init(void)
+{
+ for (unsigned int i = 0; i < NUM_VOLUMES; i++)
+ ll_init(BUSY_BINDINGS(i));
+
+ ll_init(FREE_BINDINGS());
+ for (unsigned int i = 0; i < MAX_FILEOBJS; i++)
+ {
+ mutex_init(&stream_mutexes[i]);
+ binding_add_to_free_list(&fobindings[i]);
+ }
+}
diff --git a/firmware/common/pathfuncs.c b/firmware/common/pathfuncs.c
new file mode 100644
index 0000000000..4410275adb
--- /dev/null
+++ b/firmware/common/pathfuncs.c
@@ -0,0 +1,421 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <string.h>
+#include <ctype.h>
+#include "system.h"
+#include "pathfuncs.h"
+#include "string-extra.h"
+
+#ifdef HAVE_MULTIVOLUME
+#include <stdio.h>
+#include "storage.h"
+
+enum storage_name_dec_indexes
+{
+#if (CONFIG_STORAGE & STORAGE_ATA)
+ STORAGE_DEC_IDX_ATA,
+#endif
+#if (CONFIG_STORAGE & STORAGE_MMC)
+ STORAGE_DEC_IDX_MMC,
+#endif
+#if (CONFIG_STORAGE & STORAGE_SD)
+ STORAGE_DEC_IDX_SD,
+#endif
+#if (CONFIG_STORAGE & STORAGE_NAND)
+ STORAGE_DEC_IDX_NAND,
+#endif
+#if (CONFIG_STORAGE & STORAGE_RAMDISK)
+ STORAGE_DEC_IDX_RAMDISK,
+#endif
+#if (CONFIG_STORAGE & STORAGE_HOSTFS)
+ STORAGE_DEC_IDX_HOSTFS,
+#endif
+ STORAGE_NUM_DEC_IDX,
+};
+
+static const char * const storage_dec_names[STORAGE_NUM_DEC_IDX+1] =
+{
+#if (CONFIG_STORAGE & STORAGE_ATA)
+ [STORAGE_DEC_IDX_ATA] = ATA_VOL_DEC,
+#endif
+#if (CONFIG_STORAGE & STORAGE_MMC)
+ [STORAGE_DEC_IDX_MMC] = MMC_VOL_DEC,
+#endif
+#if (CONFIG_STORAGE & STORAGE_SD)
+ [STORAGE_DEC_IDX_SD] = SD_VOL_DEC,
+#endif
+#if (CONFIG_STORAGE & STORAGE_NAND)
+ [STORAGE_DEC_IDX_NAND] = NAND_VOL_DEC,
+#endif
+#if (CONFIG_STORAGE & STORAGE_RAMDISK)
+ [STORAGE_DEC_IDX_RAMDISK] = RAMDISK_VOL_DEC,
+#endif
+#if (CONFIG_STORAGE & STORAGE_HOSTFS)
+ [STORAGE_DEC_IDX_HOSTFS] = HOSTFS_VOL_DEC,
+#endif
+ [STORAGE_NUM_DEC_IDX] = DEFAULT_VOL_DEC,
+};
+
+static const unsigned char storage_dec_indexes[STORAGE_NUM_TYPES+1] =
+{
+ [0 ... STORAGE_NUM_TYPES] = STORAGE_NUM_DEC_IDX,
+#if (CONFIG_STORAGE & STORAGE_ATA)
+ [STORAGE_ATA_NUM] = STORAGE_DEC_IDX_ATA,
+#endif
+#if (CONFIG_STORAGE & STORAGE_MMC)
+ [STORAGE_MMC_NUM] = STORAGE_DEC_IDX_MMC,
+#endif
+#if (CONFIG_STORAGE & STORAGE_SD)
+ [STORAGE_SD_NUM] = STORAGE_DEC_IDX_SD,
+#endif
+#if (CONFIG_STORAGE & STORAGE_NAND)
+ [STORAGE_NAND_NUM] = STORAGE_DEC_IDX_NAND,
+#endif
+#if (CONFIG_STORAGE & STORAGE_RAMDISK)
+ [STORAGE_RAMDISK_NUM] = STORAGE_DEC_IDX_RAMDISK,
+#endif
+#if (CONFIG_STORAGE & STORAGE_HOSTFS)
+ [STORAGE_HOSTFS_NUM] = STORAGE_DEC_IDX_HOSTFS,
+#endif
+};
+
+/* Returns on which volume this is and sets *nameptr to the portion of the
+ * path after the volume specifier, which could be the null if the path is
+ * just a volume root. If *nameptr > name, then a volume specifier was
+ * found. If 'greedy' is 'true', then it all separators after the volume
+ * specifier are consumed, if one was found.
+ */
+int path_strip_volume(const char *name, const char **nameptr, bool greedy)
+{
+ int volume = 0;
+ const char *t = name;
+ int c, v = 0;
+
+ /* format: "/<xxx##>/foo/bar"
+ * the "xxx" is pure decoration; only an unbroken trailing string of
+ * digits within the brackets is parsed as the volume number and of
+ * those, only the last ones VOL_MUM_MAX allows.
+ */
+ c = *(t = GOBBLE_PATH_SEPCH(t)); /* skip all leading slashes */
+ if (c != VOL_START_TOK) /* missing start token? no volume */
+ goto volume0;
+
+ do
+ {
+ switch (c)
+ {
+ case '0' ... '9': /* digit; parse volume number */
+ v = (v * 10 + c - '0') % VOL_NUM_MAX;
+ break;
+ case '\0':
+ case PATH_SEPCH: /* no closing bracket; no volume */
+ goto volume0;
+ default: /* something else; reset volume */
+ v = 0;
+ }
+ }
+ while ((c = *++t) != VOL_END_TOK); /* found end token? */
+
+ if (!(c = *++t)) /* no more path and no '/' is ok */
+ ;
+ else if (c != PATH_SEPCH) /* more path and no separator after end */
+ goto volume0;
+ else if (greedy)
+ t = GOBBLE_PATH_SEPCH(++t); /* strip remaining separators */
+
+ /* if 'greedy' is true and **nameptr == '\0' then it's only a volume
+ root whether or not it has trailing separators */
+
+ volume = v;
+ name = t;
+volume0:
+ if (nameptr)
+ *nameptr = name;
+ return volume;
+}
+
+/* Returns the volume specifier decorated with the storage type name.
+ * Assumes the supplied buffer size is at least {VOL_MAX_LEN}+1.
+ */
+int get_volume_name(int volume, char *buffer)
+{
+ if (volume < 0)
+ {
+ *buffer = '\0';
+ return 0;
+ }
+
+ volume %= VOL_NUM_MAX; /* as path parser would have it */
+
+ int type = storage_driver_type(volume_drive(volume));
+ if (type < 0 || type > STORAGE_NUM_TYPES)
+ type = STORAGE_NUM_TYPES;
+
+ const char *voldec = storage_dec_names[storage_dec_indexes[type]];
+ return snprintf(buffer, VOL_MAX_LEN + 1, "%c%s%d%c",
+ VOL_START_TOK, voldec, volume, VOL_END_TOK);
+}
+#endif /* HAVE_MULTIVOLUME */
+
+/* Just like path_strip_volume() but strips a leading drive specifier and
+ * returns the drive number (A=0, B=1, etc.). -1 means no drive was found.
+ * If 'greedy' is 'true', all separators after the volume are consumed.
+ */
+int path_strip_drive(const char *name, const char **nameptr, bool greedy)
+{
+ int c = toupper(*name);
+
+ if (c >= 'A' && c <= 'Z' && name[1] == PATH_DRVSEPCH)
+ {
+ name = &name[2];
+ if (greedy)
+ name = GOBBLE_PATH_SEPCH(name);
+
+ *nameptr = name;
+ return c - 'A';
+ }
+
+ *nameptr = name;
+ return -1;
+}
+
+/* Strips leading and trailing whitespace from a path
+ * " a/b \txyz" *nameptr->a, len=3: "a/b"
+ */
+size_t path_trim_whitespace(const char *name, const char **nameptr)
+{
+ int c;
+ while ((c = *name) <= ' ' && c)
+ ++name;
+
+ const char *first = name;
+ const char *last = name;
+
+ while (1)
+ {
+ if (c < ' ')
+ {
+ *nameptr = first;
+ return last - first;
+ }
+
+ while ((c = *++name) > ' ');
+ last = name;
+ while (c == ' ') c = *++name;
+ }
+}
+
+/* Strips directory components from the path
+ * "" *nameptr->NUL, len=0: ""
+ * "/" *nameptr->/, len=1: "/"
+ * "//" *nameptr->2nd /, len=1: "/"
+ * "/a" *nameptr->a, len=1: "a"
+ * "/a/bc" *nameptr->b, len=2: "bc"
+ * "d" *nameptr->d, len=1: "d"
+ * "ef/gh" *nameptr->g, len=2: "gh"
+ */
+size_t path_basename(const char *name, const char **nameptr)
+{
+ const char *p = name;
+ const char *q = p;
+ const char *r = q;
+
+ while (*(p = GOBBLE_PATH_SEPCH(p)))
+ {
+ q = p;
+ p = GOBBLE_PATH_COMP(++p);
+ r = p;
+ }
+
+ if (r == name && p > name)
+ q = p, r = q--; /* root - return last slash */
+ /* else path is an empty string */
+
+ *nameptr = q;
+ return r - q;
+}
+
+/* Strips the trailing component from the path
+ * "" *nameptr->NUL, len=0: ""
+ * "/" *nameptr->/, len=1: "/"
+ * "//" *nameptr->2nd /, len=1: "/"
+ * "/a" *nameptr->/, len=1: "/"
+ * "/a/bc" *nameptr->/, len=2: "/a"
+ * "d" *nameptr->d, len=0: ""
+ * "ef/gh" *nameptr->e, len=2: "ef"
+ */
+size_t path_dirname(const char *name, const char **nameptr)
+{
+ const char *p = GOBBLE_PATH_SEPCH(name);
+ const char *q = name;
+ const char *r = p;
+
+ while (*(p = GOBBLE_PATH_COMP(p)))
+ {
+ const char *s = p;
+
+ if (!*(p = GOBBLE_PATH_SEPCH(p)))
+ break;
+
+ q = s;
+ }
+
+ if (q == name && r > name)
+ name = r, q = name--; /* root - return last slash */
+
+ *nameptr = name;
+ return q - name;
+}
+
+/* Removes trailing separators from a path
+ * "" *nameptr->NUL, len=0: ""
+ * "/" *nameptr->/, len=1: "/"
+ * "//" *nameptr->2nd /, len=1: "/"
+ * "/a/" *nameptr->/, len=2: "/a"
+ * "//b/" *nameptr->1st /, len=3: "//b"
+ * "/c/" *nameptr->/, len=2: "/c"
+ */
+size_t path_strip_trailing_separators(const char *name, const char **nameptr)
+{
+ const char *p;
+ size_t len = path_basename(name, &p);
+
+ if (len == 1 && *p == '/' && p > name)
+ {
+ *nameptr = p;
+ name = p - 1; /* root with multiple separators */
+ }
+ else
+ {
+ *nameptr = name;
+ p += len; /* length to end of basename */
+ }
+
+ return p - name;
+}
+
+/* Transforms "wrong" separators into the correct ones
+ * "c:\windows\system32" -> "c:/windows/system32"
+ *
+ * 'path' and 'dstpath' may either be the same buffer or non-overlapping
+ */
+void path_correct_separators(char *dstpath, const char *path)
+{
+ char *dstp = dstpath;
+ const char *p = path;
+
+ while (1)
+ {
+ const char *next = strchr(p, PATH_BADSEPCH);
+ if (!next)
+ break;
+
+ size_t size = next - p;
+
+ if (dstpath != path)
+ memcpy(dstp, p, size); /* not in-place */
+
+ dstp += size;
+ *dstp++ = PATH_SEPCH;
+ p = next + 1;
+ }
+
+ if (dstpath != path)
+ strcpy(dstp, p);
+}
+
+/* Appends one path to another, adding separators between components if needed.
+ * Return value and behavior is otherwise as strlcpy so that truncation may be
+ * detected.
+ *
+ * For basepath and component:
+ * PA_SEP_HARD adds a separator even if the base path is empty
+ * PA_SEP_SOFT adds a separator only if the base path is not empty
+ */
+size_t path_append(char *buf, const char *basepath,
+ const char *component, size_t bufsize)
+{
+ const char *base = basepath && basepath[0] ? basepath : buf;
+ if (!base)
+ return bufsize; /* won't work to get lengths from buf */
+
+ if (!buf)
+ bufsize = 0;
+
+ if (path_is_absolute(component))
+ {
+ /* 'component' is absolute; replace all */
+ basepath = component;
+ component = "";
+ }
+
+ /* if basepath is not null or empty, buffer contents are replaced,
+ otherwise buf contains the base path */
+ size_t len = base == buf ? strlen(buf) : strlcpy(buf, basepath, bufsize);
+
+ bool separate = false;
+
+ if (!basepath || !component)
+ separate = !len || base[len-1] != PATH_SEPCH;
+ else if (component[0])
+ separate = len && base[len-1] != PATH_SEPCH;
+
+ /* caller might lie about size of buf yet use buf as the base */
+ if (base == buf && bufsize && len >= bufsize)
+ buf[bufsize - 1] = '\0';
+
+ buf += len;
+ bufsize -= MIN(len, bufsize);
+
+ if (separate && (len++, bufsize > 0) && --bufsize > 0)
+ *buf++ = PATH_SEPCH;
+
+ return len + strlcpy(buf, component ?: "", bufsize);
+}
+
+/* Returns the location and length of the next path component, consuming the
+ * input in the process.
+ *
+ * "/a/bc/d" breaks into:
+ * start: *namep->1st /
+ * call 1: *namep->a, *pathp->2nd / len=1: "a"
+ * call 2: *namep->b, *pathp->3rd / len=2: "bc"
+ * call 3: *namep->d, *pathp->NUL, len=1: "d"
+ * call 4: *namep->NUL, *pathp->NUL, len=0: ""
+ *
+ * Returns: 0 if the input has been consumed
+ * The length of the component otherwise
+ */
+ssize_t parse_path_component(const char **pathp, const char **namep)
+{
+ /* a component starts at a non-separator and continues until the next
+ separator or null */
+ const char *p = GOBBLE_PATH_SEPCH(*pathp);
+ const char *name = p;
+
+ if (*p)
+ p = GOBBLE_PATH_COMP(++p);
+
+ *pathp = p;
+ *namep = name;
+
+ return p - name;
+}
diff --git a/firmware/common/rbpaths.c b/firmware/common/rbpaths.c
deleted file mode 100644
index 69543bc3a7..0000000000
--- a/firmware/common/rbpaths.c
+++ /dev/null
@@ -1,432 +0,0 @@
-/***************************************************************************
- * __________ __ ___.
- * Open \______ \ ____ ____ | | _\_ |__ _______ ___
- * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
- * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
- * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
- * \/ \/ \/ \/ \/
- * $Id$
- *
- * Copyright (C) 2010 Thomas Martitz
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- ****************************************************************************/
-
-
-#include <stdio.h> /* snprintf */
-#include <stdlib.h>
-#include <stdarg.h>
-#include <sys/stat.h>
-#include <time.h>
-#include <unistd.h>
-#include "config.h"
-#include "rbpaths.h"
-#include "crc32.h"
-#include "file.h" /* MAX_PATH */
-#include "logf.h"
-#include "gcc_extensions.h"
-#include "string-extra.h"
-#include "filefuncs.h"
-
-/* In this file we need the actual OS library functions, not the shadowed
- * wrapper used within Rockbox' application code (except SDL adds
- * another layer) */
-#undef open
-#undef creat
-#undef remove
-#undef rename
-#undef opendir
-#undef closedir
-#undef readdir
-#undef mkdir
-#undef rmdir
-#undef dirent
-#undef DIR
-#undef readlink
-
-#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
-static const char rbhome[] = "/sdcard";
-#elif (CONFIG_PLATFORM & (PLATFORM_SDL|PLATFORM_MAEMO|PLATFORM_PANDORA)) && !defined(__PCTOOL__)
-const char *rbhome;
-#else
-/* YPR0, YPR1 */
-static const char rbhome[] = HOME_DIR;
-#endif
-
-#if !(defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1)) && !defined(__PCTOOL__)
-/* Special dirs are user-accessible (and user-writable) dirs which take priority
- * over the ones where Rockbox is installed to. Classic example would be
- * $HOME/.config/rockbox.org vs /usr/share/rockbox */
-#define HAVE_SPECIAL_DIRS
-#endif
-
-/* flags for get_user_file_path() */
-/* whether you need write access to that file/dir, especially true
- * for runtime generated files (config.cfg) */
-#define NEED_WRITE (1<<0)
-/* file or directory? */
-#define IS_FILE (1<<1)
-
-#ifdef HAVE_MULTIDRIVE
-static uint32_t rbhome_hash;
-/* A special link is created under e.g. HOME_DIR/<microSD1>, e.g. to access
- * external storage in a convinient location, much similar to the mount
- * point on our native targets. Here they are treated as symlink (one which
- * doesn't actually exist in the filesystem and therefore we have to override
- * readlink() */
-static const char *handle_special_links(const char* link, unsigned flags,
- char *buf, const size_t bufsize)
-{
- (void) flags;
- char vol_string[VOL_ENUM_POS + 8];
- int len = sprintf(vol_string, VOL_NAMES, 1);
-
- /* link might be passed with or without HOME_DIR expanded. To handle
- * both perform substring matching (VOL_NAMES is unique enough) */
- const char *begin = strstr(link, vol_string);
- if (begin)
- {
- /* begin now points to the start of vol_string within link,
- * we want to copy the remainder of the paths, prefixed by
- * the actual mount point (the remainder might be "") */
- snprintf(buf, bufsize, MULTIDRIVE_DIR"%s", begin + len);
- return buf;
- }
-
- return link;
-}
-#endif
-
-void paths_init(void)
-{
-#ifdef HAVE_SPECIAL_DIRS
- /* make sure $HOME/.config/rockbox.org exists, it's needed for config.cfg */
-#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
- mkdir("/sdcard/rockbox", 0777);
- mkdir("/sdcard/rockbox/rocks.data", 0777);
-#else
- char config_dir[MAX_PATH];
-
- const char *home = getenv("RBROOT");
- if (!home)
- {
- home = getenv("HOME");
- }
- if (!home)
- {
- logf("HOME environment var not set. Can't write config");
- return;
- }
-
- rbhome = home;
- snprintf(config_dir, sizeof(config_dir), "%s/.config", home);
- mkdir(config_dir, 0777);
- snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org", home);
- mkdir(config_dir, 0777);
- /* Plugin data directory */
- snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org/rocks.data", home);
- mkdir(config_dir, 0777);
-#endif
-#endif /* HAVE_SPECIAL_DIRS */
-
-#ifdef HAVE_MULTIDRIVE
- rbhome_hash = crc_32((const void *) rbhome, strlen(rbhome), 0xffffffff);
-#endif /* HAVE_MULTIDRIVE */
-}
-
-#ifdef HAVE_SPECIAL_DIRS
-static bool try_path(const char* filename, unsigned flags)
-{
- if (flags & IS_FILE)
- {
- if (file_exists(filename))
- return true;
- }
- else
- {
- if (dir_exists(filename))
- return true;
- }
- return false;
-}
-
-static const char* _get_user_file_path(const char *path,
- unsigned flags,
- char* buf,
- const size_t bufsize)
-{
- const char *ret = path;
- const char *pos = path;
- /* replace ROCKBOX_DIR in path with $HOME/.config/rockbox.org */
- pos += ROCKBOX_DIR_LEN;
- if (*pos == '/') pos += 1;
-
-#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
- if (snprintf(buf, bufsize, "/sdcard/rockbox/%s", pos)
-#else
- if (snprintf(buf, bufsize, "%s/.config/rockbox.org/%s", rbhome, pos)
-#endif
- >= (int)bufsize)
- return NULL;
-
- /* always return the replacement buffer (pointing to $HOME) if
- * write access is needed */
- if (flags & NEED_WRITE)
- ret = buf;
- else if (try_path(buf, flags))
- ret = buf;
-
- if (ret != buf) /* not found in $HOME, try ROCKBOX_BASE_DIR, !NEED_WRITE only */
- {
- if (snprintf(buf, bufsize, ROCKBOX_SHARE_PATH "/%s", pos) >= (int)bufsize)
- return NULL;
-
- if (try_path(buf, flags))
- ret = buf;
- }
-
- return ret;
-}
-
-#endif
-
-static const char* handle_special_dirs(const char* dir, unsigned flags,
- char *buf, const size_t bufsize)
-{
- (void) flags; (void) buf; (void) bufsize;
-#ifdef HAVE_SPECIAL_DIRS
- if (!strncmp(HOME_DIR, dir, HOME_DIR_LEN))
- {
- const char *p = dir + HOME_DIR_LEN;
- while (*p == '/') p++;
- snprintf(buf, bufsize, "%s/%s", rbhome, p);
- dir = buf;
- }
- else if (!strncmp(ROCKBOX_DIR, dir, ROCKBOX_DIR_LEN))
- dir = _get_user_file_path(dir, flags, buf, bufsize);
-#endif
-#ifdef HAVE_MULTIDRIVE
- dir = handle_special_links(dir, flags, buf, bufsize);
-#endif
- return dir;
-}
-
-int app_open(const char *name, int o, ...)
-{
- char realpath[MAX_PATH];
- va_list ap;
- int fd;
- int flags = IS_FILE;
- if (o & (O_CREAT|O_RDWR|O_TRUNC|O_WRONLY))
- flags |= NEED_WRITE;
-
- name = handle_special_dirs(name, flags, realpath, sizeof(realpath));
-
- va_start(ap, o);
- fd = open(name, o, va_arg(ap, unsigned int));
- va_end(ap);
-
- return fd;
-}
-
-int app_creat(const char* name, mode_t mode)
-{
- return app_open(name, O_CREAT|O_WRONLY|O_TRUNC, mode);
-}
-
-int app_remove(const char *name)
-{
- char realpath[MAX_PATH];
- const char *fname = handle_special_dirs(name, NEED_WRITE, realpath, sizeof(realpath));
-
- return remove(fname);
-}
-
-int app_rename(const char *old, const char *new)
-{
- char realpath_old[MAX_PATH], realpath_new[MAX_PATH];
- const char *final_old, *final_new;
-
- final_old = handle_special_dirs(old, NEED_WRITE, realpath_old, sizeof(realpath_old));
- final_new = handle_special_dirs(new, NEED_WRITE, realpath_new, sizeof(realpath_new));
-
- return rename(final_old, final_new);
-}
-
-/* need to wrap around DIR* because we need to save the parent's
- * directory path in order to determine dirinfo, required to implement
- * get_dir_info() */
-struct __dir {
- DIR *dir;
-#ifdef HAVE_MULTIDRIVE
- int volumes_returned;
- /* A crc of rbhome is used to speed op the common case where
- * readdir()/get_dir_info() is called on non-rbhome paths, because
- * each call needs to check against rbhome for the virtual
- * mount point of the external storage */
- uint32_t path_hash;
-#endif
- char path[];
-};
-
-struct dirinfo dir_get_info(DIR* _parent, struct dirent *dir)
-{
- struct __dir *parent = (struct __dir*)_parent;
- struct stat s;
- struct tm *tm = NULL;
- struct dirinfo ret;
- char path[MAX_PATH];
-
- memset(&ret, 0, sizeof(ret));
-
-#ifdef HAVE_MULTIDRIVE
- char vol_string[VOL_ENUM_POS + 8];
- sprintf(vol_string, VOL_NAMES, 1);
- if (UNLIKELY(rbhome_hash == parent->path_hash) &&
- /* compare path anyway because of possible hash collision */
- !strcmp(vol_string, dir->d_name))
- {
- ret.attribute = ATTR_LINK;
- strcpy(path, MULTIDRIVE_DIR);
- }
- else
-#endif
- snprintf(path, sizeof(path), "%s/%s", parent->path, dir->d_name);
-
-
- if (!lstat(path, &s))
- {
- int err = 0;
- if (S_ISLNK(s.st_mode))
- {
- ret.attribute |= ATTR_LINK;
- err = stat(path, &s);
- }
- if (!err)
- {
- if (S_ISDIR(s.st_mode))
- ret.attribute |= ATTR_DIRECTORY;
-
- ret.size = s.st_size;
- if ((tm = localtime(&(s.st_mtime))))
- {
- ret.wrtdate = ((tm->tm_year - 80) << 9) |
- ((tm->tm_mon + 1) << 5) |
- tm->tm_mday;
- ret.wrttime = (tm->tm_hour << 11) |
- (tm->tm_min << 5) |
- (tm->tm_sec >> 1);
- }
- }
- }
-
- return ret;
-}
-
-DIR* app_opendir(const char *_name)
-{
- size_t name_len;
- char realpath[MAX_PATH];
- const char *name = handle_special_dirs(_name, 0, realpath, sizeof(realpath));
- name_len = strlen(name);
- char *buf = malloc(sizeof(struct __dir) + name_len+1);
- if (!buf)
- return NULL;
-
- struct __dir *this = (struct __dir*)buf;
- /* carefully remove any trailing slash from the input, so that
- * hash/path matching in readdir() works properly */
- while (name[name_len-1] == '/' && name_len > 1)
- name_len -= 1;
- /* strcpy cannot be used because of trailing slashes */
- memcpy(this->path, name, name_len);
- this->path[name_len] = 0;
- this->dir = opendir(this->path);
-
- if (!this->dir)
- {
- free(buf);
- return NULL;
- }
-#ifdef HAVE_MULTIDRIVE
- this->volumes_returned = 0;
- this->path_hash = crc_32((const void *)this->path, name_len, 0xffffffff);
-#endif
-
- return (DIR*)this;
-}
-
-int app_closedir(DIR *dir)
-{
- struct __dir *this = (struct __dir*)dir;
- int ret = closedir(this->dir);
- free(this);
- return ret;
-}
-
-
-struct dirent* app_readdir(DIR* dir)
-{
- struct __dir *d = (struct __dir*)dir;
-#ifdef HAVE_MULTIDRIVE
- /* this is not MT-safe but OK according to man readdir */
- static struct dirent voldir;
- if (UNLIKELY(rbhome_hash == d->path_hash)
- && d->volumes_returned < (NUM_VOLUMES-1)
- && volume_present(d->volumes_returned+1)
- /* compare path anyway because of possible hash collision */
- && !strcmp(d->path, rbhome))
- {
- d->volumes_returned += 1;
- sprintf(voldir.d_name, VOL_NAMES, d->volumes_returned);
- return &voldir;
- }
-#endif
- return readdir(d->dir);
-}
-
-
-int app_mkdir(const char* name)
-{
- char realpath[MAX_PATH];
- const char *fname = handle_special_dirs(name, NEED_WRITE, realpath, sizeof(realpath));
- return mkdir(fname, 0777);
-}
-
-
-int app_rmdir(const char* name)
-{
- char realpath[MAX_PATH];
- const char *fname = handle_special_dirs(name, NEED_WRITE, realpath, sizeof(realpath));
- return rmdir(fname);
-}
-
-
-/* On MD we create a virtual symlink for the external drive,
- * for this we need to override readlink(). */
-ssize_t app_readlink(const char *path, char *buf, size_t bufsiz)
-{
- char _buf[MAX_PATH];
- (void) path; (void) buf; (void) bufsiz;
- path = handle_special_dirs(path, 0, _buf, sizeof(_buf));
-#ifdef HAVE_MULTIDRIVE
- /* if path == _buf then we can be sure handle_special_dir() did something
- * and path is not an ordinary directory */
- if (path == _buf && !strncmp(path, MULTIDRIVE_DIR, sizeof(MULTIDRIVE_DIR)-1))
- {
- /* copying NUL is not required as per readlink specification */
- ssize_t len = strlen(path);
- memcpy(buf, path, len);
- return len;
- }
-#endif
- /* does not append NUL !! */
- return readlink(path, buf, bufsiz);
-}
diff --git a/firmware/common/unicode.c b/firmware/common/unicode.c
index 3ff1814c4b..954ad47e1d 100644
--- a/firmware/common/unicode.c
+++ b/firmware/common/unicode.c
@@ -28,161 +28,227 @@
#include <stdio.h>
#include "config.h"
+#include "system.h"
+#include "thread.h"
#include "file.h"
#include "debug.h"
#include "rbunicode.h"
#include "rbpaths.h"
+#include "pathfuncs.h"
+#include "core_alloc.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
+#ifndef O_NOISODECODE
+#define O_NOISODECODE 0
+#endif
-static int default_codepage = 0;
-static int loaded_cp_table = 0;
-
-#ifdef HAVE_LCD_BITMAP
+#define getle16(p) (p[0] | (p[1] >> 8))
+#define getbe16(p) ((p[1] << 8) | p[0])
-#define MAX_CP_TABLE_SIZE 32768
-#define NUM_TABLES 5
+#if !defined (__PCTOOL__) && (CONFIG_PLATFORM & PLATFORM_NATIVE)
+/* Because file scanning uses the default CP table when matching entries,
+ on-demand loading is not feasible; we also must use the filesystem lock */
+#include "file_internal.h"
+#else /* APPLICATION */
+#ifdef __PCTOOL__
+#define yield()
+#endif
+#define open_noiso_internal open
+#endif /* !APPLICATION */
+
+#if 0 /* not needed just now (will probably end up a spinlock) */
+#include "mutex.h"
+static struct mutex cp_mutex SHAREDBSS_ATTR;
+#define cp_lock_init() mutex_init(&cp_mutex)
+#define cp_lock_enter() mutex_lock(&cp_mutex)
+#define cp_lock_leave() mutex_unlock(&cp_mutex)
+#else
+#define cp_lock_init() do {} while (0)
+#define cp_lock_enter() asm volatile ("")
+#define cp_lock_leave() asm volatile ("")
+#endif
-static const char * const filename[NUM_TABLES] =
+enum cp_tid
{
- CODEPAGE_DIR"/iso.cp",
- CODEPAGE_DIR"/932.cp", /* SJIS */
- CODEPAGE_DIR"/936.cp", /* GB2312 */
- CODEPAGE_DIR"/949.cp", /* KSX1001 */
- CODEPAGE_DIR"/950.cp" /* BIG5 */
+ CP_TID_NONE = -1,
+ CP_TID_ISO,
+ CP_TID_932,
+ CP_TID_936,
+ CP_TID_949,
+ CP_TID_950,
};
-static const char cp_2_table[NUM_CODEPAGES] =
+struct cp_info
{
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 0
+ int8_t tid;
+ const char *filename;
+ const char *name;
};
-static const char * const name_codepages[NUM_CODEPAGES+1] =
-{
- "ISO-8859-1",
- "ISO-8859-7",
- "ISO-8859-8",
- "CP1251",
- "ISO-8859-11",
- "CP1256",
- "ISO-8859-9",
- "ISO-8859-2",
- "CP1250",
- "CP1252",
- "SJIS",
- "GB-2312",
- "KSX-1001",
- "BIG5",
- "UTF-8",
- "unknown"
-};
+#ifdef HAVE_LCD_BITMAP
-#if defined(APPLICATION) && defined(__linux__)
-static const char * const name_codepages_linux[NUM_CODEPAGES+1] =
-{
- /* "ISO-8859-1" */ "iso8859-1",
- /* "ISO-8859-7" */ "iso8859-7",
- /* "ISO-8859-8" */ "iso8859-8",
- /* "CP1251" */ "cp1251",
- /* "ISO-8859-11"*/ "iso8859-11",
- /* "CP1256" */ "cp1256",
- /* "ISO-8859-9" */ "iso8859-9",
- /* "ISO-8859-2" */ "iso8859-2",
- /* "CP1250" */ "cp1250",
- /* "CP1252" */ "iso8859-15", /* closest, linux doesnt have a codepage named cp1252 */
- /* "SJIS" */ "cp932",
- /* "GB-2312" */ "cp936",
- /* "KSX-1001" */ "cp949",
- /* "BIG5" */ "cp950",
- /* "UTF-8" */ "utf8",
- /* "unknown" */ "cp437"
-};
+#define MAX_CP_TABLE_SIZE 32768
-const char *get_current_codepage_name_linux(void)
+#define CPF_ISO "iso.cp"
+#define CPF_932 "932.cp" /* SJIS */
+#define CPF_936 "936.cp" /* GB2312 */
+#define CPF_949 "949.cp" /* KSX1001 */
+#define CPF_950 "950.cp" /* BIG5 */
+
+static const struct cp_info cp_info[NUM_CODEPAGES+1] =
{
- if (default_codepage < 0 || default_codepage >= NUM_CODEPAGES)
- return name_codepages_linux[NUM_CODEPAGES];
- return name_codepages_linux[default_codepage];
-}
-#endif
+ [0 ... NUM_CODEPAGES] = { CP_TID_NONE, NULL , "unknown" },
+ [ISO_8859_1] = { CP_TID_NONE, NULL , "ISO-8859-1" },
+ [ISO_8859_7] = { CP_TID_ISO , CPF_ISO, "ISO-8859-7" },
+ [ISO_8859_8] = { CP_TID_ISO , CPF_ISO, "ISO-8859-8" },
+ [WIN_1251] = { CP_TID_ISO , CPF_ISO, "CP1251" },
+ [ISO_8859_11] = { CP_TID_ISO , CPF_ISO, "ISO-8859-11" },
+ [WIN_1256] = { CP_TID_ISO , CPF_ISO, "CP1256" },
+ [ISO_8859_9] = { CP_TID_ISO , CPF_ISO, "ISO-8859-9" },
+ [ISO_8859_2] = { CP_TID_ISO , CPF_ISO, "ISO-8859-2" },
+ [WIN_1250] = { CP_TID_ISO , CPF_ISO, "CP1250" },
+ [WIN_1252] = { CP_TID_ISO , CPF_ISO, "CP1252" },
+ [SJIS] = { CP_TID_932 , CPF_932, "SJIS" },
+ [GB_2312] = { CP_TID_936 , CPF_936, "GB-2312" },
+ [KSX_1001] = { CP_TID_949 , CPF_949, "KSX-1001" },
+ [BIG_5] = { CP_TID_950 , CPF_950, "BIG5" },
+ [UTF_8] = { CP_TID_NONE, NULL , "UTF-8" },
+};
#else /* !HAVE_LCD_BITMAP, reduced support */
#define MAX_CP_TABLE_SIZE 768
-#define NUM_TABLES 1
-static const char * const filename[NUM_TABLES] = {
- CODEPAGE_DIR"/isomini.cp"
-};
+#define CPF_ISOMINI "isomini.cp"
-static const char cp_2_table[NUM_CODEPAGES] =
+static const struct cp_info cp_info[NUM_CODEPAGES+1] =
{
- 0, 1, 1, 1, 1, 1, 1, 0
+ [0 ... NUM_CODEPAGES] = { CP_TID_NONE, NULL , "unknown" },
+ [ISO_8859_1] = { CP_TID_NONE, NULL , "ISO-8859-1" },
+ [ISO_8859_7] = { CP_TID_ISO , CPF_ISOMINI, "ISO-8859-7" },
+ [WIN_1251] = { CP_TID_ISO , CPF_ISOMINI, "CP1251" },
+ [ISO_8859_9] = { CP_TID_ISO , CPF_ISOMINI, "ISO-8859-9" },
+ [ISO_8859_2] = { CP_TID_ISO , CPF_ISOMINI, "ISO-8859-2" },
+ [WIN_1250] = { CP_TID_ISO , CPF_ISOMINI, "CP1250" },
+ [WIN_1252] = { CP_TID_ISO , CPF_ISOMINI, "CP1252" },
+ [UTF_8] = { CP_TID_ISO , NULL , "UTF-8" },
};
-static const char * const name_codepages[NUM_CODEPAGES+1] =
+#endif /* HAVE_LCD_BITMAP */
+
+static int default_cp = INIT_CODEPAGE;
+static int default_cp_tid = CP_TID_NONE;
+static int default_cp_handle = 0;
+static int volatile default_cp_table_ref = 0;
+
+static int loaded_cp_tid = CP_TID_NONE;
+static int volatile cp_table_ref = 0;
+#define CP_LOADING BIT_N(sizeof(int)*8-1) /* guard against multi loaders */
+
+/* non-default codepage table buffer (cannot be bufalloced! playback itself
+ may be making the load request) */
+static unsigned short codepage_table[MAX_CP_TABLE_SIZE+1];
+
+#if defined(APPLICATION) && defined(__linux__)
+static const char * const name_codepages_linux[NUM_CODEPAGES+1] =
{
- "ISO-8859-1",
- "ISO-8859-7",
- "CP1251",
- "ISO-8859-9",
- "ISO-8859-2",
- "CP1250",
- "CP1252",
- "UTF-8",
- "unknown"
+ [0 ... NUM_CODEPAGES] = "unknown",
+ [ISO_8859_1] = "iso8859-1",
+ [ISO_8859_7] = "iso8859-7",
+ [ISO_8859_8] = "iso8859-8",
+ [WIN_1251] = "cp1251",
+ [ISO_8859_11] = "iso8859-11",
+ [WIN_1256] = "cp1256",
+ [ISO_8859_9] = "iso8859-9",
+ [ISO_8859_2] = "iso8859-2",
+ [WIN_1250] = "cp1250",
+ /* iso8859-15 is closest, linux doesnt have a codepage named cp1252 */
+ [WIN_1252] = "iso8859-15",
+ [SJIS] = "cp932",
+ [GB_2312] = "cp936",
+ [KSX_1001] = "cp949",
+ [BIG_5] = "cp950",
+ [UTF_8] = "utf8",
};
-#endif
-
-static unsigned short codepage_table[MAX_CP_TABLE_SIZE];
+const char *get_current_codepage_name_linux(void)
+{
+ int cp = default_cp;
+ if (cp < 0 || cp>= NUM_CODEPAGES)
+ cp = NUM_CODEPAGES;
+ return name_codepages_linux[cp];
+}
+#endif /* defined(APPLICATION) && defined(__linux__) */
static const unsigned char utf8comp[6] =
{
0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC
};
-/* Load codepage file into memory */
-static int load_cp_table(int cp)
+static inline void cptable_tohw16(uint16_t *buf, unsigned int count)
{
- int i = 0;
- int table = cp_2_table[cp];
- int file, tablesize;
- unsigned char tmp[2];
+#ifdef ROCKBOX_BIG_ENDIAN
+ for (unsigned int i = 0; i < count; i++)
+ buf[i] = letoh16(buf[i]);
+#endif
+ (void)buf; (void)count;
+}
- if (table == 0 || table == loaded_cp_table)
- return 1;
+static int move_callback(int handle, void *current, void *new)
+{
+ /* we don't keep a pointer but we have to stop it if this applies to a
+ buffer not yet swapped-in since it will likely be in use in an I/O
+ call */
+ return (handle != default_cp_handle || default_cp_table_ref != 0) ?
+ BUFLIB_CB_CANNOT_MOVE : BUFLIB_CB_OK;
+ (void)current; (void)new;
+}
- file = open(filename[table-1], O_RDONLY|O_BINARY);
+static int alloc_and_load_cp_table(int cp, void *buf)
+{
+ static struct buflib_callbacks ops =
+ { .move_callback = move_callback };
- if (file < 0) {
- DEBUGF("Can't open codepage file: %s.cp\n", filename[table-1]);
+ /* alloc and read only if there is an associated file */
+ const char *filename = cp_info[cp].filename;
+ if (!filename)
return 0;
+
+ char path[MAX_PATH];
+ if (path_append(path, CODEPAGE_DIR, filename, sizeof (path))
+ >= sizeof (path)) {
+ return -1;
}
- tablesize = filesize(file) / 2;
+ /* must be opened without a chance of reentering from FS code */
+ int fd = open_noiso_internal(path, O_RDONLY);
+ if (fd < 0)
+ return -1;
- if (tablesize > MAX_CP_TABLE_SIZE) {
- DEBUGF("Invalid codepage file: %s.cp\n", filename[table-1]);
- close(file);
- return 0;
- }
+ off_t size = filesize(fd);
- while (i < tablesize) {
- if (!read(file, tmp, 2)) {
- DEBUGF("Can't read from codepage file: %s.cp\n",
- filename[table-1]);
- loaded_cp_table = 0;
- return 0;
+ if (size > 0 && size <= MAX_CP_TABLE_SIZE*2 &&
+ !(size % (off_t)sizeof (uint16_t))) {
+
+ /* if the buffer is provided, use that but don't alloc */
+ int handle = buf ? 0 : core_alloc_ex(filename, size, &ops);
+ if (handle > 0)
+ buf = core_get_data(handle);
+
+ if (buf && read(fd, buf, size) == size) {
+ close(fd);
+ cptable_tohw16(buf, size / sizeof (uint16_t));
+ return handle;
}
- codepage_table[i++] = (tmp[1] << 8) | tmp[0];
+
+ if (handle > 0)
+ core_free(handle);
}
- loaded_cp_table = table;
- close(file);
- return 1;
+ close(fd);
+ return -1;
}
/* Encode a UCS value as UTF-8 and return a pointer after this UTF-8 char. */
@@ -205,47 +271,96 @@ unsigned char* utf8encode(unsigned long ucs, unsigned char *utf8)
unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8,
int cp, int count)
{
- unsigned short ucs, tmp;
+ uint16_t *table = NULL;
+
+ cp_lock_enter();
+
+ if (cp < 0 || cp >= NUM_CODEPAGES)
+ cp = default_cp;
- if (cp == -1) /* use default codepage */
- cp = default_codepage;
+ int tid = cp_info[cp].tid;
- if (!load_cp_table(cp)) cp = 0;
+ while (1) {
+ if (tid == default_cp_tid) {
+ /* use default table */
+ if (default_cp_handle > 0) {
+ table = core_get_data(default_cp_handle);
+ default_cp_table_ref++;
+ }
+
+ break;
+ }
+
+ bool load = false;
+
+ if (tid == loaded_cp_tid) {
+ /* use loaded table */
+ if (!(cp_table_ref & CP_LOADING)) {
+ if (tid != CP_TID_NONE) {
+ table = codepage_table;
+ cp_table_ref++;
+ }
+
+ break;
+ }
+ } else if (cp_table_ref == 0) {
+ load = true;
+ cp_table_ref |= CP_LOADING;
+ }
+
+ /* alloc and load must be done outside the lock */
+ cp_lock_leave();
+
+ if (!load) {
+ yield();
+ } else if (alloc_and_load_cp_table(cp, codepage_table) < 0) {
+ cp = INIT_CODEPAGE; /* table may be clobbered now */
+ tid = cp_info[cp].tid;
+ }
+
+ cp_lock_enter();
+
+ if (load) {
+ loaded_cp_tid = tid;
+ cp_table_ref &= ~CP_LOADING;
+ }
+ }
+
+ cp_lock_leave();
while (count--) {
+ unsigned short ucs, tmp;
+
if (*iso < 128 || cp == UTF_8) /* Already UTF-8 */
*utf8++ = *iso++;
else {
-
- /* cp tells us which codepage to convert from */
- switch (cp) {
- case ISO_8859_7: /* Greek */
- case WIN_1252: /* Western European */
- case WIN_1251: /* Cyrillic */
- case ISO_8859_9: /* Turkish */
- case ISO_8859_2: /* Latin Extended */
- case WIN_1250: /* Central European */
-#ifdef HAVE_LCD_BITMAP
- case ISO_8859_8: /* Hebrew */
- case ISO_8859_11: /* Thai */
- case WIN_1256: /* Arabic */
-#endif
+ /* tid tells us which table to use and how */
+ switch (tid) {
+ case CP_TID_ISO: /* Greek */
+ /* Hebrew */
+ /* Cyrillic */
+ /* Thai */
+ /* Arabic */
+ /* Turkish */
+ /* Latin Extended */
+ /* Central European */
+ /* Western European */
tmp = ((cp-1)*128) + (*iso++ - 128);
- ucs = codepage_table[tmp];
+ ucs = table[tmp];
break;
#ifdef HAVE_LCD_BITMAP
- case SJIS: /* Japanese */
+ case CP_TID_932: /* Japanese */
if (*iso > 0xA0 && *iso < 0xE0) {
tmp = *iso++ | (0xA100 - 0x8000);
- ucs = codepage_table[tmp];
+ ucs = table[tmp];
break;
}
- case GB_2312: /* Simplified Chinese */
- case KSX_1001: /* Korean */
- case BIG_5: /* Traditional Chinese */
+ case CP_TID_936: /* Simplified Chinese */
+ case CP_TID_949: /* Korean */
+ case CP_TID_950: /* Traditional Chinese */
if (count < 1 || !iso[1]) {
ucs = *iso++;
break;
@@ -256,7 +371,7 @@ unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8,
tmp = *iso++ << 8;
tmp |= *iso++;
tmp -= 0x8000;
- ucs = codepage_table[tmp];
+ ucs = table[tmp];
count--;
break;
#endif /* HAVE_LCD_BITMAP */
@@ -271,6 +386,17 @@ unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8,
utf8 = utf8encode(ucs, utf8);
}
}
+
+ if (table) {
+ cp_lock_enter();
+ if (table == codepage_table) {
+ cp_table_ref--;
+ } else {
+ default_cp_table_ref--;
+ }
+ cp_lock_leave();
+ }
+
return utf8;
}
@@ -288,7 +414,7 @@ unsigned char* utf16LEdecode(const unsigned char *utf16, unsigned char *utf8,
utf16 += 4;
count -= 2;
} else {
- ucs = (utf16[0] | (utf16[1] << 8));
+ ucs = getle16(utf16);
utf16 += 2;
count -= 1;
}
@@ -310,7 +436,7 @@ unsigned char* utf16BEdecode(const unsigned char *utf16, unsigned char *utf8,
utf16 += 4;
count -= 2;
} else {
- ucs = (utf16[0] << 8) | utf16[1];
+ ucs = getbe16(utf16);
utf16 += 2;
count -= 1;
}
@@ -400,8 +526,50 @@ const unsigned char* utf8decode(const unsigned char *utf8, unsigned short *ucs)
void set_codepage(int cp)
{
- default_codepage = cp;
- return;
+ if (cp < 0 || cp >= NUM_CODEPAGES)
+ cp = NUM_CODEPAGES;
+
+ /* load first then swap if load is successful, else just leave it; if
+ handle is 0 then we just free the current one; this won't happen often
+ thus we don't worry about reusing it and consequently avoid possible
+ clobbering of the existing one */
+
+ int handle = -1;
+ int tid = cp_info[cp].tid;
+
+ while (1) {
+ cp_lock_enter();
+
+ if (default_cp_tid == tid)
+ break;
+
+ if (handle >= 0 && default_cp_table_ref == 0) {
+ int hold = default_cp_handle;
+ default_cp_handle = handle;
+ handle = hold;
+ default_cp_tid = tid;
+ break;
+ }
+
+ /* alloc and load must be done outside the lock */
+ cp_lock_leave();
+
+ if (handle < 0 && (handle = alloc_and_load_cp_table(cp, NULL)) < 0)
+ return; /* OOM; change nothing */
+
+ yield();
+ }
+
+ default_cp = cp;
+ cp_lock_leave();
+
+ if (handle > 0)
+ core_free(handle);
+}
+
+int get_codepage(void)
+{
+ return default_cp;
}
/* seek to a given char in a utf8 string and
@@ -418,9 +586,16 @@ int utf8seek(const unsigned char* utf8, int offset)
return pos;
}
-const char* get_codepage_name(int cp)
+const char * get_codepage_name(int cp)
{
- if (cp < 0 || cp>= NUM_CODEPAGES)
- return name_codepages[NUM_CODEPAGES];
- return name_codepages[cp];
+ if (cp < 0 || cp >= NUM_CODEPAGES)
+ cp = NUM_CODEPAGES;
+ return cp_info[cp].name;
}
+
+#if 0 /* not needed just now */
+void unicode_init(void)
+{
+ cp_lock_init();
+}
+#endif
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
diff --git a/firmware/export/config.h b/firmware/export/config.h
index 5e4178cd4c..2a1cdb3416 100644
--- a/firmware/export/config.h
+++ b/firmware/export/config.h
@@ -27,12 +27,21 @@
/* symbolic names for multiple choice configurations: */
/* CONFIG_STORAGE (note these are combineable bit-flags) */
-#define STORAGE_ATA 0x01
-#define STORAGE_MMC 0x02
-#define STORAGE_SD 0x04
-#define STORAGE_NAND 0x08
-#define STORAGE_RAMDISK 0x10
-#define STORAGE_HOSTFS 0x20 /* meant for APPLICATION targets (implicit for SIMULATOR) */
+#define STORAGE_ATA_NUM 0
+#define STORAGE_MMC_NUM 1
+#define STORAGE_SD_NUM 2
+#define STORAGE_NAND_NUM 3
+#define STORAGE_RAMDISK_NUM 4
+#define STORAGE_HOSTFS_NUM 5
+#define STORAGE_NUM_TYPES 6
+
+#define STORAGE_ATA (1 << STORAGE_ATA_NUM)
+#define STORAGE_MMC (1 << STORAGE_MMC_NUM)
+#define STORAGE_SD (1 << STORAGE_SD_NUM)
+#define STORAGE_NAND (1 << STORAGE_NAND_NUM)
+#define STORAGE_RAMDISK (1 << STORAGE_RAMDISK_NUM)
+ /* meant for APPLICATION targets (implicit for SIMULATOR) */
+#define STORAGE_HOSTFS (1 << STORAGE_HOSTFS_NUM)
/* CONFIG_TUNER (note these are combineable bit-flags) */
#define S1A0903X01 0x01 /* Samsung */
@@ -573,6 +582,8 @@ Lyre prototype 1 */
#ifdef __PCTOOL__
#undef CONFIG_CPU
#define CONFIG_CPU 0
+#undef HAVE_MULTIVOLUME
+#undef HAVE_MULTIDRIVE
#endif
#ifdef APPLICATION
@@ -831,9 +842,11 @@ Lyre prototype 1 */
* plenty of RAM. Both features can be enabled independently. */
#if (MEMORYSIZE >= 8) && !defined(BOOTLOADER) && !defined(__PCTOOL__) \
&& !defined(APPLICATION)
+#ifndef SIMULATOR
#define HAVE_DIRCACHE
+#endif
#ifdef HAVE_TAGCACHE
-#define HAVE_TC_RAMCACHE
+//#define HAVE_TC_RAMCACHE
#endif
#endif
@@ -1178,7 +1191,7 @@ Lyre prototype 1 */
/* This attribute can be used to enable to detection of plugin file handles leaks.
* When enabled, the plugin core will monitor open/close/creat and when the plugin exits
* will display an error message if the plugin leaked some file handles */
-#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
+#if (CONFIG_PLATFORM & PLATFORM_NATIVE) || defined (SIMULATOR)
#define HAVE_PLUGIN_CHECK_OPEN_CLOSE
#endif
diff --git a/firmware/export/config/gigabeats.h b/firmware/export/config/gigabeats.h
index 11fc02be3d..6657134f9d 100644
--- a/firmware/export/config/gigabeats.h
+++ b/firmware/export/config/gigabeats.h
@@ -16,6 +16,9 @@
/* define this if you use an ATA controller */
#define CONFIG_STORAGE STORAGE_ATA
+/* For the Gigabeat S, we mount the second partition */
+#define CONFIG_DEFAULT_PARTNUM 1
+
/*define this if the ATA controller and method of USB access support LBA48 */
#define HAVE_LBA48
diff --git a/firmware/export/disk.h b/firmware/export/disk.h
index 8d6b41b5bd..c66028fe45 100644
--- a/firmware/export/disk.h
+++ b/firmware/export/disk.h
@@ -24,7 +24,8 @@
#include "config.h"
#include "mv.h" /* for volume definitions */
-struct partinfo {
+struct partinfo
+{
unsigned long start; /* first sector (LBA) */
unsigned long size; /* number of sectors */
unsigned char type;
@@ -35,11 +36,9 @@ struct partinfo {
#define PARTITION_TYPE_FAT16 0x06
#define PARTITION_TYPE_OS2_HIDDEN_C_DRIVE 0x84
-/* returns a pointer to an array of 8 partinfo structs */
-struct partinfo* disk_init(IF_MD_NONVOID(int drive));
-struct partinfo* disk_partinfo(int partition);
+bool disk_init(IF_MD_NONVOID(int drive));
+bool disk_partinfo(int partition, struct partinfo *info);
-void disk_init_subsystem(void) INIT_ATTR; /* Initialises mutexes */
int disk_mount_all(void); /* returns the # of successful mounts */
int disk_mount(int drive);
int disk_unmount_all(void);
@@ -50,4 +49,6 @@ int disk_unmount(int drive);
int disk_get_sector_multiplier(IF_MD_NONVOID(int drive));
#endif
-#endif
+bool disk_present(IF_MD_NONVOID(int drive));
+
+#endif /* _DISK_H_ */
diff --git a/firmware/export/fat.h b/firmware/export/fat.h
index a0d52acc35..3aa1e254dc 100644
--- a/firmware/export/fat.h
+++ b/firmware/export/fat.h
@@ -18,123 +18,168 @@
* KIND, either express or implied.
*
****************************************************************************/
-
#ifndef FAT_H
#define FAT_H
#include <stdbool.h>
-#include "mv.h" /* for volume definitions */
+#include <sys/types.h>
+#include <time.h>
#include "config.h"
#include "system.h"
+#include "mv.h" /* for volume definitions */
+
+/********
+ **** DO NOT use these functions directly unless otherwise noted. Required
+ **** synchronization is done by higher-level interfaces to minimize locking
+ **** overhead.
+ ****
+ **** Volume, drive, string, etc. parameters should also be checked by
+ **** callers for gross violations-- NULL strings, out-of-bounds values,
+ **** etc.
+ ****/
+
+/****************************************************************************
+ ** Values that can be overridden by a target in config-[target].h
+ **/
+
+/* if your ATA implementation can do better, go right ahead and increase this
+ * value */
+#ifndef FAT_MAX_TRANSFER_SIZE
+#define FAT_MAX_TRANSFER_SIZE 256
+#endif
-/* This value can be overwritten by a target in config-[target].h, but
- that behaviour is still experimental */
+/* still experimental? */
+/* increasing this will increase the total memory used by the cache; the
+ cache, as noted in disk_cache.h, has other minimum requirements that may
+ prevent reducing its number of entries in order to compensate */
#ifndef SECTOR_SIZE
#define SECTOR_SIZE 512
#endif
+/**
+ ****************************************************************************/
+
+#define INVALID_SECNUM (0xfffffffeul) /* sequential, not FAT */
+#define FAT_MAX_FILE_SIZE (0xfffffffful) /* 2^32-1 bytes */
+#define MAX_DIRENTRIES 65536
+#define MAX_DIRECTORY_SIZE (MAX_DIRENTRIES*32) /* 2MB max size */
+
+/* these aren't I/O error conditions, so define specially; failure rc's
+ * shouldn't return the last digit as "0", therefore this is unambiguous */
+#define FAT_RC_ENOSPC (-10)
+#define FAT_SEEK_EOF (-20)
+
/* Number of bytes reserved for a file name (including the trailing \0).
- Since names are stored in the entry as UTF-8, we won't be able to
+ Since names are stored in the entry as UTF-8, we won't be ble to
store all names allowed by FAT. In FAT, a name can have max 255
characters (not bytes!). Since the UTF-8 encoding of a char may take
up to 4 bytes, there will be names that we won't be able to store
completely. For such names, the short DOS name is used. */
-#define FAT_FILENAME_BYTES 256
-
+#define FAT_DIRENTRY_NAME_MAX 255
struct fat_direntry
{
- unsigned char name[FAT_FILENAME_BYTES]; /* UTF-8 encoded name plus \0 */
- unsigned short attr; /* Attributes */
- unsigned char crttimetenth; /* Millisecond creation
- time stamp (0-199) */
- unsigned short crttime; /* Creation time */
- unsigned short crtdate; /* Creation date */
- unsigned short lstaccdate; /* Last access date */
- unsigned short wrttime; /* Last write time */
- unsigned short wrtdate; /* Last write date */
- unsigned long filesize; /* File size in bytes */
- long firstcluster; /* fstclusterhi<<16 + fstcluslo */
+ union {
+ uint8_t name[255+1+4]; /* UTF-8 name plus \0 plus parse slop */
+ uint16_t ucssegs[5+20][13]; /* UTF-16 segment buffer - layout saves... */
+ }; /* ...130 bytes (important if stacked) */
+ uint16_t ucsterm; /* allow one NULL-term after ucssegs */
+ uint8_t shortname[13]; /* DOS filename (OEM charset) */
+ uint8_t attr; /* file attributes */
+ uint8_t crttimetenth; /* millisecond creation time stamp (0-199) */
+ uint16_t crttime; /* creation time */
+ uint16_t crtdate; /* creation date */
+ uint16_t lstaccdate; /* last access date */
+ uint16_t wrttime; /* last write time */
+ uint16_t wrtdate; /* last write date */
+ uint32_t filesize; /* file size in bytes */
+ int32_t firstcluster; /* first FAT cluster of file, 0 if empty */
};
-#define FAT_ATTR_READ_ONLY 0x01
-#define FAT_ATTR_HIDDEN 0x02
-#define FAT_ATTR_SYSTEM 0x04
-#define FAT_ATTR_VOLUME_ID 0x08
-#define FAT_ATTR_DIRECTORY 0x10
-#define FAT_ATTR_ARCHIVE 0x20
-#define FAT_ATTR_VOLUME 0x40 /* this is a volume, not a real directory */
+/* cursor structure used for scanning directories; holds the last-returned
+ entry information */
+struct fat_dirscan_info
+{
+ unsigned int entry; /* short dir entry index in parent */
+ unsigned int entries; /* number of dir entries used */
+};
+
+#define FAT_RW_VAL (0u - 1)
+/* basic FAT file information about where to find a file and who houses it */
struct fat_file
{
- long firstcluster; /* first cluster in file */
- long lastcluster; /* cluster of last access */
- long lastsector; /* sector of last access */
- long clusternum; /* current clusternum */
- long sectornum; /* sector number in this cluster */
- unsigned int direntry; /* short dir entry index from start of dir */
- unsigned int direntries; /* number of dir entries used by this file */
- long dircluster; /* first cluster of dir */
- bool eof;
#ifdef HAVE_MULTIVOLUME
- int volume; /* file resides on which volume */
+ int volume; /* file resides on which volume (first!) */
#endif
+ long firstcluster; /* first cluster in file */
+ long dircluster; /* first cluster of parent directory */
+ struct fat_dirscan_info e; /* entry information */
};
-struct fat_dir
+/* this stores what was last accessed when read or writing a file's data */
+struct fat_filestr
{
- unsigned char sectorcache[SECTOR_SIZE] CACHEALIGN_ATTR;
- unsigned int entry;
- unsigned int entrycount;
- long sector;
- struct fat_file file;
- /* There are 2-bytes per characters. We don't want to bother too much, as LFN entries are
- * at much 255 characters longs, that's at most 20 LFN entries. Each entry hold at most
- * 13 characters, that a total of 260 characters. So we keep a buffer of that size.
- * Keep coherent with fat.c code. */
- unsigned char longname[260 * 2];
-} CACHEALIGN_ATTR;
-
-#ifdef HAVE_HOTSWAP
-extern void fat_lock(void);
-extern void fat_unlock(void);
-#endif
+ struct fat_file *fatfilep; /* common file information */
+ long lastcluster; /* cluster of last access */
+ unsigned long lastsector; /* sector of last access */
+ long clusternum; /* cluster number of last access */
+ unsigned long sectornum; /* sector number within current cluster */
+ bool eof; /* end-of-file reached */
+};
+
+/** File entity functions **/
+int fat_create_file(struct fat_file *parent, const char *name,
+ uint8_t attr, struct fat_file *file,
+ struct fat_direntry *fatent);
+bool fat_dir_is_parent(const struct fat_file *dir, const struct fat_file *file);
+bool fat_file_is_same(const struct fat_file *file1, const struct fat_file *file2);
+int fat_fstat(struct fat_file *file, struct fat_direntry *entry);
+int fat_open(const struct fat_file *parent, long startcluster,
+ struct fat_file *file);
+int fat_open_rootdir(IF_MV(int volume,) struct fat_file *dir);
+enum fat_remove_op /* what should fat_remove(), remove? */
+{
+ FAT_RM_DIRENTRIES = 0x1, /* remove only directory entries */
+ FAT_RM_DATA = 0x2, /* remove only file data */
+ FAT_RM_ALL = 0x3, /* remove all of above */
+};
+int fat_remove(struct fat_file *file, enum fat_remove_op what);
+int fat_rename(struct fat_file *parent, struct fat_file *file,
+ const unsigned char *newname);
-extern void fat_init(void);
-extern int fat_get_bytes_per_sector(IF_MV_NONVOID(int volume));
-extern int fat_mount(IF_MV(int volume,) IF_MD(int drive,) long startsector);
-extern int fat_unmount(int volume, bool flush);
-extern void fat_size(IF_MV(int volume,) /* public for info */
- unsigned long* size,
- unsigned long* free);
-extern void fat_recalc_free(IF_MV_NONVOID(int volume)); /* public for debug info screen */
-extern int fat_create_dir(const char* name,
- struct fat_dir* newdir,
- struct fat_dir* dir);
-extern int fat_open(IF_MV(int volume,)
- long cluster,
- struct fat_file* ent,
- const struct fat_dir* dir);
-extern int fat_create_file(const char* name,
- struct fat_file* ent,
- struct fat_dir* dir);
-extern long fat_readwrite(struct fat_file *ent, long sectorcount,
- void* buf, bool write );
-extern int fat_closewrite(struct fat_file *ent, long size, int attr);
-extern int fat_seek(struct fat_file *ent, unsigned long sector );
-extern int fat_remove(struct fat_file *ent);
-extern int fat_truncate(const struct fat_file *ent);
-extern int fat_rename(struct fat_file* file,
- struct fat_dir* dir,
- const unsigned char* newname,
- long size, int attr);
-
-extern int fat_opendir(IF_MV(int volume,)
- struct fat_dir *ent, unsigned long startcluster,
- const struct fat_dir *parent_dir);
-extern int fat_getnext(struct fat_dir *ent, struct fat_direntry *entry);
-extern unsigned int fat_get_cluster_size(IF_MV_NONVOID(int volume)); /* public for debug info screen */
-extern bool fat_ismounted(int volume);
-extern void* fat_get_sector_buffer(void);
-extern void fat_release_sector_buffer(void);
+/** File stream functions **/
+int fat_closewrite(struct fat_filestr *filestr, uint32_t size,
+ struct fat_direntry *fatentp);
+void fat_filestr_init(struct fat_filestr *filestr, struct fat_file *file);
+unsigned long fat_query_sectornum(const struct fat_filestr *filestr);
+long fat_readwrite(struct fat_filestr *filestr, unsigned long sectorcount,
+ void *buf, bool write);
+void fat_rewind(struct fat_filestr *filestr);
+int fat_seek(struct fat_filestr *filestr, unsigned long sector);
+int fat_truncate(const struct fat_filestr *filestr);
-#endif
+/** Directory stream functions **/
+struct filestr_cache;
+int fat_readdir(struct fat_filestr *dirstr, struct fat_dirscan_info *scan,
+ struct filestr_cache *cachep, struct fat_direntry *entry);
+void fat_rewinddir(struct fat_dirscan_info *scan);
+
+/** Mounting and unmounting functions **/
+bool fat_ismounted(IF_MV_NONVOID(int volume));
+int fat_mount(IF_MV(int volume,) IF_MD(int drive,) unsigned long startsector);
+int fat_unmount(IF_MV_NONVOID(int volume));
+
+/** Debug screen stuff **/
+#ifdef MAX_LOG_SECTOR_SIZE
+int fat_get_bytes_per_sector(IF_MV_NONVOID(int volume));
+#endif /* MAX_LOG_SECTOR_SIZE */
+unsigned int fat_get_cluster_size(IF_MV_NONVOID(int volume));
+void fat_recalc_free(IF_MV_NONVOID(int volume));
+bool fat_size(IF_MV(int volume,) unsigned long *size, unsigned long *free);
+
+/** Misc. **/
+time_t fattime_mktime(uint16_t fatdate, uint16_t fattime);
+void fat_empty_fat_direntry(struct fat_direntry *entry);
+void fat_init(void);
+
+#endif /* FAT_H */
diff --git a/firmware/export/hostfs.h b/firmware/export/hostfs.h
index bbadecec31..a24d009ca9 100644
--- a/firmware/export/hostfs.h
+++ b/firmware/export/hostfs.h
@@ -41,4 +41,29 @@ extern bool hostfs_removable(int drive);
extern bool hostfs_present(int drive);
#endif
+/* This has to be repeated here for now for sim's sake since HAVE_HOSTFS
+ eats all the other stuff in storage.h. The sim probably shouldn't use
+ this. */
+#ifdef CONFIG_STORAGE_MULTI
+extern int hostfs_driver_type(int drive);
+#else
+# ifdef APPLICATION
+# define hostfs_driver_type(drive) (STORAGE_HOSTFS_NUM)
+# else /* !APPLICATION */
+# if (CONFIG_STORAGE & STORAGE_ATA)
+# define hostfs_driver_type(drive) (STORAGE_ATA_NUM)
+# elif (CONFIG_STORAGE & STORAGE_SD)
+# define hostfs_driver_type(drive) (STORAGE_SD_NUM)
+# elif (CONFIG_STORAGE & STORAGE_MMC)
+# define hostfs_driver_type(drive) (STORAGE_MMC_NUM)
+# elif (CONFIG_STORAGE & STORAGE_NAND)
+# define hostfs_driver_type(drive) (STORAGE_NAND_NUM)
+# elif (CONFIG_STORAGE & STORAGE_RAMDISK)
+# define hostfs_driver_type(drive) (STORAGE_RAMDISK_NUM)
+# else
+# error Unknown storage driver
+# endif /* CONFIG_STORAGE */
+# endif /* APPLICATION */
+#endif /* CONFIG_STORAGE_MULTI */
+
#endif /* HOSTFS_H */
diff --git a/firmware/export/load_code.h b/firmware/export/load_code.h
index cca577044e..df85a81f82 100644
--- a/firmware/export/load_code.h
+++ b/firmware/export/load_code.h
@@ -45,7 +45,17 @@ static inline void lc_close(void *handle) { (void)handle; }
#elif (CONFIG_PLATFORM & PLATFORM_HOSTED)
+#ifdef APPLICATION
+/* App doesn't simulate code loading from a buffer */
+static inline void * lc_open_from_mem(void *addr, size_t blob_size)
+{
+ return NULL;
+ (void)addr; (void)blob_size;
+}
+#else
extern void *lc_open_from_mem(void* addr, size_t blob_size);
+#endif
+
extern void *lc_get_header(void *handle);
extern void lc_close(void *handle);
diff --git a/firmware/export/mv.h b/firmware/export/mv.h
index 1d0a536663..620d77d30d 100644
--- a/firmware/export/mv.h
+++ b/firmware/export/mv.h
@@ -45,23 +45,44 @@
#define IF_MV(x...) x /* valist contents or empty */
#define IF_MV_NONVOID(x...) x /* valist contents or 'void' */
#define IF_MV_VOL(v) v /* volume argument or '0' */
-/* how to name volumes, first char must be outside of legal file names,
- a number gets appended to enumerate, if applicable */
-#if (CONFIG_STORAGE & STORAGE_MMC)
-#define VOL_NAMES "<MMC%d>"
-#define VOL_ENUM_POS 4 /* position of %d, to avoid runtime calculation */
-#elif (CONFIG_STORAGE & STORAGE_SD)
-#define VOL_NAMES "<microSD%d>"
-#define VOL_ENUM_POS 8 /* position of %d, to avoid runtime calculation */
-#else
-#define VOL_NAMES "<HD%d>"
-#define VOL_ENUM_POS 3
-#endif /* CONFIG_STORAGE */
-#ifdef HAVE_HOTSWAP
-bool volume_removable(int volume);
-bool volume_present(int volume);
+/* Format: "/<DEC###>/foo/bar"
+ * The "DEC" is pure decoration and treated as a comment. Only an unbroken
+ * trailing string of digits within the brackets is parsed as the volume
+ * number.
+ *
+ * IMPORTANT!: Adjust VOL_DEC_MAX_LEN if needed to the longest of these
+ */
+#define DEFAULT_VOL_DEC "Volume"
+
+#if (CONFIG_STORAGE & STORAGE_ATA)
+#define ATA_VOL_DEC "HDD"
+#endif
+#if (CONFIG_STORAGE & STORAGE_MMC)
+#define MMC_VOL_DEC "MMC"
+#endif
+#if (CONFIG_STORAGE & STORAGE_SD)
+#define SD_VOL_DEC "microSD"
+#endif
+#if (CONFIG_STORAGE & STORAGE_NAND)
+#define NAND_VOL_DEC "NAND"
+#endif
+#if (CONFIG_STORAGE & STORAGE_RAMDISK)
+#define RAMDISK_VOL_DEC "RAMDisk"
+#endif
+#if (CONFIG_STORAGE & STORAGE_HOSTFS)
+#ifndef HOSTFS_VOL_DEC /* overridable */
+#define HOSTFS_VOL_DEC DEFAULT_VOL_DEC
#endif
+#endif
+
+/* Characters that delimit a volume specifier at any root point in the path.
+ The tokens must be outside of legal filename characters */
+#define VOL_START_TOK '<'
+#define VOL_END_TOK '>'
+#define VOL_DEC_MAX_LEN 7 /* biggest of all xxx_VOL_DEC defines */
+#define VOL_MAX_LEN (1 + VOL_DEC_MAX_LEN + 2 + 1)
+#define VOL_NUM_MAX 100
#else /* empty definitions if no multi-volume */
#define IF_MV(x...)
@@ -69,4 +90,30 @@ bool volume_present(int volume);
#define IF_MV_VOL(v) 0
#endif /* HAVE_MULTIVOLUME */
+#define CHECK_VOL(volume) \
+ ((unsigned int)IF_MV_VOL(volume) < NUM_VOLUMES)
+
+#define CHECK_DRV(drive) \
+ ((unsigned int)IF_MD_DRV(drive) < NUM_DRIVES)
+
+/* Volume-centric functions (in disk.c) */
+void volume_recalc_free(IF_MV_NONVOID(int volume));
+unsigned int volume_get_cluster_size(IF_MV_NONVOID(int volume));
+void volume_size(IF_MV(int volume,) unsigned long *size, unsigned long *free);
+bool volume_ismounted(IF_MV_NONVOID(int volume));
+#ifdef HAVE_HOTSWAP
+bool volume_removable(int volume);
+bool volume_present(int volume);
+#endif /* HAVE_HOTSWAP */
+
+#ifdef HAVE_MULTIDRIVE
+int volume_drive(int volume);
+#else /* !HAVE_MULTIDRIVE */
+static inline int volume_drive(int volume)
+{
+ return 0;
+ (void)volume;
+}
+#endif /* HAVE_MULTIDRIVE */
+
#endif /* __MV_H__ */
diff --git a/firmware/export/pathfuncs.h b/firmware/export/pathfuncs.h
new file mode 100644
index 0000000000..26eb4a1067
--- /dev/null
+++ b/firmware/export/pathfuncs.h
@@ -0,0 +1,100 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _PATHFUNCS_H_
+#define _PATHFUNCS_H_
+
+#include <sys/types.h>
+#define __need_size_t
+#include <stddef.h>
+#include <stdbool.h>
+#include "config.h"
+
+/* useful char constants that could be reconfigured if desired */
+#define PATH_SEPCH '/'
+#define PATH_SEPSTR "/"
+#define PATH_ROOTSTR "/"
+#define PATH_BADSEPCH '\\'
+#define PATH_DRVSEPCH ':'
+
+/* a nicer way to check for "." and ".." than two strcmp() calls */
+static inline bool is_dotdir_name(const char *name)
+{
+ return name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]));
+}
+
+static inline bool name_is_dot(const char *name)
+{
+ return name[0] == '.' && !name[1];
+}
+
+static inline bool name_is_dot_dot(const char *name)
+{
+ return name[0] == '.' && name[1] == '.' && !name[2];
+}
+
+/* return a pointer to the character following path separators */
+#define GOBBLE_PATH_SEPCH(p) \
+ ({ int _c; \
+ const char *_p = (p); \
+ while ((_c = *_p) == PATH_SEPCH) \
+ ++_p; \
+ _p; })
+
+/* return a pointer to the character following a path component which may
+ be a separator or the terminating nul */
+#define GOBBLE_PATH_COMP(p) \
+ ({ int _c; \
+ const char *_p = (p); \
+ while ((_c = *_p) && _c != PATH_SEPCH) \
+ ++_p; \
+ _p; })
+
+/* does the character terminate a path component? */
+#define IS_COMP_TERMINATOR(c) \
+ ({ int _c = (c); \
+ !_c || _c == PATH_SEPCH; })
+
+#ifdef HAVE_MULTIVOLUME
+int path_strip_volume(const char *name, const char **nameptr, bool greedy);
+int get_volume_name(int volume, char *name);
+#endif
+
+int path_strip_drive(const char *name, const char **nameptr, bool greedy);
+size_t path_trim_whitespace(const char *name, const char **nameptr);
+size_t path_basename(const char *name, const char **nameptr);
+size_t path_dirname(const char *name, const char **nameptr);
+size_t path_strip_trailing_separators(const char *name, const char **nameptr);
+void path_correct_separators(char *dstpath, const char *path);
+
+/* constants useable in basepath and component */
+#define PA_SEP_HARD NULL /* separate even if base is empty */
+#define PA_SEP_SOFT "" /* separate only if base is nonempty */
+size_t path_append(char *buffer, const char *basepath, const char *component,
+ size_t bufsize);
+ssize_t parse_path_component(const char **pathp, const char **namep);
+
+/* return true if path begins with a root '/' component and is not NULL */
+static inline bool path_is_absolute(const char *path)
+{
+ return path && path[0] == PATH_SEPCH;
+}
+
+#endif /* _PATHFUNCS_H_ */
diff --git a/firmware/export/rbpaths.h b/firmware/export/rbpaths.h
index 3600709482..b04d669716 100644
--- a/firmware/export/rbpaths.h
+++ b/firmware/export/rbpaths.h
@@ -70,27 +70,6 @@
#define HOME_DIR_LEN (sizeof(HOME_DIR)-1)
-#ifdef APPLICATION
-
-#include <dirent.h>
-#include <fcntl.h>
-#include <unistd.h>
-
-int app_open(const char *name, int o, ...);
-int app_creat(const char* name, mode_t mode);
-int app_remove(const char *name);
-int app_rename(const char *old, const char *new);
-DIR* app_opendir(const char *_name);
-int app_closedir(DIR *dir);
-struct dirent* app_readdir(DIR* dir);
-int app_mkdir(const char* name);
-int app_rmdir(const char* name);
-ssize_t app_readlink(const char *path, char *buf, size_t bufsiz);
-
-extern void paths_init(void);
-
-#endif
-
#define REC_BASE_DIR HOME_DIR
#define PLAYLIST_CATALOG_DEFAULT_DIR HOME_DIR "/Playlists"
diff --git a/firmware/export/storage.h b/firmware/export/storage.h
index 8e7281d523..14cba09b35 100644
--- a/firmware/export/storage.h
+++ b/firmware/export/storage.h
@@ -93,6 +93,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
#define storage_removable(drive) hostfs_removable(IF_MD(drive))
#define storage_present(drive) hostfs_present(IF_MD(drive))
#endif
+ #define storage_driver_type(drive) hostfs_driver_type(IF_MV(drive))
#elif (CONFIG_STORAGE & STORAGE_ATA)
#define STORAGE_FUNCTION(NAME) (ata_## NAME)
#define storage_spindown ata_spindown
@@ -119,6 +120,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
#define storage_removable(drive) ata_removable(IF_MD(drive))
#define storage_present(drive) ata_present(IF_MD(drive))
#endif
+ #define storage_driver_type(drive) (STORAGE_ATA_NUM)
#elif (CONFIG_STORAGE & STORAGE_SD)
#define STORAGE_FUNCTION(NAME) (sd_## NAME)
#define storage_spindown sd_spindown
@@ -145,6 +147,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
#define storage_removable(drive) sd_removable(IF_MD(drive))
#define storage_present(drive) sd_present(IF_MD(drive))
#endif
+ #define storage_driver_type(drive) (STORAGE_SD_NUM)
#elif (CONFIG_STORAGE & STORAGE_MMC)
#define STORAGE_FUNCTION(NAME) (mmc_## NAME)
#define storage_spindown mmc_spindown
@@ -170,6 +173,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
#define storage_removable(drive) mmc_removable(IF_MD(drive))
#define storage_present(drive) mmc_present(IF_MD(drive))
#endif
+ #define storage_driver_type(drive) (STORAGE_MMC_NUM)
#elif (CONFIG_STORAGE & STORAGE_NAND)
#define STORAGE_FUNCTION(NAME) (nand_## NAME)
#define storage_spindown nand_spindown
@@ -195,6 +199,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
#define storage_removable(drive) nand_removable(IF_MD(drive))
#define storage_present(drive) nand_present(IF_MD(drive))
#endif
+ #define storage_driver_type(drive) (STORAGE_NAND_NUM)
#elif (CONFIG_STORAGE & STORAGE_RAMDISK)
#define STORAGE_FUNCTION(NAME) (ramdisk_## NAME)
#define storage_spindown ramdisk_spindown
@@ -220,6 +225,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
#define storage_removable(drive) ramdisk_removable(IF_MD(drive))
#define storage_present(drive) ramdisk_present(IF_MD(drive))
#endif
+ #define storage_driver_type(drive) (STORAGE_RAMDISK_NUM)
#else
//#error No storage driver!
#endif
@@ -246,6 +252,7 @@ void storage_get_info(int drive, struct storage_info *info);
bool storage_removable(int drive);
bool storage_present(int drive);
#endif
+int storage_driver_type(int drive);
#endif /* NOT CONFIG_STORAGE_MULTI and NOT SIMULATOR*/
diff --git a/firmware/export/system.h b/firmware/export/system.h
index 4442eb96d7..47dd858d81 100644
--- a/firmware/export/system.h
+++ b/firmware/export/system.h
@@ -104,6 +104,10 @@ int get_cpu_boost_counter(void);
/* return number of elements in array a */
#define ARRAYLEN(a) (sizeof(a)/sizeof((a)[0]))
+/* is the given pointer "p" inside the said bounds of array "a"? */
+#define PTR_IN_ARRAY(a, p, numelem) \
+ ((uintptr_t)(p) - (uintptr_t)(a) < (uintptr_t)(numelem)*sizeof ((a)[0]))
+
/* return p incremented by specified number of bytes */
#define SKIPBYTES(p, count) ((typeof (p))((char *)(p) + (count)))
@@ -188,9 +192,13 @@ enum {
#include "system-target.h"
#elif defined(HAVE_SDL) /* SDL build */
#include "system-sdl.h"
+#ifdef SIMULATOR
+#include "system-sim.h"
+#endif
#elif defined(__PCTOOL__)
-#include "system-sdl.h"
+#include "system-hosted.h"
#endif
+
#include "bitswap.h"
#include "rbendian.h"
diff --git a/firmware/include/dir.h b/firmware/include/dir.h
index 6e8b70588e..f7719823a9 100644
--- a/firmware/include/dir.h
+++ b/firmware/include/dir.h
@@ -18,46 +18,71 @@
* KIND, either express or implied.
*
****************************************************************************/
-
#ifndef _DIR_H_
#define _DIR_H_
+#include <sys/types.h>
+#include <fcntl.h>
+#include <time.h>
#include "config.h"
+#include "fs_attr.h"
-#define ATTR_READ_ONLY 0x01
-#define ATTR_HIDDEN 0x02
-#define ATTR_SYSTEM 0x04
-#define ATTR_VOLUME_ID 0x08
-#define ATTR_DIRECTORY 0x10
-#define ATTR_ARCHIVE 0x20
-#define ATTR_VOLUME 0x40 /* this is a volume, not a real directory */
-#define ATTR_LINK 0x80
-
-#ifdef HAVE_DIRCACHE
-# include "dircache.h"
-# define DIR DIR_CACHED
-# define dirent dirent_cached
-# define opendir opendir_cached
-# define closedir closedir_cached
-# define readdir readdir_cached
-# define closedir closedir_cached
-# define mkdir mkdir_cached
-# define rmdir rmdir_cached
+#if defined (APPLICATION)
+#include "filesystem-app.h"
+#elif defined(SIMULATOR) || defined(__PCTOOL__)
+#include "../../uisimulator/common/filesystem-sim.h"
#else
-# include "dir_uncached.h"
-# define DIR DIR_UNCACHED
-# define dirent dirent_uncached
-# define opendir opendir_uncached
-# define closedir closedir_uncached
-# define readdir readdir_uncached
-# define closedir closedir_uncached
-# define mkdir mkdir_uncached
-# define rmdir rmdir_uncached
+#include "filesystem-native.h"
+#endif
+
+#ifndef DIRFUNCTIONS_DEFINED
+#ifndef opendir
+#define opendir FS_PREFIX(opendir)
+#endif
+#ifndef readdir
+#define readdir FS_PREFIX(readdir)
+#endif
+#ifndef readdir_r
+#define readdir_r FS_PREFIX(readdir_r)
+#endif
+#ifndef rewinddir
+#define rewinddir FS_PREFIX(rewinddir)
#endif
+#ifndef closedir
+#define closedir FS_PREFIX(closedir)
+#endif
+#ifndef mkdir
+#define mkdir FS_PREFIX(mkdir)
+#endif
+#ifndef rmdir
+#define rmdir FS_PREFIX(rmdir)
+#endif
+#ifndef samedir
+#define samedir FS_PREFIX(samedir)
+#endif
+#ifndef dir_exists
+#define dir_exists FS_PREFIX(dir_exists)
+#endif
+#endif /* !DIRFUNCTIONS_DEFINED */
+#ifndef DIRENT_DEFINED
+struct DIRENT
+{
+ struct dirinfo_native info; /* platform extra info */
+ char d_name[MAX_PATH]; /* UTF-8 name of entry (last!) */
+};
+#endif /* DIRENT_DEFINED */
-typedef DIR* (*opendir_func)(const char* name);
-typedef int (*closedir_func)(DIR* dir);
-typedef struct dirent* (*readdir_func)(DIR* dir);
+struct dirinfo
+{
+ unsigned int attribute; /* attribute bits of file */
+ off_t size; /* binary size of file */
+ time_t mtime; /* local file time */
+};
-#endif
+#ifndef DIRFUNCTIONS_DECLARED
+/* TIP: set errno to zero before calling to see if anything failed */
+struct dirinfo dir_get_info(DIR *dirp, struct DIRENT *entry);
+#endif /* !DIRFUNCTIONS_DECLARED */
+
+#endif /* _DIR_H_ */
diff --git a/firmware/include/dir_uncached.h b/firmware/include/dir_uncached.h
deleted file mode 100644
index 6443d5ba97..0000000000
--- a/firmware/include/dir_uncached.h
+++ /dev/null
@@ -1,107 +0,0 @@
-/***************************************************************************
- * __________ __ ___.
- * Open \______ \ ____ ____ | | _\_ |__ _______ ___
- * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
- * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
- * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
- * \/ \/ \/ \/ \/
- * $Id: dir.h 13741 2007-06-30 02:08:27Z jethead71 $
- *
- * Copyright (C) 2002 by Björn Stenberg
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- ****************************************************************************/
-#ifndef _DIR_UNCACHED_H_
-#define _DIR_UNCACHED_H_
-
-#include "config.h"
-
-struct dirinfo {
- int attribute;
- long size;
- unsigned short wrtdate;
- unsigned short wrttime;
-};
-
-#include <stdbool.h>
-#include "file.h"
-
-#if defined(SIMULATOR) || defined(__PCTOOL__)
-# define dirent_uncached sim_dirent
-# define DIR_UNCACHED SIM_DIR
-# define opendir_uncached sim_opendir
-# define readdir_uncached sim_readdir
-# define closedir_uncached sim_closedir
-# define mkdir_uncached sim_mkdir
-# define rmdir_uncached sim_rmdir
-#elif defined(APPLICATION)
-# include "rbpaths.h"
-# define DIRENT_DEFINED
-# define DIR_DEFINED
-# define dirent_uncached dirent
-# define DIR_UNCACHED DIR
-# define opendir_uncached app_opendir
-# define readdir_uncached app_readdir
-# define closedir_uncached app_closedir
-# define mkdir_uncached app_mkdir
-# define rmdir_uncached app_rmdir
-#endif
-
-
-#ifndef DIRENT_DEFINED
-struct dirent_uncached {
- unsigned char d_name[MAX_PATH];
- struct dirinfo info;
- long startcluster;
-};
-#endif
-
-#include "fat.h"
-
-#ifndef DIR_DEFINED
-typedef struct {
-#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
- struct fat_dir fatdir CACHEALIGN_ATTR;
- bool busy;
- long startcluster;
- struct dirent_uncached theent;
-#ifdef HAVE_MULTIVOLUME
- int volumecounter; /* running counter for faked volume entries */
-#endif
-#else
- /* simulator/application: */
- void *dir; /* actually a DIR* dir */
- char *name;
-#endif
-} DIR_UNCACHED CACHEALIGN_ATTR;
-#endif
-
-#ifdef HAVE_HOTSWAP
-char *get_volume_name(int volume);
-#endif
-
-#ifdef HAVE_MULTIVOLUME
- int strip_volume(const char*, char*);
-#endif
-
-#ifndef DIRFUNCTIONS_DEFINED
-
-extern DIR_UNCACHED* opendir_uncached(const char* name);
-extern int closedir_uncached(DIR_UNCACHED* dir);
-extern int mkdir_uncached(const char* name);
-extern int rmdir_uncached(const char* name);
-
-extern struct dirent_uncached* readdir_uncached(DIR_UNCACHED* dir);
-
-extern int release_dirs(int volume);
-
-#endif /* DIRFUNCTIONS_DEFINED */
-
-#endif
diff --git a/firmware/include/dircache.h b/firmware/include/dircache.h
index 019ccf49b7..7e8c764e7f 100644
--- a/firmware/include/dircache.h
+++ b/firmware/include/dircache.h
@@ -8,6 +8,7 @@
* $Id$
*
* Copyright (C) 2005 by Miika Pekkarinen
+ * 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
@@ -21,84 +22,154 @@
#ifndef _DIRCACHE_H
#define _DIRCACHE_H
-#include "config.h"
-#include "dir_uncached.h"
-#include <string.h> /* size_t */
+#include "mv.h"
+#include <string.h> /* size_t */
+#include <sys/types.h> /* ssize_t */
#ifdef HAVE_DIRCACHE
-#define DIRCACHE_RESERVE (1024*64)
-#define DIRCACHE_LIMIT (1024*1024*6)
-
-#define DIRCACHE_APPFLAG_TAGCACHE 0x0001
-#define DIRCACHE_APPFLAG_PLAYLIST 0x0002
-
-/* Internal structures. */
-struct travel_data {
- struct dircache_entry *first;
- struct dircache_entry *ce;
- struct dircache_entry *down_entry;
-#if (CONFIG_PLATFORM & PLATFORM_HOSTED)
- DIR_UNCACHED *dir, *newdir;
- struct dirent_uncached *entry;
-#else
- struct fat_dir *dir;
- struct fat_dir newdir;
- struct fat_direntry entry;
+/****************************************************************************
+ ** Configurable values
+ **/
+
+#if 0
+/* enable dumping code */
+#define DIRCACHE_DUMPSTER
+#define DIRCACHE_DUMPSTER_BIN "/dircache_dump.bin"
+#define DIRCACHE_DUMPSTER_CSV "/dircache_dump.csv"
#endif
- int pathpos;
+
+/* dircache builds won't search below this but will work down to this point
+ while below it the cache will just pass requests through to the storage;
+ the limiting factor is the scanning thread stack size, not the
+ implementation -- tune the two together */
+#define DIRCACHE_MAX_DEPTH 15
+#define DIRCACHE_STACK_SIZE (DEFAULT_STACK_SIZE + 0x100)
+
+/* memory buffer constants that control allocation */
+#define DIRCACHE_RESERVE (1024*64) /* 64 KB - new entry slack */
+#define DIRCACHE_MIN (1024*1024*1) /* 1 MB - provision min size */
+#define DIRCACHE_LIMIT (1024*1024*6) /* 6 MB - provision max size */
+
+/* make it easy to change serialnumber size without modifying anything else;
+ 32 bits allows 21845 builds before wrapping in a 6MB cache that is filled
+ exclusively with entries and nothing else (32 byte entries), making that
+ figure pessimistic */
+typedef uint32_t dc_serial_t;
+
+/**
+ ****************************************************************************/
+
+#if CONFIG_PLATFORM & PLATFORM_NATIVE
+/* native dircache is lower-level than on a hosted target */
+#define DIRCACHE_NATIVE
+#endif
+
+struct dircache_file
+{
+ int idx; /* this file's cache index */
+ dc_serial_t serialnum; /* this file's serial number */
};
-struct dirent_cached {
- struct dirinfo info;
- char *d_name;
- long startcluster;
+enum dircache_status
+{
+ DIRCACHE_IDLE = 0, /* no volume is initialized */
+ DIRCACHE_SCANNING = 1, /* dircache is scanning a volume */
+ DIRCACHE_READY = 2, /* dircache is ready to be used */
+};
+
+/** Dircache control **/
+void dircache_wait(void);
+void dircache_suspend(void);
+int dircache_resume(void);
+int dircache_enable(void);
+void dircache_disable(void);
+void dircache_free_buffer(void);
+
+/** Volume mounting **/
+void dircache_mount(void); /* always tries building everything it can */
+void dircache_unmount(IF_MV_NONVOID(int volume));
+
+
+/** File API service functions **/
+
+/* avoid forcing #include of file_internal.h, fat.h and dir.h */
+struct filestr_base;
+struct file_base_info;
+struct file_base_binding;
+struct dirent;
+struct dirscan_info;
+struct dirinfo_native;
+
+int dircache_readdir_dirent(struct filestr_base *stream,
+ struct dirscan_info *scanp,
+ struct dirent *entry);
+void dircache_rewinddir_dirent(struct dirscan_info *scanp);
+
+#ifdef DIRCACHE_NATIVE
+struct fat_direntry;
+int dircache_readdir_internal(struct filestr_base *stream,
+ struct file_base_info *infop,
+ struct fat_direntry *fatent);
+void dircache_rewinddir_internal(struct file_base_info *info);
+#endif /* DIRCACHE_NATIVE */
+
+
+/** Dircache live updating **/
+
+void dircache_get_rootinfo(struct file_base_info *infop);
+void dircache_bind_file(struct file_base_binding *bindp);
+void dircache_unbind_file(struct file_base_binding *bindp);
+void dircache_fileop_create(struct file_base_info *dirinfop,
+ struct file_base_binding *bindp,
+ const char *basename,
+ const struct dirinfo_native *dinp);
+void dircache_fileop_rename(struct file_base_info *dirinfop,
+ struct file_base_binding *bindp,
+ const char *basename);
+void dircache_fileop_remove(struct file_base_binding *bindp);
+void dircache_fileop_sync(struct file_base_binding *infop,
+ const struct dirinfo_native *dinp);
+
+
+/** Dircache paths and files **/
+ssize_t dircache_get_path(const struct dircache_file *dcfilep, char *buf,
+ size_t size);
+int dircache_get_file(const char *path, struct dircache_file *dcfilep);
+
+
+/** Debug screen/info stuff **/
+
+struct dircache_info
+{
+ enum dircache_status status; /* current composite status value */
+ const char *statusdesc; /* pointer to string describing 'status' */
+ size_t last_size; /* cache size after last build */
+ size_t size; /* total size of entries (with holes) */
+ size_t sizeused; /* bytes of 'size' actually utilized */
+ size_t size_limit; /* maximum possible size */
+ size_t reserve; /* size of reserve area */
+ size_t reserve_used; /* amount of reserve used */
+ unsigned int entry_count; /* number of cache entries */
+ long build_ticks; /* total time used to build cache */
};
-typedef struct {
- bool busy;
- struct dirent_cached theent; /* .attribute is set to -1 on init(opendir) */
- int internal_entry; /* the current entry in the directory */
- DIR_UNCACHED *regulardir;
-} DIR_CACHED;
+void dircache_get_info(struct dircache_info *info);
+#ifdef DIRCACHE_DUMPSTER
+void dircache_dump(void);
+#endif /* DIRCACHE_DUMPSTER */
+
-void dircache_init(void) INIT_ATTR;
+/** Misc. stuff **/
+void dircache_dcfile_init(struct dircache_file *dcfilep);
+
+#ifdef HAVE_EEPROM_SETTINGS
int dircache_load(void);
int dircache_save(void);
-int dircache_build(int last_size);
-void* dircache_steal_buffer(size_t *size);
-bool dircache_is_enabled(void);
-bool dircache_is_initializing(void);
-void dircache_set_appflag(long mask);
-bool dircache_get_appflag(long mask);
-int dircache_get_entry_count(void);
-int dircache_get_cache_size(void);
-int dircache_get_reserve_used(void);
-int dircache_get_build_ticks(void);
-void dircache_disable(void);
-void dircache_suspend(void);
-bool dircache_resume(void);
-int dircache_get_entry_id(const char *filename);
-size_t dircache_copy_path(int index, char *buf, size_t size);
-
-/* the next two are internal for file.c */
-long _dircache_get_entry_startcluster(int id);
-struct dirinfo* _dircache_get_entry_dirinfo(int id);
-
-void dircache_bind(int fd, const char *path);
-void dircache_update_filesize(int fd, long newsize, long startcluster);
-void dircache_update_filetime(int fd);
-void dircache_mkdir(const char *path);
-void dircache_rmdir(const char *path);
-void dircache_remove(const char *name);
-void dircache_rename(const char *oldpath, const char *newpath);
-void dircache_add_file(const char *path, long startcluster);
-
-DIR_CACHED* opendir_cached(const char* name);
-struct dirent_cached* readdir_cached(DIR_CACHED* dir);
-int closedir_cached(DIR_CACHED *dir);
-int mkdir_cached(const char *name);
-int rmdir_cached(const char* name);
-#endif /* !HAVE_DIRCACHE */
+#endif /* HAVE_EEPROM_SETTINGS */
-#endif
+void dircache_init(size_t last_size) INIT_ATTR;
+
+#endif /* HAVE_DIRCACHE */
+
+#endif /* _DIRCACHE_H */
diff --git a/firmware/include/dircache_redirect.h b/firmware/include/dircache_redirect.h
new file mode 100644
index 0000000000..15fb4bc38d
--- /dev/null
+++ b/firmware/include/dircache_redirect.h
@@ -0,0 +1,198 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _DIRCACHE_REDIRECT_H_
+
+#include "dir.h"
+
+/***
+ ** Internal redirects that depend upon whether or not dircache is made
+ **/
+
+/** File binding **/
+
+static inline void get_rootinfo_internal(struct file_base_info *infop)
+{
+#ifdef HAVE_DIRCACHE
+ dircache_get_rootinfo(infop);
+#else
+ (void)infop;
+#endif
+}
+
+static inline void fileobj_bind_file(struct file_base_binding *bindp)
+{
+#ifdef HAVE_DIRCACHE
+ dircache_bind_file(bindp);
+#else
+ file_binding_insert_last(bindp);
+#endif
+}
+
+static inline void fileobj_unbind_file(struct file_base_binding *bindp)
+{
+#ifdef HAVE_DIRCACHE
+ dircache_unbind_file(bindp);
+#else
+ file_binding_remove(bindp);
+#endif
+}
+
+
+/** File event handlers **/
+
+static inline void fileop_onopen_internal(struct filestr_base *stream,
+ struct file_base_info *srcinfop,
+ unsigned int callflags)
+{
+ fileobj_fileop_open(stream, srcinfop, callflags);
+}
+
+static inline void fileop_onclose_internal(struct filestr_base *stream)
+{
+ fileobj_fileop_close(stream);
+}
+
+static inline void fileop_oncreate_internal(struct filestr_base *stream,
+ struct file_base_info *srcinfop,
+ unsigned int callflags,
+ struct file_base_info *dirinfop,
+ const char *basename)
+{
+#ifdef HAVE_DIRCACHE
+ dircache_dcfile_init(&srcinfop->dcfile);
+#endif
+ fileobj_fileop_create(stream, srcinfop, callflags);
+#ifdef HAVE_DIRCACHE
+ struct dirinfo_native din;
+ fill_dirinfo_native(&din);
+ dircache_fileop_create(dirinfop, stream->bindp, basename, &din);
+#endif
+ (void)dirinfop; (void)basename;
+}
+
+static inline void fileop_onremove_internal(struct filestr_base *stream,
+ struct file_base_info *oldinfop)
+{
+ fileobj_fileop_remove(stream, oldinfop);
+#ifdef HAVE_DIRCACHE
+ dircache_fileop_remove(stream->bindp);
+#endif
+}
+
+static inline void fileop_onrename_internal(struct filestr_base *stream,
+ struct file_base_info *oldinfop,
+ struct file_base_info *dirinfop,
+ const char *basename)
+{
+ fileobj_fileop_rename(stream, oldinfop);
+#ifdef HAVE_DIRCACHE
+ dircache_fileop_rename(dirinfop, stream->bindp, basename);
+#endif
+ (void)dirinfop; (void)basename;
+}
+
+static inline void fileop_onsync_internal(struct filestr_base *stream)
+{
+ fileobj_fileop_sync(stream);
+#ifdef HAVE_DIRCACHE
+ struct dirinfo_native din;
+ fill_dirinfo_native(&din);
+ dircache_fileop_sync(stream->bindp, &din);
+#endif
+}
+
+static inline void fileop_ontruncate_internal(struct filestr_base *stream)
+{
+ fileobj_fileop_truncate(stream);
+}
+
+static inline void volume_onmount_internal(IF_MV_NONVOID(int volume))
+{
+#ifdef HAVE_DIRCACHE
+ dircache_mount();
+#endif
+ IF_MV( (void)volume; )
+}
+
+static inline void volume_onunmount_internal(IF_MV_NONVOID(int volume))
+{
+ fileobj_mgr_unmount(IF_MV(volume));
+#ifdef HAVE_DIRCACHE
+ dircache_unmount(IF_MV(volume));
+#endif
+}
+
+
+/** Directory reading **/
+
+static inline int readdir_dirent(struct filestr_base *stream,
+ struct dirscan_info *scanp,
+ struct dirent *entry)
+{
+#ifdef HAVE_DIRCACHE
+ return dircache_readdir_dirent(stream, scanp, entry);
+#else
+ return uncached_readdir_dirent(stream, scanp, entry);
+#endif
+}
+
+static inline void rewinddir_dirent(struct dirscan_info *scanp)
+{
+#ifdef HAVE_DIRCACHE
+ dircache_rewinddir_dirent(scanp);
+#else
+ uncached_rewinddir_dirent(scanp);
+#endif
+}
+
+static inline int readdir_internal(struct filestr_base *stream,
+ struct file_base_info *infop,
+ struct fat_direntry *fatent)
+{
+#ifdef HAVE_DIRCACHE
+ return dircache_readdir_internal(stream, infop, fatent);
+#else
+ return uncached_readdir_internal(stream, infop, fatent);
+#endif
+}
+
+static inline void rewinddir_internal(struct file_base_info *infop)
+{
+#ifdef HAVE_DIRCACHE
+ dircache_rewinddir_internal(infop);
+#else
+ uncached_rewinddir_internal(infop);
+#endif
+}
+
+
+/** Misc. stuff **/
+
+static inline struct fat_direntry *get_dir_fatent_dircache(void)
+{
+#ifdef HAVE_DIRCACHE
+ return get_dir_fatent();
+#else
+ return NULL;
+#endif
+}
+
+#endif /* _DIRCACHE_REDIRECT_H_ */
diff --git a/firmware/include/disk_cache.h b/firmware/include/disk_cache.h
new file mode 100644
index 0000000000..725b3778cc
--- /dev/null
+++ b/firmware/include/disk_cache.h
@@ -0,0 +1,83 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef DISK_CACHE_H
+#define DISK_CACHE_H
+
+/* This needs enough for all file handles to have a buffer in the worst case
+ * plus at least one reserved exclusively for the cache client and a couple
+ * for other file system code. The buffers are put to use by the cache if not
+ * taken for another purpose (meaning nothing is wasted sitting fallow).
+ *
+ * One map per volume is maintained in order to avoid collisions between
+ * volumes that would slow cache probing. DC_MAP_NUM_ENTRIES is the number
+ * for each map per volume. The buffers themselves are shared.
+ */
+#if MEMORYSIZE < 8
+#define DC_NUM_ENTRIES 32
+#define DC_MAP_NUM_ENTRIES 128
+#elif MEMORYSIZE <= 32
+#define DC_NUM_ENTRIES 48
+#define DC_MAP_NUM_ENTRIES 128
+#else /* MEMORYSIZE > 32 */
+#define DC_NUM_ENTRIES 64
+#define DC_MAP_NUM_ENTRIES 256
+#endif /* MEMORYSIZE */
+
+/* this _could_ be larger than a sector if that would ever be useful */
+#define DC_CACHE_BUFSIZE SECTOR_SIZE
+
+#include "mutex.h"
+#include "mv.h"
+
+static inline void dc_lock_cache(void)
+{
+ extern struct mutex disk_cache_mutex;
+ mutex_lock(&disk_cache_mutex);
+}
+
+static inline void dc_unlock_cache(void)
+{
+ extern struct mutex disk_cache_mutex;
+ mutex_unlock(&disk_cache_mutex);
+}
+
+void * dc_cache_probe(IF_MV(int volume,) unsigned long secnum,
+ unsigned int *flags);
+void dc_dirty_buf(void *buf);
+void dc_discard_buf(void *buf);
+void dc_commit_all(IF_MV_NONVOID(int volume));
+void dc_discard_all(IF_MV_NONVOID(int volume));
+
+void dc_init(void) INIT_ATTR;
+
+/* in addition to filling, writeback is implemented by the client */
+extern void dc_writeback_callback(IF_MV(int volume, ) unsigned long sector,
+ void *buf);
+
+
+/** These synchronize and can be called by anyone **/
+
+/* expropriate a buffer from the cache of DC_CACHE_BUFSIZE bytes */
+void * dc_get_buffer(void);
+/* return buffer to the cache by buffer */
+void dc_release_buffer(void *buf);
+
+#endif /* DISK_CACHE_H */
diff --git a/firmware/include/file.h b/firmware/include/file.h
index 77930864c7..8e5bacec0e 100644
--- a/firmware/include/file.h
+++ b/firmware/include/file.h
@@ -18,81 +18,88 @@
* KIND, either express or implied.
*
****************************************************************************/
-
#ifndef _FILE_H_
#define _FILE_H_
#include <sys/types.h>
-#include "config.h"
-#include "gcc_extensions.h"
+#include <stdbool.h>
#include <fcntl.h>
#ifdef WIN32
/* this has SEEK_SET et al */
#include <stdio.h>
#endif
-
+#include "config.h"
+#include "gcc_extensions.h"
#undef MAX_PATH /* this avoids problems when building simulator */
#define MAX_PATH 260
-#define MAX_OPEN_FILES 11
-#if !defined(PLUGIN) && !defined(CODEC)
-#if defined(APPLICATION) && !defined(__PCTOOL__)
-#include "rbpaths.h"
-# define open(x, ...) app_open(x, __VA_ARGS__)
-# define creat(x,m) app_creat(x, m)
-# define remove(x) app_remove(x)
-# define rename(x,y) app_rename(x,y)
-# define readlink(x,y,z) app_readlink(x,y,z)
-# if (CONFIG_PLATFORM & (PLATFORM_SDL|PLATFORM_MAEMO|PLATFORM_PANDORA))
-/* SDL overrides a few more */
-# define read(x,y,z) sim_read(x,y,z)
-# define write(x,y,z) sim_write(x,y,z)
-# endif
-#elif defined(SIMULATOR) || defined(DBTOOL)
-# define open(x, ...) sim_open(x, __VA_ARGS__)
-# define creat(x,m) sim_creat(x,m)
-# define remove(x) sim_remove(x)
-# define rename(x,y) sim_rename(x,y)
-# define fsync(x) sim_fsync(x)
-# define ftruncate(x,y) sim_ftruncate(x,y)
-# define lseek(x,y,z) sim_lseek(x,y,z)
-# define read(x,y,z) sim_read(x,y,z)
-# define write(x,y,z) sim_write(x,y,z)
-# define close(x) sim_close(x)
-/* readlink() not used in the sim yet */
-extern int sim_open(const char *name, int o, ...);
-extern int sim_creat(const char *name, mode_t mode);
-#endif
+enum relate_result
+{
+ /* < 0 == failure */
+ RELATE_DIFFERENT = 0, /* the two paths are different objects */
+ RELATE_SAME, /* the two paths are the same object */
+ RELATE_PREFIX, /* the path2 contains path1 as a prefix */
+};
-typedef int (*open_func)(const char* pathname, int flags, ...);
-typedef ssize_t (*read_func)(int fd, void *buf, size_t count);
-typedef int (*creat_func)(const char *pathname, mode_t mode);
-typedef ssize_t (*write_func)(int fd, const void *buf, size_t count);
-typedef void (*qsort_func)(void *base, size_t nmemb, size_t size,
- int(*_compar)(const void *, const void *));
+#if defined(APPLICATION)
+#include "filesystem-app.h"
+#elif defined(SIMULATOR) || defined(__PCTOOL__)
+#include "../../uisimulator/common/filesystem-sim.h"
+#else
+#include "filesystem-native.h"
+#endif
-extern int file_open(const char* pathname, int flags);
-extern int close(int fd);
-extern int fsync(int fd);
-extern ssize_t read(int fd, void *buf, size_t count);
-extern off_t lseek(int fildes, off_t offset, int whence);
-extern int file_creat(const char *pathname);
-#if ((CONFIG_PLATFORM & PLATFORM_NATIVE) && !defined(__PCTOOL__)) || \
- defined(TEST_FAT)
-#define creat(x, y) file_creat(x)
+#ifndef FILEFUNCTIONS_DECLARED
+int fdprintf(int fildes, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
+#endif /* FILEFUNCTIONS_DECLARED */
-#if !defined(CODEC) && !defined(PLUGIN)
-#define open(x, y, ...) file_open(x,y)
+#ifndef FILEFUNCTIONS_DEFINED
+#ifndef open
+#define open FS_PREFIX(open)
#endif
+#ifndef creat
+#define creat FS_PREFIX(creat)
#endif
-
-extern ssize_t write(int fd, const void *buf, size_t count);
-extern int remove(const char* pathname);
-extern int rename(const char* path, const char* newname);
-extern int ftruncate(int fd, off_t length);
-extern off_t filesize(int fd);
-extern int release_files(int volume);
-int fdprintf (int fd, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
-#endif /* !CODEC && !PLUGIN */
+#ifndef close
+#define close FS_PREFIX(close)
+#endif
+#ifndef ftruncate
+#define ftruncate FS_PREFIX(ftruncate)
+#endif
+#ifndef fsync
+#define fsync FS_PREFIX(fsync)
#endif
+#ifndef lseek
+#define lseek FS_PREFIX(lseek)
+#endif
+#ifndef read
+#define read FS_PREFIX(read)
+#endif
+#ifndef write
+#define write FS_PREFIX(write)
+#endif
+#ifndef remove
+#define remove FS_PREFIX(remove)
+#endif
+#ifndef rename
+#define rename FS_PREFIX(rename)
+#endif
+#ifndef filesize
+#define filesize FS_PREFIX(filesize)
+#endif
+#ifndef fsamefile
+#define fsamefile FS_PREFIX(fsamefile)
+#endif
+#ifndef file_exists
+#define file_exists FS_PREFIX(file_exists)
+#endif
+#ifndef relate
+#define relate FS_PREFIX(relate)
+#endif
+#ifndef readlink
+#define readlink FS_PREFIX(readlink)
+#endif
+#endif /* FILEFUNCTIONS_DEFINED */
+
+#endif /* _FILE_H_ */
diff --git a/firmware/include/file_internal.h b/firmware/include/file_internal.h
new file mode 100644
index 0000000000..d1bb67406a
--- /dev/null
+++ b/firmware/include/file_internal.h
@@ -0,0 +1,371 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _FILE_INTERNAL_H_
+#define _FILE_INTERNAL_H_
+
+#include <sys/types.h>
+#include <stdlib.h>
+#include "mv.h"
+#include "linked_list.h"
+#include "mutex.h"
+#include "mrsw_lock.h"
+#include "fs_attr.h"
+#include "fat.h"
+#ifdef HAVE_DIRCACHE
+#include "dircache.h"
+#endif
+
+/** Tuneable parameters **/
+
+/* limits for number of open descriptors - if you increase these values, make
+ certain that the disk cache has enough available buffers */
+#define MAX_OPEN_FILES 11
+#define MAX_OPEN_DIRS 12
+#define MAX_OPEN_HANDLES (MAX_OPEN_FILES+MAX_OPEN_DIRS)
+
+/* internal functions open streams as well; make sure they don't fail if all
+ user descs are busy; this needs to be at least the greatest quantity needed
+ at once by all internal functions */
+#ifdef HAVE_DIRCACHE
+#define AUX_FILEOBJS 3
+#else
+#define AUX_FILEOBJS 2
+#endif
+
+/* number of components statically allocated to handle the vast majority
+ of path depths; should maybe be tuned for >= 90th percentile but for now,
+ imma just guessing based on something like:
+ root + 'Music' + 'Artist' + 'Album' + 'Disc N' + filename */
+#define STATIC_PATHCOMP_NUM 6
+
+#define MAX_NAME 255
+
+/* unsigned value that will also hold the off_t range we need without
+ overflow */
+#define file_size_t uint32_t
+
+#ifdef __USE_FILE_OFFSET64
+/* if we want, we can deal with files up to 2^32-1 bytes-- the full FAT16/32
+ range */
+#define FILE_SIZE_MAX (0xffffffffu)
+#else
+/* file contents and size will be preserved by the APIs so long as ftruncate()
+ isn't used; bytes passed 2^31-1 will not accessible nor will writes succeed
+ that would extend the file beyond the max for a 32-bit off_t */
+#define FILE_SIZE_MAX (0x7fffffffu)
+#endif
+
+/* if file is "large(ish)", then get rid of the contents now rather than
+ lazily when the file is synced or closed in order to free-up space */
+#define O_TRUNC_THRESH 65536
+
+/* default attributes when creating new files and directories */
+#define ATTR_NEW_FILE (ATTR_ARCHIVE)
+#define ATTR_NEW_DIRECTORY (ATTR_DIRECTORY)
+
+#define ATTR_MOUNT_POINT (ATTR_VOLUME | ATTR_DIRECTORY)
+
+/** File sector cache **/
+
+enum filestr_cache_flags
+{
+ FSC_DIRTY = 0x1, /* buffer is dirty (needs writeback) */
+ FSC_NEW = 0x2, /* buffer is new (never yet written) */
+};
+
+struct filestr_cache
+{
+ uint8_t *buffer; /* buffer to hold sector */
+ unsigned long sector; /* file sector that is in buffer */
+ unsigned int flags; /* FSC_* bits */
+};
+
+void file_cache_init(struct filestr_cache *cachep);
+void file_cache_reset(struct filestr_cache *cachep);
+void file_cache_alloc(struct filestr_cache *cachep);
+void file_cache_free(struct filestr_cache *cachep);
+
+
+/** Common bitflags used throughout **/
+
+/* bitflags used by open files and descriptors */
+enum fildes_and_obj_flags
+{
+ /* used in descriptor and common */
+ FDO_BUSY = 0x0001, /* descriptor/object is in use */
+ /* only used in individual stream descriptor */
+ FD_WRITE = 0x0002, /* descriptor has write mode */
+ FD_WRONLY = 0x0004, /* descriptor is write mode only */
+ FD_APPEND = 0x0008, /* descriptor is append mode */
+ /* only used as common flags */
+ FO_DIRECTORY = 0x0010, /* fileobj is a directory */
+ FO_TRUNC = 0x0020, /* fileobj is opened to be truncated */
+ FO_REMOVED = 0x0040, /* fileobj was deleted while open */
+ FO_SINGLE = 0x0080, /* fileobj has only one stream open */
+ FDO_MASK = 0x00ff,
+ /* bitflags that instruct various 'open' functions how to behave */
+ FF_FILE = 0x0000, /* expect file; accept file only */
+ FF_DIR = 0x0100, /* expect dir; accept dir only */
+ FF_ANYTYPE = 0x0200, /* succeed if either file or dir */
+ FF_TYPEMASK = 0x0300, /* mask of typeflags */
+ FF_CREAT = 0x0400, /* create if file doesn't exist */
+ FF_EXCL = 0x0800, /* fail if creating and file exists */
+ FF_CHECKPREFIX = 0x1000, /* detect if file is prefix of path */
+ FF_NOISO = 0x2000, /* do not decode ISO filenames to UTF-8 */
+ FF_MASK = 0x3f00,
+ /* special values used in isolation */
+ FV_NONEXIST = 0x8000, /* closed but not freed (unmounted) */
+ FV_OPENSYSROOT = 0xc001, /* open sysroot, volume 0 not mounted */
+};
+
+
+/** Common data structures used throughout **/
+
+/* basic file information about its location */
+struct file_base_info
+{
+ union {
+#ifdef HAVE_MULTIVOLUME
+ int volume; /* file's volume (overlaps fatfile.volume) */
+#endif
+#if CONFIG_PLATFORM & PLATFORM_NATIVE
+ struct fat_file fatfile; /* FS driver file info */
+#endif
+ };
+#ifdef HAVE_DIRCACHE
+ struct dircache_file dcfile; /* dircache file info */
+#endif
+};
+
+#define BASEINFO_VOL(infop) \
+ IF_MV_VOL((infop)->volume)
+
+/* open files binding item */
+struct file_base_binding
+{
+ struct ll_node node; /* list item node (first!) */
+ struct file_base_info info; /* basic file info */
+};
+
+#define BASEBINDING_VOL(bindp) \
+ BASEINFO_VOL(&(bindp)->info)
+
+/* directory scanning position info */
+struct dirscan_info
+{
+#if CONFIG_PLATFORM & PLATFORM_NATIVE
+ struct fat_dirscan_info fatscan; /* FS driver scan info */
+#endif
+#ifdef HAVE_DIRCACHE
+ struct dircache_file dcscan; /* dircache scan info */
+#endif
+};
+
+/* describes the file as an open stream */
+struct filestr_base
+{
+ struct ll_node node; /* list item node (first!) */
+ uint16_t flags; /* FD_* bits of this stream */
+ uint16_t unused; /* not used */
+ struct filestr_cache cache; /* stream-local cache */
+ struct filestr_cache *cachep; /* the cache in use (local or shared) */
+ struct file_base_info *infop; /* base file information */
+ struct fat_filestr fatstr; /* FS driver information */
+ struct file_base_binding *bindp; /* common binding for file/dir */
+ struct mutex *mtx; /* serialization for this stream */
+};
+
+void filestr_base_init(struct filestr_base *stream);
+void filestr_base_destroy(struct filestr_base *stream);
+void filestr_alloc_cache(struct filestr_base *stream);
+void filestr_free_cache(struct filestr_base *stream);
+void filestr_assign_cache(struct filestr_base *stream,
+ struct filestr_cache *cachep);
+void filestr_copy_cache(struct filestr_base *stream,
+ struct filestr_cache *cachep);
+void filestr_discard_cache(struct filestr_base *stream);
+
+/* allocates a cache buffer if needed and returns the cache pointer */
+static inline struct filestr_cache *
+filestr_get_cache(struct filestr_base *stream)
+{
+ struct filestr_cache *cachep = stream->cachep;
+
+ if (!cachep->buffer)
+ filestr_alloc_cache(stream);
+
+ return cachep;
+}
+
+static inline void filestr_lock(struct filestr_base *stream)
+{
+ mutex_lock(stream->mtx);
+}
+
+static inline void filestr_unlock(struct filestr_base *stream)
+{
+ mutex_unlock(stream->mtx);
+}
+
+/* stream lock doesn't have to be used if getting RW lock writer access */
+#define FILESTR_WRITER 0
+#define FILESTR_READER 1
+
+#define FILESTR_LOCK(type, stream) \
+ ({ if (FILESTR_##type) filestr_lock(stream); })
+
+#define FILESTR_UNLOCK(type, stream) \
+ ({ if (FILESTR_##type) filestr_unlock(stream); })
+
+#define ATTR_PREFIX (0x8000) /* out of the way of all ATTR_* bits */
+
+/* structure to return detailed information about what you opened */
+struct path_component_info
+{
+ const char *name; /* pointer to name within 'path' */
+ size_t length; /* length of component within 'path' */
+ file_size_t filesize; /* size of the opened file (0 if dir) */
+ unsigned int attr; /* attributes of this component */
+ struct file_base_info *prefixp; /* base info to check as prefix (IN) */
+ struct file_base_info parentinfo; /* parent directory info of file */
+};
+
+int open_stream_internal(const char *path, unsigned int callflags,
+ struct filestr_base *stream,
+ struct path_component_info *compinfo);
+int close_stream_internal(struct filestr_base *stream);
+int create_stream_internal(struct file_base_info *parentinfop,
+ const char *basename, size_t length,
+ unsigned int attr, unsigned int callflags,
+ struct filestr_base *stream);
+int remove_stream_internal(const char *path, struct filestr_base *stream,
+ unsigned int callflags);
+int test_stream_exists_internal(const char *path, unsigned int callflags);
+
+int open_noiso_internal(const char *path, int oflag); /* file.c */
+
+struct dirent;
+int uncached_readdir_dirent(struct filestr_base *stream,
+ struct dirscan_info *scanp,
+ struct dirent *entry);
+void uncached_rewinddir_dirent(struct dirscan_info *scanp);
+
+int uncached_readdir_internal(struct filestr_base *stream,
+ struct file_base_info *infop,
+ struct fat_direntry *fatent);
+void uncached_rewinddir_internal(struct file_base_info *infop);
+
+int test_dir_empty_internal(struct filestr_base *stream);
+
+struct dirinfo_internal
+{
+ unsigned int attr;
+ file_size_t size;
+ uint16_t wrtdate;
+ uint16_t wrttime;
+};
+
+/** Synchronization used throughout **/
+
+/* acquire the filesystem lock as READER */
+static inline void file_internal_lock_READER(void)
+{
+ extern struct mrsw_lock file_internal_mrsw;
+ mrsw_read_acquire(&file_internal_mrsw);
+}
+
+/* release the filesystem lock as READER */
+static inline void file_internal_unlock_READER(void)
+{
+ extern struct mrsw_lock file_internal_mrsw;
+ mrsw_read_release(&file_internal_mrsw);
+}
+
+/* acquire the filesystem lock as WRITER */
+static inline void file_internal_lock_WRITER(void)
+{
+ extern struct mrsw_lock file_internal_mrsw;
+ mrsw_write_acquire(&file_internal_mrsw);
+}
+
+/* release the filesystem lock as WRITER */
+static inline void file_internal_unlock_WRITER(void)
+{
+ extern struct mrsw_lock file_internal_mrsw;
+ mrsw_write_release(&file_internal_mrsw);
+}
+
+#define ERRNO 0 /* maintain errno value */
+#define RC 0 /* maintain rc value */
+
+/* NOTES: if _errno is a non-constant expression, it must set an error
+ * number and not return the ERRNO constant which will merely set
+ * errno to zero, not preserve the current value; if you must set
+ * errno to zero, set it explicitly, not in the macro
+ *
+ * if _rc is constant-expression evaluation to 'RC', then rc will
+ * NOT be altered; i.e. if you must set rc to zero, set it explicitly,
+ * not in the macro
+ */
+
+/* set errno and rc and proceed to the "file_error:" label */
+#define FILE_ERROR(_errno, _rc) \
+ ({ __builtin_constant_p(_errno) ? \
+ ({ if ((_errno) != ERRNO) errno = (_errno); }) : \
+ ({ errno = (_errno); }); \
+ __builtin_constant_p(_rc) ? \
+ ({ if ((_rc) != RC) rc = (_rc); }) : \
+ ({ rc = (_rc); }); \
+ goto file_error; })
+
+/* set errno and return a value at the point of invocation */
+#define FILE_ERROR_RETURN(_errno, _rc...) \
+ ({ __builtin_constant_p(_errno) ? \
+ ({ if ((_errno) != ERRNO) errno = (_errno); }) : \
+ ({ errno = (_errno); }); \
+ return _rc; })
+
+
+/** Misc. stuff **/
+
+/* iterate through all the volumes if volume < 0, else just the given volume */
+#define FOR_EACH_VOLUME(volume, i) \
+ for (int i = (IF_MV_VOL(volume) >= 0 ? IF_MV_VOL(volume) : 0), \
+ _end = (IF_MV_VOL(volume) >= 0 ? i : NUM_VOLUMES-1); \
+ i <= _end; i++)
+
+/* return a pointer to the static struct fat_direntry */
+static inline struct fat_direntry *get_dir_fatent(void)
+{
+ extern struct fat_direntry dir_fatent;
+ return &dir_fatent;
+}
+
+void iso_decode_d_name(char *d_name);
+
+#ifdef HAVE_DIRCACHE
+void empty_dirent(struct dirent *entry);
+void fill_dirinfo_native(struct dirinfo_native *din);
+#endif /* HAVE_DIRCACHE */
+
+void filesystem_init(void) INIT_ATTR;
+
+#endif /* _FILE_INTERNAL_H_ */
diff --git a/firmware/include/fileobj_mgr.h b/firmware/include/fileobj_mgr.h
new file mode 100644
index 0000000000..c90a59bea0
--- /dev/null
+++ b/firmware/include/fileobj_mgr.h
@@ -0,0 +1,56 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _FILEOBJ_MGR_H_
+#define _FILEOBJ_MGR_H_
+
+#include "file_internal.h"
+
+void file_binding_insert_first(struct file_base_binding *bindp);
+void file_binding_insert_last(struct file_base_binding *bindp);
+void file_binding_remove(struct file_base_binding *bindp);
+void file_binding_remove_next(struct file_base_binding *prevp,
+ struct file_base_binding *bindp);
+
+void fileobj_fileop_open(struct filestr_base *stream,
+ const struct file_base_info *srcinfop,
+ unsigned int callflags);
+void fileobj_fileop_close(struct filestr_base *stream);
+void fileobj_fileop_create(struct filestr_base *stream,
+ const struct file_base_info *srcinfop,
+ unsigned int callflags);
+void fileobj_fileop_rename(struct filestr_base *stream,
+ const struct file_base_info *oldinfop);
+void fileobj_fileop_remove(struct filestr_base *stream,
+ const struct file_base_info *oldinfop);
+void fileobj_fileop_sync(struct filestr_base *stream);
+void fileobj_fileop_truncate(struct filestr_base *stream);
+extern void ftruncate_internal_callback(struct filestr_base *stream,
+ struct filestr_base *s);
+
+file_size_t * fileobj_get_sizep(const struct filestr_base *stream);
+unsigned int fileobj_get_flags(const struct filestr_base *stream);
+void fileobj_change_flags(struct filestr_base *stream,
+ unsigned int flags, unsigned int mask);
+void fileobj_mgr_unmount(IF_MV_NONVOID(int volume));
+
+void fileobj_mgr_init(void) INIT_ATTR;
+
+#endif /* _FILEOBJ_MGR_H_ */
diff --git a/firmware/include/filesystem-native.h b/firmware/include/filesystem-native.h
new file mode 100644
index 0000000000..640e179890
--- /dev/null
+++ b/firmware/include/filesystem-native.h
@@ -0,0 +1,107 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Björn Stenberg
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _FILESYSTEM_NATIVE_H_
+#define _FILESYSTEM_NATIVE_H_
+
+#if defined(PLUGIN) || defined(CODEC)
+#define FILEFUNCTIONS_DECLARED
+#define FILEFUNCTIONS_DEFINED
+#define DIRFUNCTIONS_DECLARED
+#define DIRFUNCTIONS_DEFINED
+#endif /* PLUGIN || CODEC */
+
+#define FS_PREFIX(_x_) _x_
+#endif /* _FILESYSTEM_NATIVE_H_ */
+
+#ifdef _FILE_H_
+#ifndef _FILESYSTEM_NATIVE__FILE_H_
+#define _FILESYSTEM_NATIVE__FILE_H_
+
+#ifdef RB_FILESYSTEM_OS
+#define FILEFUNCTIONS_DEFINED
+#endif
+
+#ifndef FILEFUNCTIONS_DECLARED
+#define __OPEN_MODE_ARG
+#define __CREAT_MODE_ARG
+
+int open(const char *name, int oflag);
+int creat(const char *name);
+int close(int fildes);
+int ftruncate(int fildes, off_t length);
+int fsync(int fildes);
+off_t lseek(int fildes, off_t offset, int whence);
+ssize_t read(int fildes, void *buf, size_t nbyte);
+ssize_t write(int fildes, const void *buf, size_t nbyte);
+int remove(const char *path);
+int rename(const char *old, const char *new);
+off_t filesize(int fildes);
+int fsamefile(int fildes1, int fildes2);
+int relate(const char *path1, const char *path2);
+bool file_exists(const char *path);
+#endif /* !FILEFUNCTIONS_DECLARED */
+
+#if !defined(RB_FILESYSTEM_OS) && !defined (FILEFUNCTIONS_DEFINED)
+#define open(path, oflag, ...) open(path, oflag)
+#define creat(path, mode) creat(path)
+#endif /* FILEFUNCTIONS_DEFINED */
+
+#endif /* _FILESYSTEM_NATIVE__FILE_H_ */
+#endif /* _FILE_H_ */
+
+#ifdef _DIR_H_
+#ifndef _FILESYSTEM_NATIVE__DIR_H_
+#define _FILESYSTEM_NATIVE__DIR_H_
+
+#define DIRENT dirent
+struct dirent;
+
+struct dirinfo_native
+{
+ unsigned int attr;
+ off_t size;
+ uint16_t wrtdate;
+ uint16_t wrttime;
+};
+
+typedef struct {} DIR;
+
+#ifndef DIRFUNCTIONS_DECLARED
+#define __MKDIR_MODE_ARG
+
+#ifdef RB_FILESYSTEM_OS
+#define DIRFUNCTIONS_DEFINED
+#endif
+
+DIR * opendir(const char *dirname);
+struct dirent * readdir(DIR *dirp);
+int readdir_r(DIR *dirp, struct dirent *entry,
+ struct dirent **result);
+void rewinddir(DIR *dirp);
+int closedir(DIR *dirp);
+int mkdir(const char *path);
+int rmdir(const char *path);
+int samedir(DIR *dirp1, DIR *dirp2);
+bool dir_exists(const char *dirname);
+#endif /* !DIRFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_NATIVE__DIR_H_ */
+#endif /* _DIR_H_ */
diff --git a/firmware/include/fs_attr.h b/firmware/include/fs_attr.h
new file mode 100644
index 0000000000..0e1d25e1de
--- /dev/null
+++ b/firmware/include/fs_attr.h
@@ -0,0 +1,39 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 by Kévin Ferrare
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _FS_ATTR_H_
+#define _FS_ATTR_H_
+
+#include <stdbool.h>
+
+#undef MAX_PATH /* this avoids problems when building simulator */
+#define MAX_PATH 260
+
+/* also used by fat.c so values must not change */
+#define ATTR_READ_ONLY 0x01
+#define ATTR_HIDDEN 0x02
+#define ATTR_SYSTEM 0x04
+#define ATTR_VOLUME_ID 0x08
+#define ATTR_DIRECTORY 0x10
+#define ATTR_ARCHIVE 0x20
+#define ATTR_VOLUME 0x40 /* this is a volume, not a real directory */
+#define ATTR_LINK 0x80
+
+#endif /* _FS_ATTR_H_ */
diff --git a/firmware/include/rb-loader.h b/firmware/include/rb-loader.h
index e1e756dba9..86c5026af9 100644
--- a/firmware/include/rb-loader.h
+++ b/firmware/include/rb-loader.h
@@ -18,5 +18,4 @@
*
****************************************************************************/
-const char *rb_strerror(int8_t errno);
int load_firmware(unsigned char* buf, const char* firmware, int buffer_size);
diff --git a/firmware/include/rbunicode.h b/firmware/include/rbunicode.h
index 3c28b3031e..077029304d 100644
--- a/firmware/include/rbunicode.h
+++ b/firmware/include/rbunicode.h
@@ -51,7 +51,8 @@ enum codepages {
KSX_1001, /* Korean */
BIG_5, /* Trad. Chinese */
UTF_8, /* Unicode */
- NUM_CODEPAGES
+ NUM_CODEPAGES,
+ INIT_CODEPAGE = ISO_8859_1,
};
#else /* !HAVE_LCD_BITMAP, reduced support */
@@ -65,7 +66,8 @@ enum codepages {
WIN_1250, /* Central European */
WIN_1252, /* Western European */
UTF_8, /* Unicode */
- NUM_CODEPAGES
+ NUM_CODEPAGES,
+ INIT_CODEPAGE = ISO_8859_1,
};
#endif
@@ -78,9 +80,19 @@ unsigned char* utf16BEdecode(const unsigned char *utf16, unsigned char *utf8, in
unsigned long utf8length(const unsigned char *utf8);
const unsigned char* utf8decode(const unsigned char *utf8, unsigned short *ucs);
void set_codepage(int cp);
+int get_codepage(void);
int utf8seek(const unsigned char* utf8, int offset);
const char* get_codepage_name(int cp);
-#if defined(APPLICATION) && defined(__linux__)
+#ifdef APPLICATION
+#if defined(__linux__)
const char *get_current_codepage_name_linux(void);
#endif
+#endif /* APPLICATION */
+
+#if 0 /* not needed just now */
+void unicode_init(void);
+#else
+#define unicode_init() do {} while (0)
+#endif
+
#endif /* _RBUNICODE_H_ */
diff --git a/firmware/kernel/include/mutex.h b/firmware/kernel/include/mutex.h
index b74bfe23f5..4778eb7f11 100644
--- a/firmware/kernel/include/mutex.h
+++ b/firmware/kernel/include/mutex.h
@@ -31,21 +31,12 @@ struct mutex
struct blocker blocker; /* priority inheritance info
for waiters and owner*/
IF_COP( struct corelock cl; ) /* multiprocessor sync */
-#ifdef HAVE_PRIORITY_SCHEDULING
- bool no_preempt;
-#endif
};
extern void mutex_init(struct mutex *m);
extern void mutex_lock(struct mutex *m);
extern void mutex_unlock(struct mutex *m);
-#ifdef HAVE_PRIORITY_SCHEDULING
-/* Deprecated temporary function to disable mutex preempting a thread on
- * unlock - firmware/drivers/fat.c and a couple places in apps/buffering.c -
- * reliance on it is a bug! */
-static inline void mutex_set_preempt(struct mutex *m, bool preempt)
- { m->no_preempt = !preempt; }
-#else
+#ifndef HAVE_PRIORITY_SCHEDULING
/* Deprecated but needed for now - firmware/drivers/ata_mmc.c */
static inline bool mutex_test(const struct mutex *m)
{ return m->blocker.thread != NULL; }
diff --git a/firmware/kernel/mutex.c b/firmware/kernel/mutex.c
index 876b704b51..cb9e6816b8 100644
--- a/firmware/kernel/mutex.c
+++ b/firmware/kernel/mutex.c
@@ -33,9 +33,6 @@ void mutex_init(struct mutex *m)
wait_queue_init(&m->queue);
m->recursion = 0;
blocker_init(&m->blocker);
-#ifdef HAVE_PRIORITY_SCHEDULING
- m->no_preempt = false;
-#endif
corelock_init(&m->cl);
}
@@ -115,7 +112,7 @@ void mutex_unlock(struct mutex *m)
corelock_unlock(&m->cl);
#ifdef HAVE_PRIORITY_SCHEDULING
- if((result & THREAD_SWITCH) && !m->no_preempt)
+ if(result & THREAD_SWITCH)
switch_thread();
#endif
(void)result;
diff --git a/firmware/libc/include/errno.h b/firmware/libc/include/errno.h
index 9df261db9f..2ddf036c92 100644
--- a/firmware/libc/include/errno.h
+++ b/firmware/libc/include/errno.h
@@ -88,6 +88,7 @@ extern int * __errno(void);
#define ELBIN 75 /* Inode is remote (not really error) */
#define EDOTDOT 76 /* Cross mount point (not really error) */
#define EBADMSG 77 /* Trying to read unreadable message */
+#define EOVERFLOW 78 /* Value too large to be stored in data type */
#define ENOTUNIQ 80 /* Given log. name not unique */
#define EBADFD 81 /* f.d. invalid for this operation */
#define EREMCHG 82 /* Remote address changed */
diff --git a/firmware/libc/include/fcntl.h b/firmware/libc/include/fcntl.h
index 34740c9ca2..ec53d728cf 100644
--- a/firmware/libc/include/fcntl.h
+++ b/firmware/libc/include/fcntl.h
@@ -23,18 +23,20 @@
#define __FCNTL_H__
#ifndef O_RDONLY
-#define O_RDONLY 0
-#define O_WRONLY 1
-#define O_RDWR 2
-#define O_CREAT 4
-#define O_APPEND 8
-#define O_TRUNC 0x10
+#define O_RDONLY 0x0000 /* open for reading only */
+#define O_WRONLY 0x0001 /* open for writing only */
+#define O_RDWR 0x0002 /* open for reading and writing */
+#define O_ACCMODE 0x0003 /* mask for above modes */
+#define O_APPEND 0x0008 /* set append mode */
+#define O_CREAT 0x0200 /* create if nonexistent */
+#define O_TRUNC 0x0400 /* truncate to zero length */
+#define O_EXCL 0x0800 /* error if already exists */
#endif
#ifndef SEEK_SET
-#define SEEK_SET 0
-#define SEEK_CUR 1
-#define SEEK_END 2
+#define SEEK_SET 0 /* set file offset to offset */
+#define SEEK_CUR 1 /* set file offset to current plus offset */
+#define SEEK_END 2 /* set file offset to EOF plus offset */
#endif
#endif /* __FCNTL_H__ */
diff --git a/firmware/libc/mktime.c b/firmware/libc/mktime.c
index a52381ede5..eaa017122a 100644
--- a/firmware/libc/mktime.c
+++ b/firmware/libc/mktime.c
@@ -23,7 +23,6 @@
#include <time.h>
#include "config.h"
-#if CONFIG_RTC
/* mktime() code taken from lynx-2.8.5 source, written
by Philippe De Muyter <phdm@macqel.be> */
time_t mktime(struct tm *t)
@@ -58,4 +57,3 @@ time_t mktime(struct tm *t)
result += t->tm_sec;
return(result);
}
-#endif
diff --git a/firmware/storage.c b/firmware/storage.c
index 8740e4ebd2..7ef7428854 100644
--- a/firmware/storage.c
+++ b/firmware/storage.c
@@ -190,6 +190,15 @@ int storage_num_drives(void)
return num_drives;
}
+int storage_driver_type(int drive)
+{
+ if (drive >= num_drives)
+ return -1;
+
+ unsigned int bit = (storage_drivers[drive] & DRIVER_MASK)>>DRIVER_OFFSET;
+ return bit ? find_first_set_bit(bit) : -1;
+}
+
int storage_init(void)
{
int rc=0;
diff --git a/firmware/target/arm/as3525/sd-as3525.c b/firmware/target/arm/as3525/sd-as3525.c
index c80c7f7491..ead40eac3c 100644
--- a/firmware/target/arm/as3525/sd-as3525.c
+++ b/firmware/target/arm/as3525/sd-as3525.c
@@ -449,21 +449,12 @@ static void sd_thread(void)
{
#ifdef HAVE_HOTSWAP
case SYS_HOTSWAP_INSERTED:
- case SYS_HOTSWAP_EXTRACTED:
- {
- int microsd_init = 1;
- fat_lock(); /* lock-out FAT activity first -
- prevent deadlocking via disk_mount that
- would cause a reverse-order attempt with
- another thread */
- mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
- into driver that bypass the fat cache */
+ case SYS_HOTSWAP_EXTRACTED:;
+ int success = 1;
- /* We now have exclusive control of fat cache and ata */
+ disk_unmount(SD_SLOT_AS3525); /* release "by force" */
- disk_unmount(SD_SLOT_AS3525); /* release "by force", ensure file
- descriptors aren't leaked and any busy
- ones are invalid if mounting */
+ mutex_lock(&sd_mtx); /* lock-out card activity */
/* Force card init for new card, re-init for re-inserted one or
* clear if the last attempt to init failed with an error. */
@@ -471,29 +462,32 @@ static void sd_thread(void)
if (ev.id == SYS_HOTSWAP_INSERTED)
{
+ success = 0;
sd_enable(true);
init_pl180_controller(SD_SLOT_AS3525);
- microsd_init = sd_init_card(SD_SLOT_AS3525);
- if (microsd_init < 0) /* initialisation failed */
- panicf("microSD init failed : %d", microsd_init);
-
- microsd_init = disk_mount(SD_SLOT_AS3525); /* 0 if fail */
+ int rc = sd_init_card(SD_SLOT_AS3525);
+ sd_enable(false);
+ if (rc >= 0)
+ success = 2;
+ else /* initialisation failed */
+ panicf("microSD init failed : %d", rc);
}
+ mutex_unlock(&sd_mtx);
+
+ if (success > 1)
+ success = disk_mount(SD_SLOT_AS3525); /* 0 if fail */
+
/*
* Mount succeeded, or this was an EXTRACTED event,
* in both cases notify the system about the changed filesystems
*/
- if (microsd_init)
+ if (success)
queue_broadcast(SYS_FS_CHANGED, 0);
- /* Access is now safe */
- mutex_unlock(&sd_mtx);
- fat_unlock();
- sd_enable(false);
- }
break;
-#endif
+#endif /* HAVE_HOTSWAP */
+
case SYS_TIMEOUT:
if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
{
diff --git a/firmware/target/arm/as3525/sd-as3525v2.c b/firmware/target/arm/as3525/sd-as3525v2.c
index ae3dde4495..b4ac40152b 100644
--- a/firmware/target/arm/as3525/sd-as3525v2.c
+++ b/firmware/target/arm/as3525/sd-as3525v2.c
@@ -598,21 +598,13 @@ static void sd_thread(void)
{
#ifdef HAVE_HOTSWAP
case SYS_HOTSWAP_INSERTED:
- case SYS_HOTSWAP_EXTRACTED:
- {
- int changed = 1;
- fat_lock(); /* lock-out FAT activity first -
- prevent deadlocking via disk_mount that
- would cause a reverse-order attempt with
- another thread */
- mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
- into driver that bypass the fat cache */
-
- /* We now have exclusive control of fat cache and ata */
-
- disk_unmount(SD_SLOT_AS3525); /* release "by force", ensure file
- descriptors aren't leaked and any busy
- ones are invalid if mounting */
+ case SYS_HOTSWAP_EXTRACTED:;
+ int success = 1;
+
+ disk_unmount(SD_SLOT_AS3525); /* release "by force" */
+
+ mutex_lock(&sd_mtx); /* lock-out card activity */
+
/* Force card init for new card, re-init for re-inserted one or
* clear if the last attempt to init failed with an error. */
card_info[SD_SLOT_AS3525].initialized = 0;
@@ -620,24 +612,25 @@ static void sd_thread(void)
if (ev.id == SYS_HOTSWAP_INSERTED)
{
sd_enable(true);
- changed = (sd_init_card(SD_SLOT_AS3525) == 0) && disk_mount(SD_SLOT_AS3525); /* 0 if fail */
+ success = sd_init_card(SD_SLOT_AS3525) == 0 ? 2 : 0;
+ sd_enable(false);
}
+ mutex_unlock(&sd_mtx);
+
+ if (success > 1)
+ success = disk_mount(SD_SLOT_AS3525); /* 0 if fail */
+
/*
* Mount succeeded, or this was an EXTRACTED event,
* in both cases notify the system about the changed filesystems
*/
- if (changed)
+ if (success)
queue_broadcast(SYS_FS_CHANGED, 0);
- sd_enable(false);
-
- /* Access is now safe */
- mutex_unlock(&sd_mtx);
- fat_unlock();
- }
break;
-#endif
+#endif /* HAVE_HOTSWAP */
+
case SYS_TIMEOUT:
if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
{
diff --git a/firmware/target/arm/imx233/sdmmc-imx233.c b/firmware/target/arm/imx233/sdmmc-imx233.c
index 8f293543ab..87548aef53 100644
--- a/firmware/target/arm/imx233/sdmmc-imx233.c
+++ b/firmware/target/arm/imx233/sdmmc-imx233.c
@@ -766,14 +766,7 @@ static void sdmmc_thread(void)
case SYS_HOTSWAP_INSERTED:
case SYS_HOTSWAP_EXTRACTED:
{
- int microsd_init = 1;
- /* lock-out FAT activity first -
- * prevent deadlocking via disk_mount that
- * would cause a reverse-order attempt with
- * another thread */
-#ifdef HAVE_HOTSWAP
- fat_lock();
-#endif
+ int microsd_init = ev.id == SYS_HOTSWAP_INSERTED ? 0 : 1;
/* We now have exclusive control of fat cache and sd.
* Release "by force", ensure file
@@ -785,35 +778,37 @@ static void sdmmc_thread(void)
/* Skip non-removable drivers */
if(!sdmmc_removable(drive))
continue;
- /* lock-out card activity - direct calls
- * into driver that bypass the fat cache */
- mutex_lock(&mutex[drive]);
+
disk_unmount(sd_first_drive + sd_drive);
+
+ mutex_lock(&mutex[drive]); /* lock-out card activity */
+
/* Force card init for new card, re-init for re-inserted one or
* clear if the last attempt to init failed with an error. */
SDMMC_INFO(sd_map[sd_drive]).initialized = 0;
+ int rc = -1;
if(ev.id == SYS_HOTSWAP_INSERTED)
{
- microsd_init = init_drive(drive);
- if(microsd_init < 0) /* initialisation failed */
- panicf("%s init failed : %d", SDMMC_CONF(sd_map[sd_drive]).name, microsd_init);
-
- microsd_init = disk_mount(sd_first_drive + sd_drive); /* 0 if fail */
+ rc = init_drive(drive);
+ if(rc < 0) /* initialisation failed */
+ panicf("%s init failed : %d", SDMMC_CONF(sd_map[sd_drive]).name, rc);
}
- /*
- * Mount succeeded, or this was an EXTRACTED event,
- * in both cases notify the system about the changed filesystems
- */
- if(microsd_init)
- queue_broadcast(SYS_FS_CHANGED, 0);
+
/* unlock card */
mutex_unlock(&mutex[drive]);
+
+ if (rc >= 0)
+ microsd_init += disk_mount(sd_first_drive + sd_drive); /* 0 if fail */
}
/* Access is now safe */
-#ifdef HAVE_HOTSWAP
- fat_unlock();
-#endif
+ /*
+ * One or more mounts succeeded, or this was an EXTRACTED event,
+ * in both cases notify the system about the changed filesystems
+ */
+ if(microsd_init)
+ queue_broadcast(SYS_FS_CHANGED, 0);
+
break;
}
#endif
diff --git a/firmware/target/arm/pp/ata-sd-pp.c b/firmware/target/arm/pp/ata-sd-pp.c
index bcf8a660c2..2a11b40fee 100644
--- a/firmware/target/arm/pp/ata-sd-pp.c
+++ b/firmware/target/arm/pp/ata-sd-pp.c
@@ -19,7 +19,6 @@
*
****************************************************************************/
#include "config.h" /* for HAVE_MULTIDRIVE */
-#include "fat.h"
#include "sdmmc.h"
#include "gcc_extensions.h"
#ifdef HAVE_HOTSWAP
@@ -1125,35 +1124,28 @@ static void sd_thread(void)
{
#ifdef HAVE_HOTSWAP
case SYS_HOTSWAP_INSERTED:
- case SYS_HOTSWAP_EXTRACTED:
- fat_lock(); /* lock-out FAT activity first -
- prevent deadlocking via disk_mount that
- would cause a reverse-order attempt with
- another thread */
- mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
- into driver that bypass the fat cache */
+ case SYS_HOTSWAP_EXTRACTED:;
+ int success = 1;
- /* We now have exclusive control of fat cache and ata */
+ disk_unmount(sd_first_drive+1); /* release "by force" */
- disk_unmount(sd_first_drive+1); /* release "by force", ensure file
- descriptors aren't leaked and any busy
- ones are invalid if mounting */
+ mutex_lock(&sd_mtx); /* lock-out card activity */
/* Force card init for new card, re-init for re-inserted one or
* clear if the last attempt to init failed with an error. */
card_info[1].initialized = 0;
sd_status[1].retry = 0;
- if (ev.id == SYS_HOTSWAP_INSERTED)
- disk_mount(sd_first_drive+1);
-
- queue_broadcast(SYS_FS_CHANGED, 0);
-
/* Access is now safe */
mutex_unlock(&sd_mtx);
- fat_unlock();
+
+ if (ev.id == SYS_HOTSWAP_INSERTED)
+ success = disk_mount(sd_first_drive+1); /* 0 if fail */
+
+ if (success)
+ queue_broadcast(SYS_FS_CHANGED, 0);
break;
-#endif
+#endif /* HAVE_HOTSWAP */
case SYS_TIMEOUT:
if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
{
diff --git a/firmware/target/arm/rk27xx/sd-rk27xx.c b/firmware/target/arm/rk27xx/sd-rk27xx.c
index deca8a1fa7..9d6821ee38 100644
--- a/firmware/target/arm/rk27xx/sd-rk27xx.c
+++ b/firmware/target/arm/rk27xx/sd-rk27xx.c
@@ -22,7 +22,6 @@
****************************************************************************/
#include "config.h" /* for HAVE_MULTIVOLUME */
-#include "fat.h"
#include "thread.h"
#include "gcc_extensions.h"
#include "led.h"
@@ -331,50 +330,45 @@ static void sd_thread(void)
{
#ifdef HAVE_HOTSWAP
case SYS_HOTSWAP_INSERTED:
- case SYS_HOTSWAP_EXTRACTED:
- {
- int microsd_init = 1;
- fat_lock(); /* lock-out FAT activity first -
- prevent deadlocking via disk_mount that
- would cause a reverse-order attempt with
- another thread */
- mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
- into driver that bypass the fat cache */
-
- /* We now have exclusive control of fat cache and ata */
-
- disk_unmount(sd_first_drive); /* release "by force", ensure file
- descriptors aren't leaked and any busy
- ones are invalid if mounting */
+ case SYS_HOTSWAP_EXTRACTED:;
+ int success = 1;
+
+ disk_unmount(sd_first_drive); /* release "by force" */
+
+ mutex_lock(&sd_mtx); /* lock-out card activity */
+
/* Force card init for new card, re-init for re-inserted one or
* clear if the last attempt to init failed with an error. */
card_info.initialized = 0;
if (ev.id == SYS_HOTSWAP_INSERTED)
{
+ success = 0;
sd_enable(true);
- microsd_init = sd_init_card(sd_first_drive);
- if (microsd_init < 0) /* initialisation failed */
- panicf("microSD init failed : %d", microsd_init);
-
- microsd_init = disk_mount(sd_first_drive); /* 0 if fail */
+ int rc = sd_init_card(sd_first_drive);
+ sd_enable(false);
+ if (rc >= 0)
+ success = 2;
+ else /* initialisation failed */
+ panicf("microSD init failed : %d", rc);
}
+ /* Access is now safe */
+ mutex_unlock(&sd_mtx);
+
+ if (success > 1)
+ success = disk_mount(sd_first_drive); /* 0 if fail */
+
/*
* Mount succeeded, or this was an EXTRACTED event,
* in both cases notify the system about the changed filesystems
*/
- if (microsd_init)
+ if (success)
queue_broadcast(SYS_FS_CHANGED, 0);
- sd_enable(false);
-
- /* Access is now safe */
- mutex_unlock(&sd_mtx);
- fat_unlock();
- }
break;
-#endif
+#endif /* HAVE_HOTSWAP */
+
case SYS_TIMEOUT:
if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
{
diff --git a/firmware/target/arm/s3c2440/sd-s3c2440.c b/firmware/target/arm/s3c2440/sd-s3c2440.c
index 6658fa1515..e8de3ac78d 100644
--- a/firmware/target/arm/s3c2440/sd-s3c2440.c
+++ b/firmware/target/arm/s3c2440/sd-s3c2440.c
@@ -34,7 +34,6 @@
#ifdef HAVE_HOTSWAP
#include "sdmmc.h"
#include "disk.h"
-#include "fat.h"
#endif
#include "dma-target.h"
#include "system-target.h"
@@ -585,48 +584,29 @@ static void sd_thread(void)
{
#ifdef HAVE_HOTSWAP
case SYS_HOTSWAP_INSERTED:
- case SYS_HOTSWAP_EXTRACTED:
- {
+ case SYS_HOTSWAP_EXTRACTED:;
int success = 1;
- fat_lock(); /* lock-out FAT activity first -
- prevent deadlocking via disk_mount that
- would cause a reverse-order attempt with
- another thread */
- mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
- into driver that bypass the fat cache */
- /* We now have exclusive control of fat cache and ata */
+ disk_unmount(0); /* release "by force" */
- disk_unmount(0); /* release "by force", ensure file
- descriptors aren't leaked and any busy
- ones are invalid if mounting */
+ mutex_lock(&sd_mtx); /* lock-out card activity */
/* Force card init for new card, re-init for re-inserted one or
* clear if the last attempt to init failed with an error. */
card_info[0].initialized = 0;
+ /* Access is now safe */
+ mutex_unlock(&sd_mtx);
+
if (ev.id == SYS_HOTSWAP_INSERTED)
- {
- /* FIXME: once sd_enabled is implement properly,
- * reinitializing the controllers might be needed */
- sd_enable(true);
- if (success < 0) /* initialisation failed */
- panicf("SD init failed : %d", success);
success = disk_mount(0); /* 0 if fail */
- }
/* notify the system about the changed filesystems
*/
if (success)
queue_broadcast(SYS_FS_CHANGED, 0);
-
- /* Access is now safe */
- mutex_unlock(&sd_mtx);
- fat_unlock();
- sd_enable(false);
- }
break;
-#endif
+#endif /* HAVE_HOTSWAP */
}
}
}
diff --git a/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c b/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c
index 395c0f49e6..0fd74787d1 100644
--- a/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c
+++ b/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c
@@ -32,7 +32,7 @@
#include "s5l8702.h"
#include "led.h"
#include "ata_idle_notify.h"
-#include "fat.h"
+#include "disk_cache.h"
#include "splash.h"
@@ -68,6 +68,7 @@ static struct semaphore mmc_wakeup;
static struct semaphore mmc_comp_wakeup;
static int spinup_time = 0;
static int dma_mode = 0;
+static char aligned_buffer[SECTOR_SIZE] __attribute__((aligned(0x10)));
#ifdef ATA_HAVE_BBT
@@ -857,8 +858,25 @@ int ata_bbt_translate(uint64_t sector, uint32_t count, uint64_t* phys, uint32_t*
static int ata_rw_sectors(uint64_t sector, uint32_t count, void* buffer, bool write)
{
if (((uint32_t)buffer) & 0xf)
- panicf("ATA: Misaligned data buffer at %08X (sector %lu, count %lu)",
- (unsigned int)buffer, (long unsigned int)sector, (long unsigned int)count);
+ {
+ while (count)
+ {
+ if (write)
+ memcpy(aligned_buffer, buffer, SECTOR_SIZE);
+
+ PASS_RC(ata_rw_sectors(sector, 1, aligned_buffer, write), 0, 0);
+
+ if (!write)
+ memcpy(buffer, aligned_buffer, SECTOR_SIZE);
+
+ buffer += SECTOR_SIZE;
+ sector++;
+ count--;
+ }
+
+ return 0;
+ }
+
#ifdef ATA_HAVE_BBT
if (sector + count > ata_virtual_sectors) RET_ERR(0);
if (ata_bbt)
@@ -1117,14 +1135,13 @@ int ata_init(void)
-- Michael Sparmann (theseven), 2011-10-22 */
if (!ceata)
{
- unsigned char* sector = fat_get_sector_buffer();
+ unsigned char* sector = aligned_buffer;
ata_rw_sectors(0, 1, sector, false);
if (sector[510] == 0xaa && sector[511] == 0x55)
{
ata_swap = true;
splashf(5000, "Wrong HDD endianness, please update your emCORE version!");
}
- fat_release_sector_buffer();
}
create_thread(ata_thread, ata_stack,
diff --git a/firmware/target/arm/tcc780x/sd-tcc780x.c b/firmware/target/arm/tcc780x/sd-tcc780x.c
index 55ae4e7c70..b7abea8be4 100644
--- a/firmware/target/arm/tcc780x/sd-tcc780x.c
+++ b/firmware/target/arm/tcc780x/sd-tcc780x.c
@@ -28,7 +28,6 @@
#include "led.h"
#include "thread.h"
#include "disk.h"
-#include "fat.h"
#include "ata_idle_notify.h"
#include "usb.h"
@@ -657,35 +656,30 @@ static void sd_thread(void)
{
#ifdef HAVE_HOTSWAP
case SYS_HOTSWAP_INSERTED:
- case SYS_HOTSWAP_EXTRACTED:
- fat_lock(); /* lock-out FAT activity first -
- prevent deadlocking via disk_mount that
- would cause a reverse-order attempt with
- another thread */
- mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
- into driver that bypass the fat cache */
-
- /* We now have exclusive control of fat cache and ata */
-
- /* Release "by force", ensure file descriptors aren't leaked and
- any busy ones are invalid if mounting */
+ case SYS_HOTSWAP_EXTRACTED:;
+ int success = 1;
+
+ /* Release "by force" */
disk_unmount(sd_first_drive + CARD_NUM_SLOT);
+ mutex_lock(&sd_mtx); /* lock-out card activity */
+
/* Force card init for new card, re-init for re-inserted one or
* clear if the last attempt to init failed with an error. */
card_info[CARD_NUM_SLOT].initialized = 0;
sd_status[CARD_NUM_SLOT].retry = 0;
+ mutex_unlock(&sd_mtx);
+
if (ev.id == SYS_HOTSWAP_INSERTED)
- disk_mount(sd_first_drive + CARD_NUM_SLOT);
+ success = disk_mount(sd_first_drive + CARD_NUM_SLOT);
- queue_broadcast(SYS_FS_CHANGED, 0);
+ if (success)
+ queue_broadcast(SYS_FS_CHANGED, 0);
- /* Access is now safe */
- mutex_unlock(&sd_mtx);
- fat_unlock();
break;
-#endif
+#endif /* HAVE_HOTSWAP */
+
case SYS_TIMEOUT:
if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
{
diff --git a/firmware/target/arm/tms320dm320/sdmmc-dm320.c b/firmware/target/arm/tms320dm320/sdmmc-dm320.c
index 284061e1ad..d46dbf5e40 100644
--- a/firmware/target/arm/tms320dm320/sdmmc-dm320.c
+++ b/firmware/target/arm/tms320dm320/sdmmc-dm320.c
@@ -592,48 +592,29 @@ static void sd_thread(void)
{
#ifdef HAVE_HOTSWAP
case SYS_HOTSWAP_INSERTED:
- case SYS_HOTSWAP_EXTRACTED:
- {
+ case SYS_HOTSWAP_EXTRACTED:;
int success = 1;
- fat_lock(); /* lock-out FAT activity first -
- prevent deadlocking via disk_mount that
- would cause a reverse-order attempt with
- another thread */
- mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
- into driver that bypass the fat cache */
- /* We now have exclusive control of fat cache and ata */
+ disk_unmount(0); /* release "by force" */
- disk_unmount(0); /* release "by force", ensure file
- descriptors aren't leaked and any busy
- ones are invalid if mounting */
+ mutex_lock(&sd_mtx); /* lock-out card activity */
/* Force card init for new card, re-init for re-inserted one or
* clear if the last attempt to init failed with an error. */
card_info[0].initialized = 0;
+ mutex_unlock(&sd_mtx);
+
if (ev.id == SYS_HOTSWAP_INSERTED)
- {
- /* FIXME: once sd_enabled is implement properly,
- * reinitializing the controllers might be needed */
- sd_enable(true);
- if (success < 0) /* initialisation failed */
- panicf("SD init failed : %d", success);
success = disk_mount(0); /* 0 if fail */
- }
- /* notify the system about the changed filesystems
- */
+ /* notify the system about the changed filesystems */
if (success)
queue_broadcast(SYS_FS_CHANGED, 0);
- /* Access is now safe */
- mutex_unlock(&sd_mtx);
- fat_unlock();
- sd_enable(false);
- }
break;
-#endif
+#endif /* HAVE_HOTSWAP */
+
case SYS_TIMEOUT:
if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
{
diff --git a/firmware/target/hosted/filesystem-app.c b/firmware/target/hosted/filesystem-app.c
new file mode 100644
index 0000000000..7ef8d3109b
--- /dev/null
+++ b/firmware/target/hosted/filesystem-app.c
@@ -0,0 +1,562 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2010 Thomas Martitz
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#define RB_FILESYSTEM_OS
+#include <stdio.h> /* snprintf */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+#include "config.h"
+#include "system.h"
+#include "file.h"
+#include "dir.h"
+#include "file_internal.h"
+#include "pathfuncs.h"
+#include "string-extra.h"
+#include "rbpaths.h"
+#include "logf.h"
+
+
+#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
+static const char rbhome[] = "/sdcard";
+#elif (CONFIG_PLATFORM & (PLATFORM_SDL|PLATFORM_MAEMO|PLATFORM_PANDORA)) \
+ && !defined(__PCTOOL__)
+const char *rbhome;
+#else
+/* YPR0, YPR1 */
+static const char rbhome[] = HOME_DIR;
+#endif
+
+#if !(defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1)) && !defined(__PCTOOL__)
+/* Special dirs are user-accessible (and user-writable) dirs which take priority
+ * over the ones where Rockbox is installed to. Classic example would be
+ * $HOME/.config/rockbox.org vs /usr/share/rockbox */
+#define HAVE_SPECIAL_DIRS
+#endif
+
+#ifdef HAVE_MULTIDRIVE
+/* This is to compare any opened directories with the home directory so that
+ the special drive links may be returned for it only */
+static int rbhome_fildes = -1;
+
+/* A special link is created under e.g. HOME_DIR/<microSD1>, e.g. to access
+ * external storage in a convenient location, much similar to the mount
+ * point on our native targets. Here they are treated as symlink (one which
+ * doesn't actually exist in the filesystem and therefore we have to override
+ * readlink() */
+static const char *handle_special_links(const char* link, unsigned flags,
+ char *buf, const size_t bufsize)
+{
+ (void) flags;
+ char vol_string[VOL_MAX_LEN + 1];
+ get_volume_name(-1, vol_string);
+
+ /* link might be passed with or without HOME_DIR expanded. To handle
+ * both perform substring matching (VOL_NAMES is unique enough) */
+ const char *begin = strstr(link, vol_string);
+ if (begin)
+ {
+ /* begin now points to the start of vol_string within link,
+ * we want to copy the remainder of the paths, prefixed by
+ * the actual mount point (the remainder might be "") */
+ snprintf(buf, bufsize, MULTIDRIVE_DIR"%s", begin + len);
+ return buf;
+ }
+
+ return link;
+}
+#endif
+
+#ifdef HAVE_MULTIDRIVE
+/* we keep an open descriptor of the home directory to detect when it has been
+ opened by opendir() so that its "symlinks" may be enumerated */
+static void cleanup_rbhome(void)
+{
+ os_close(rbhome_fildes);
+ rbhome_fildes = -1;
+}
+#endif /* HAVE_MULTIDRIVE */
+
+void paths_init(void)
+{
+#ifdef HAVE_SPECIAL_DIRS
+ /* make sure $HOME/.config/rockbox.org exists, it's needed for config.cfg */
+#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
+ os_mkdir("/sdcard/rockbox" __MKDIR_MODE_ARG);
+ os_mkdir("/sdcard/rockbox/rocks.data" __MKDIR_MODE_ARG);
+#else
+ char config_dir[MAX_PATH];
+
+ const char *home = getenv("RBROOT");
+ if (!home)
+ {
+ home = getenv("HOME");
+ }
+
+ if (!home)
+ {
+ logf("HOME environment var not set. Can't write config");
+ return;
+ }
+
+ rbhome = home;
+ snprintf(config_dir, sizeof(config_dir), "%s/.config", home);
+ os_mkdir(config_dir __MKDIR_MODE_ARG);
+ snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org", home);
+ os_mkdir(config_dir __MKDIR_MODE_ARG);
+ /* Plugin data directory */
+ snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org/rocks.data", home);
+ os_mkdir(config_dir __MKDIR_MODE_ARG);
+#endif
+#endif /* HAVE_SPECIAL_DIRS */
+
+#ifdef HAVE_MULTIDRIVE
+ /* if this fails then alternate volumes will not work, but this function
+ cannot return that fact */
+ rbhome_fildes = os_opendirfd(rbhome);
+ if (rbhome_fildes >= 0)
+ atexit(cleanup_rbhome);
+#endif /* HAVE_MULTIDRIVE */
+}
+
+#ifdef HAVE_SPECIAL_DIRS
+static const char* _get_user_file_path(const char *path,
+ unsigned flags,
+ char* buf,
+ const size_t bufsize)
+{
+ const char *ret = path;
+ const char *pos = path;
+ /* replace ROCKBOX_DIR in path with $HOME/.config/rockbox.org */
+ pos += ROCKBOX_DIR_LEN;
+ if (*pos == '/') pos += 1;
+
+#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
+ if (path_append(buf, "/sdcard/rockbox", pos, bufsize) >= bufsize)
+ return NULL;
+#else
+ if (path_append(buf, rbhome, ".config/rockbox.org", bufsize) >= bufsize ||
+ path_append(buf, PA_SEP_SOFT, pos, bufsize) >= bufsize)
+ return NULL;
+#endif
+
+ /* always return the replacement buffer (pointing to $HOME) if
+ * write access is needed */
+ if (flags & NEED_WRITE)
+ ret = buf;
+ else if (os_file_exists(buf))
+ ret = buf;
+
+ if (ret != buf) /* not found in $HOME, try ROCKBOX_BASE_DIR, !NEED_WRITE only */
+ {
+ if (path_append(buf, ROCKBOX_SHARE_PATH, pos, bufsize) >= bufsize)
+ return NULL;
+
+ if (os_file_exists(buf))
+ ret = buf;
+ }
+
+ return ret;
+}
+
+#endif
+
+const char * handle_special_dirs(const char *dir, unsigned flags,
+ char *buf, const size_t bufsize)
+{
+ (void) flags; (void) buf; (void) bufsize;
+#ifdef HAVE_SPECIAL_DIRS
+ if (!strncmp(HOME_DIR, dir, HOME_DIR_LEN))
+ {
+ const char *p = dir + HOME_DIR_LEN;
+ while (*p == '/') p++;
+ snprintf(buf, bufsize, "%s/%s", rbhome, p);
+ dir = buf;
+ }
+ else if (!strncmp(ROCKBOX_DIR, dir, ROCKBOX_DIR_LEN))
+ dir = _get_user_file_path(dir, flags, buf, bufsize);
+#endif
+#ifdef HAVE_MULTIDRIVE
+ dir = handle_special_links(dir, flags, buf, bufsize);
+#endif
+ return dir;
+}
+
+int app_open(const char *path, int oflag, ...)
+{
+ int flags = IS_FILE;
+ if (oflag & O_ACCMODE)
+ flags |= NEED_WRITE;
+
+ char realpath[MAX_PATH];
+ const char *fpath = handle_special_dirs(path, flags, realpath,
+ sizeof (realpath));
+ if (!fpath)
+ FILE_ERROR_RETURN(ENAMETOOLONG, -1);
+
+ return os_open(fpath, oflag __OPEN_MODE_ARG);
+}
+
+int app_creat(const char *path, mode_t mode)
+{
+ return app_open(path, O_CREAT|O_WRONLY|O_TRUNC, mode);
+}
+
+int app_remove(const char *path)
+{
+ char realpath[MAX_PATH];
+ const char *fpath = handle_special_dirs(path, NEED_WRITE, realpath,
+ sizeof (realpath));
+ if (!fpath)
+ FILE_ERROR_RETURN(ENAMETOOLONG, -1);
+
+ return os_remove(fpath);
+}
+
+int app_rename(const char *old, const char *new)
+{
+ char realpath_old[MAX_PATH], realpath_new[MAX_PATH];
+ const char *fold = handle_special_dirs(old, NEED_WRITE, realpath_old,
+ sizeof (realpath_old));
+ const char *fnew = handle_special_dirs(new, NEED_WRITE, realpath_new,
+ sizeof (realpath_new));
+ if (!fold || !fnew)
+ FILE_ERROR_RETURN(ENAMETOOLONG, -1);
+
+ return os_rename(fold, fnew);
+}
+
+#ifdef HAVE_SDL_THREADS
+ssize_t app_read(int fd, void *buf, size_t nbyte)
+{
+ return os_read(fd, buf, nbyte);
+}
+
+ssize_t app_write(int fd, const void *buf, size_t nbyte)
+{
+ return os_write(fd, buf, nbyte);
+}
+#endif /* HAVE_SDL_THREADS */
+
+int app_relate(const char *path1, const char *path2)
+{
+ char realpath_1[MAX_PATH], realpath_2[MAX_PATH];
+ const char *fpath1 = handle_special_dirs(path1, 0, realpath_1,
+ sizeof (realpath_1));
+ const char *fpath2 = handle_special_dirs(path2, 0, realpath_2,
+ sizeof (realpath_2));
+
+ if (!fpath1 || !fpath2)
+ FILE_ERROR_RETURN(ENAMETOOLONG, -1);
+
+ return os_relate(fpath1, fpath2);
+}
+
+bool app_file_exists(const char *path)
+{
+ char realpath[MAX_PATH];
+ const char *fpath = handle_special_dirs(path, NEED_WRITE, realpath,
+ sizeof (realpath));
+ if (!fpath)
+ FILE_ERROR_RETURN(ENAMETOOLONG, false);
+
+ return os_file_exists(fpath);
+}
+
+/* need to wrap around DIR* because we need to save the parent's directory
+ * path in order to determine dirinfo for volumes or convert the path to UTF-8;
+ * also is required to implement get_dir_info() */
+struct __dir
+{
+ OS_DIR_T *osdirp;
+#ifdef HAVE_MULTIDRIVE
+ int volumes_returned;
+#endif
+ int osfd;
+ bool osfd_is_opened;
+#if defined(OS_DIRENT_CONVERT) || defined (HAVE_MULTIDRIVE)
+ #define USE_DIRENTP
+ struct dirent *direntp;
+ size_t d_name_size;
+#endif
+ char path[];
+};
+
+static void __dir_free(struct __dir *this)
+{
+ if (!this)
+ return;
+
+#ifdef USE_DIRENTP
+ free(this->direntp);
+#endif
+
+ if (this->osfd_is_opened)
+ os_close(this->osfd);
+
+ free(this);
+}
+
+DIR * app_opendir(const char *dirname)
+{
+ int rc;
+ char realpath[MAX_PATH];
+ const char *fname = handle_special_dirs(dirname, 0, realpath,
+ sizeof (realpath));
+ if (!fname)
+ FILE_ERROR_RETURN(ENAMETOOLONG, NULL);
+
+ size_t name_len = path_strip_trailing_separators(fname, &fname);
+ struct __dir *this = calloc(1, sizeof (*this) + name_len + 1);
+ if (!this)
+ FILE_ERROR(ENOMEM, RC);
+
+#ifdef USE_DIRENTP
+ /* allocate what we're really going to return to callers, making certain
+ it has at least the d_name size we want */
+ this->d_name_size = MAX(MAX_PATH, sizeof (this->direntp->d_name));
+ this->direntp = calloc(1, offsetof(typeof (*this->direntp), d_name) +
+ this->d_name_size);
+ if (!this->direntp)
+ FILE_ERROR(ENOMEM, RC);
+
+ /* only the d_name field will be valid but that is all that anyone may
+ truely count on portably existing */
+#endif /* USE_DIRENTP */
+
+ strmemcpy(this->path, fname, name_len);
+
+ rc = os_opendir_and_fd(this->path, &this->osdirp, &this->osfd);
+ if (rc < 0)
+ FILE_ERROR(ERRNO, RC);
+
+ this->osfd_is_opened = rc > 0;
+
+#ifdef HAVE_MULTIDRIVE
+ this->volumes_returned = INT_MAX; /* assume NOT $HOME */
+ if (rbhome_fildes >= 0 && os_samefile(rbhome_fildes, fd) > 0)
+ this->volumes_returned = 0; /* there's no place like $HOME */
+#endif /* HAVE_MULTIDRIVE */
+
+ return (DIR *)this;
+file_error:
+ __dir_free(this);
+ return NULL;
+}
+
+int app_closedir(DIR *dirp)
+{
+ struct __dir *this = (struct __dir *)dirp;
+ if (!this)
+ FILE_ERROR_RETURN(EBADF, -1);
+
+ OS_DIR_T *osdirp = this->osdirp;
+ __dir_free(this);
+
+ return os_closedir(osdirp);
+}
+
+struct dirent * app_readdir(DIR *dirp)
+{
+ struct __dir *this = (struct __dir *)dirp;
+ if (!this)
+ FILE_ERROR_RETURN(EBADF, NULL);
+
+#ifdef HAVE_MULTIDRIVE
+ if (this->volumes_returned < NUM_VOLUMES)
+ {
+ while (++this->volumes_returned < NUM_VOLUMES)
+ {
+ if (!volume_present(this->volumes_returned))
+ continue;
+
+ get_volume_name(this->volumes_returned, this->direntp->d_name);
+ return this->direntp;
+ }
+ }
+ /* do normal directory reads */
+#endif /* HAVE_MULTIDRIVE */
+
+ OS_DIRENT_T *osdirent = os_readdir(this->osdirp);
+
+#ifdef OS_DIRENT_CONVERT
+ if (strlcpy_from_os(this->direntp->d_name, osdirent->d_name,
+ this->d_name_size) >= this->d_name_size)
+ {
+ this->direntp->d_name[0] = '\0';
+ errno = EOVERFLOW;
+ return NULL;
+ }
+
+ osdirent = (OS_DIRENT_T *)this->direntp;
+#endif /* OS_DIRENT_CONVERT */
+
+ return (struct dirent *)osdirent;
+}
+
+int app_mkdir(const char *path)
+{
+ char realpath[MAX_PATH];
+ const char *fname = handle_special_dirs(path, NEED_WRITE, realpath,
+ sizeof (realpath));
+ if (!fname)
+ FILE_ERROR_RETURN(ENAMETOOLONG, -1);
+
+ return os_mkdir(fname __MKDIR_MODE_ARG);
+}
+
+int app_rmdir(const char *path)
+{
+ char realpath[MAX_PATH];
+ const char *fname = handle_special_dirs(path, NEED_WRITE, realpath,
+ sizeof (realpath));
+ if (!fname)
+ FILE_ERROR_RETURN(ENAMETOOLONG, -1);
+
+ return os_rmdir(fname);
+}
+
+int app_samedir(DIR *dirp1, DIR *dirp2)
+{
+ struct __dir *this1 = (struct __dir *)dirp1;
+ struct __dir *this2 = (struct __dir *)dirp2;
+
+ if (!this1 || !this2)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ return os_fsamefile(this1->osfd, this2->osfd);
+}
+
+bool app_dir_exists(const char *dirname)
+{
+ char realpath[MAX_PATH];
+ const char *fname = handle_special_dirs(dirname, 0, realpath,
+ sizeof (realpath));
+ if (!fname)
+ FILE_ERROR_RETURN(ENAMETOOLONG, false);
+
+ OS_DIR_T *osdirp = os_opendir(fname);
+ if (!osdirp)
+ return false;
+
+ os_closedir(osdirp);
+ return true;
+}
+
+struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry)
+{
+ struct __dir *this = (struct __dir *)dirp;
+ struct dirinfo ret = { .mtime = 0 };
+
+ if (!this)
+ FILE_ERROR_RETURN(EBADF, ret);
+
+ if (!entry || entry->d_name[0] == '\0')
+ FILE_ERROR_RETURN(ENOENT, ret);
+
+ char path[MAX_PATH];
+
+#ifdef HAVE_MULTIDRIVE
+ if (this->volumes_returned < NUM_VOLUMES)
+ {
+ /* last thing read was a "symlink" */
+ ret.attribute = ATTR_LINK;
+ strcpy(path, MULTIDRIVE_DIR);
+ }
+ else
+#endif
+ if (path_append(path, this->path, entry->d_name, sizeof (path))
+ >= sizeof (path))
+ {
+ FILE_ERROR_RETURN(ENAMETOOLONG, ret);
+ }
+
+ struct stat s;
+ if (os_lstat(path, &s) < 0)
+ FILE_ERROR_RETURN(ERRNO, ret);
+
+ int err = 0;
+ if (S_ISLNK(s.st_mode))
+ {
+ ret.attribute |= ATTR_LINK;
+ err = os_stat(path, &s);
+ }
+
+ if (err < 0)
+ FILE_ERROR_RETURN(ERRNO, ret);
+
+ if (S_ISDIR(s.st_mode))
+ ret.attribute |= ATTR_DIRECTORY;
+
+ ret.size = s.st_size;
+
+ struct tm tm;
+ if (!localtime_r(&s.st_mtime, &tm))
+ FILE_ERROR_RETURN(ERRNO, ret);
+
+ ret.mtime = mktime(&tm);
+ return ret;
+}
+
+/* On MD we create a virtual symlink for the external drive,
+ * for this we need to override readlink(). */
+ssize_t app_readlink(const char *path, char *buf, size_t bufsiz)
+{
+ char _buf[MAX_PATH];
+ path = handle_special_dirs(path, 0, _buf, sizeof(_buf));
+#ifdef HAVE_MULTIDRIVE
+ /* if path == _buf then we can be sure handle_special_dir() did something
+ * and path is not an ordinary directory */
+ if (path == _buf && !strncmp(path, MULTIDRIVE_DIR, sizeof(MULTIDRIVE_DIR)-1))
+ {
+ /* copying NUL is not required as per readlink specification */
+ ssize_t len = strlen(path);
+ memcpy(buf, path, len);
+ return len;
+ }
+#endif
+ /* does not append NUL !! */
+ return os_readlink(path, buf, bufsiz);
+ (void) path; (void) buf; (void) bufsiz;
+}
+
+int os_volume_path(IF_MV(int volume, ) char *buffer, size_t bufsize)
+{
+#ifdef HAVE_MULTIVOLUME
+ char volname[VOL_MAX_LEN + 1];
+ get_volume_name(volume, volname);
+#else
+ const char *volname = "/";
+#endif
+
+ if (!handle_special_dirs(volname, NEED_WRITE, buffer, bufsize))
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/firmware/target/hosted/filesystem-app.h b/firmware/target/hosted/filesystem-app.h
new file mode 100644
index 0000000000..68b3f13b6e
--- /dev/null
+++ b/firmware/target/hosted/filesystem-app.h
@@ -0,0 +1,117 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#ifndef _FILESYSTEM_APP_H_
+#define _FILESYSTEM_APP_H_
+
+#if defined(PLUGIN) || defined(CODEC)
+/* Prevent often-problematic plugin namespace pollution */
+#define FILEFUNCTIONS_DECLARED
+#define FILEFUNCTIONS_DEFINED
+#define DIRFUNCTIONS_DECLARED
+#define DIRFUNCTIONS_DEFINED
+#define OSFUNCTIONS_DECLARED
+#endif /* PLUGIN || CODEC */
+
+/* flags for get_user_file_path() */
+/* whether you need write access to that file/dir, especially true
+ * for runtime generated files (config.cfg) */
+#define NEED_WRITE (1<<0)
+/* file or directory? */
+#define IS_FILE (1<<1)
+
+#ifndef OSFUNCTIONS_DECLARED
+#define FS_PREFIX(_x_) app_ ## _x_
+
+void paths_init(void);
+const char * handle_special_dirs(const char *dir, unsigned flags,
+ char *buf, const size_t bufsize);
+
+#endif /* !OSFUNCTIONS_DECLARED */
+#endif /* _FILESYSTEM_APP_H_ */
+
+#ifdef HAVE_SDL
+#include "filesystem-sdl.h"
+#endif /* HAVE_SDL */
+#ifdef WIN32
+#include "filesystem-win32.h"
+#else /* !WIN32 */
+#include "filesystem-unix.h"
+#endif /* WIN32 */
+#include "filesystem-hosted.h"
+
+#ifdef _FILE_H_
+#ifndef _FILESYSTEM_APP__FILE_H_
+#define _FILESYSTEM_APP__FILE_H_
+
+#ifdef RB_FILESYSTEM_OS
+#define FILEFUNCTIONS_DEFINED
+#endif
+
+#ifndef FILEFUNCTIONS_DECLARED
+int app_open(const char *name, int oflag, ...);
+int app_creat(const char *name, mode_t mode);
+#define app_close os_close
+#define app_ftruncate os_ftruncate
+#define app_fsync os_fsync
+#define app_lseek os_lseek
+#ifdef HAVE_SDL_THREADS
+ssize_t app_read(int fildes, void *buf, size_t nbyte);
+ssize_t app_write(int fildes, const void *buf, size_t nbyte);
+#else
+#define app_read os_read
+#define app_write os_write
+#endif /* HAVE_SDL_THREADS */
+int app_remove(const char *path);
+int app_rename(const char *old, const char *new);
+#define app_filesize os_filesize
+#define app_fsamefile os_fsamefile
+int app_relate(const char *path1, const char *path2);
+bool app_file_exists(const char *path);
+ssize_t app_readlink(const char *path, char *buf, size_t bufsize);
+#endif /* !FILEFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_APP__FILE_H_ */
+#endif /* _FILE_H_ */
+
+#ifdef _DIR_H_
+#ifndef _FILESYSTEM_APP__DIR_H_
+#define _FILESYSTEM_APP__DIR_H_
+
+#ifdef RB_FILESYSTEM_OS
+#define DIRFUNCTIONS_DEFINED
+#endif
+
+#define DIRENT dirent
+#define DIRENT_DEFINED
+
+#ifndef DIRFUNCTIONS_DECLARED
+DIR * app_opendir(const char *dirname);
+struct dirent * app_readdir(DIR *dirp);
+int app_closedir(DIR *dirp);
+int app_mkdir(const char *path);
+int app_rmdir(const char *path);
+int app_samedir(DIR *dirp1, DIR *dirp2);
+bool app_dir_exists(const char *dirname);
+#endif /* DIRFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_APP__DIR_H_ */
+#endif /* _DIR_H_ */
diff --git a/firmware/target/hosted/filesystem-hosted.h b/firmware/target/hosted/filesystem-hosted.h
new file mode 100644
index 0000000000..3d979eb19d
--- /dev/null
+++ b/firmware/target/hosted/filesystem-hosted.h
@@ -0,0 +1,74 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _FILESYSTEM_HOSTED_H_
+#define _FILESYSTEM_HOSTED_H_
+
+#include "mv.h"
+
+int os_volume_path(IF_MV(int volume, ) char *buffer, size_t bufsize);
+void * os_lc_open(const char *ospath);
+
+#endif /* _FILESYSTEM_HOSTED_H_ */
+
+#ifdef _FILE_H_
+#ifndef _FILESYSTEM_HOSTED__FILE_H_
+#define _FILESYSTEM_HOSTED__FILE_H_
+
+#ifndef OSFUNCTIONS_DECLARED
+off_t os_filesize(int osfd);
+int os_fsamefile(int osfd1, int osfd2);
+int os_relate(const char *path1, const char *path2);
+bool os_file_exists(const char *ospath);
+
+#define __OPEN_MODE_ARG \
+ , ({ \
+ mode_t mode = 0; \
+ if (oflag & O_CREAT) \
+ { \
+ va_list ap; \
+ va_start(ap, oflag); \
+ mode = va_arg(ap, unsigned int); \
+ va_end(ap); \
+ } \
+ mode; })
+
+#define __CREAT_MODE_ARG \
+ , mode
+
+#endif /* OSFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_HOSTED__FILE_H_ */
+#endif /* _FILE_H_ */
+
+#ifdef _DIR_H_
+#ifndef _FILESYSTEM_HOSTED__DIR_H_
+#define _FILESYSTEM_HOSTED__DIR_H_
+
+#ifndef OSFUNCTIONS_DECLARED
+int os_opendir_and_fd(const char *osdirname, OS_DIR_T **osdirpp,
+ int *osfdp);
+
+#define __MKDIR_MODE_ARG \
+ , 0777
+#endif /* OSFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_HOSTED__DIR_H_ */
+#endif /* _DIR_H_ */
diff --git a/firmware/target/hosted/filesystem-unix.c b/firmware/target/hosted/filesystem-unix.c
index 8ac1d4ada9..907d6ab14e 100644
--- a/firmware/target/hosted/filesystem-unix.c
+++ b/firmware/target/hosted/filesystem-unix.c
@@ -18,24 +18,204 @@
* KIND, either express or implied.
*
****************************************************************************/
+#define RB_FILESYSTEM_OS
+#include <sys/statfs.h> /* lowest common denominator */
+#include <sys/stat.h>
+#include <string.h>
+#include <errno.h>
+#include "config.h"
+#include "system.h"
+#include "file.h"
+#include "dir.h"
+#include "mv.h"
+#include "debug.h"
+#include "pathfuncs.h"
+#include "string-extra.h"
-#include <sys/stat.h> /* stat() */
-#include "mv.h" /* stat() */
+#define SAME_FILE_INFO(sb1p, sb2p) \
+ ((sb1p)->st_dev == (sb2p)->st_dev && (sb1p)->st_ino == (sb2p)->st_ino)
-
-long filesize(int fd)
+off_t os_filesize(int osfd)
{
- struct stat buf;
+ struct stat sb;
- if (!fstat(fd, &buf))
- return buf.st_size;
+ if (!os_fstat(osfd, &sb))
+ return sb.st_size;
else
return -1;
}
-/* do we really need this in the app? */
-void fat_size(IF_MV(int volume,) unsigned long* size, unsigned long* free)
+int os_fsamefile(int osfd1, int osfd2)
+{
+ struct stat sb1, sb2;
+
+ if (os_fstat(osfd1, &sb1))
+ return -1;
+
+ if (os_fstat(osfd2, &sb2))
+ return -1;
+
+ return SAME_FILE_INFO(&sb1, &sb2);
+}
+
+int os_relate(const char *ospath1, const char *ospath2)
+{
+ DEBUGF("\"%s\" : \"%s\"\n", ospath1, ospath2);
+
+ if (!ospath2 || !*ospath2)
+ {
+ errno = ospath2 ? ENOENT : EFAULT;
+ return -1;
+ }
+
+ /* First file must stay open for duration so that its stats don't change */
+ int fd1 = os_open(ospath1, O_RDONLY);
+ if (fd1 < 0)
+ return -2;
+
+ struct stat sb1;
+ if (os_fstat(fd1, &sb1))
+ {
+ os_close(fd1);
+ return -3;
+ }
+
+ char path2buf[strlen(ospath2) + 1];
+ *path2buf = 0;
+
+ ssize_t len = 0;
+ const char *p = ospath2;
+ const char *sepmo = path_is_absolute(ospath2) ? PA_SEP_HARD : PA_SEP_SOFT;
+
+ int rc = RELATE_DIFFERENT;
+
+ while (1)
+ {
+ if (sepmo != PA_SEP_HARD &&
+ !(len = parse_path_component(&ospath2, &p)))
+ {
+ break;
+ }
+
+ char compname[len + 1];
+ strmemcpy(compname, p, len);
+
+ path_append(path2buf, sepmo, compname, sizeof (path2buf));
+ sepmo = PA_SEP_SOFT;
+
+ int errnum = errno; /* save and restore if not actually failing */
+ struct stat sb2;
+
+ if (!os_stat(path2buf, &sb2))
+ {
+ if (SAME_FILE_INFO(&sb1, &sb2))
+ {
+ rc = RELATE_SAME;
+ }
+ else if (rc == RELATE_SAME)
+ {
+ if (name_is_dot_dot(compname))
+ rc = RELATE_DIFFERENT;
+ else if (!name_is_dot(compname))
+ rc = RELATE_PREFIX;
+ }
+ }
+ else if (errno == ENOENT && !*GOBBLE_PATH_SEPCH(ospath2) &&
+ !name_is_dot_dot(compname))
+ {
+ if (rc == RELATE_SAME)
+ rc = RELATE_PREFIX;
+
+ errno = errnum;
+ break;
+ }
+ else
+ {
+ rc = -4;
+ break;
+ }
+ }
+
+ if (os_close(fd1) && rc >= 0)
+ rc = -5;
+
+ return rc;
+}
+
+bool os_file_exists(const char *ospath)
+{
+ int sim_fd = os_open(ospath, O_RDONLY, 0);
+ if (sim_fd < 0)
+ return false;
+
+ int errnum = errno;
+ os_close(sim_fd);
+ errno = errnum;
+
+ return true;
+}
+
+int os_opendirfd(const char *osdirname)
+{
+ return os_open(osdirname, O_RDONLY);
+}
+
+int os_opendir_and_fd(const char *osdirname, DIR **osdirpp, int *osfdp)
+{
+ /* another possible way is to use open() then fdopendir() */
+ *osdirpp = NULL;
+ *osfdp = -1;
+
+ DIR *dirp = os_opendir(osdirname);
+ if (!dirp)
+ return -1;
+
+ int rc = 0;
+ int errnum = errno;
+
+ int fd = os_dirfd(dirp);
+ if (fd < 0)
+ {
+ fd = os_opendirfd(osdirname);
+ rc = 1;
+ }
+
+ if (fd < 0)
+ {
+ os_closedir(dirp);
+ return -2;
+ }
+
+ errno = errnum;
+
+ *osdirpp = dirp;
+ *osfdp = fd;
+
+ return rc;
+}
+
+/* do we really need this in the app? (in the sim, yes) */
+void volume_size(IF_MV(int volume,) unsigned long *sizep, unsigned long *freep)
{
- IF_MV((void) volume);
- *size = *free = 0;
+ unsigned long size = 0, free = 0;
+ char volpath[MAX_PATH];
+ struct statfs fs;
+
+ if (os_volume_path(IF_MV(volume,) volpath, sizeof (volpath)) >= 0
+ && !statfs(volpath, &fs))
+ {
+ DEBUGF("statvfs: frsize=%d blocks=%ld bfree=%ld\n",
+ (int)fs.f_frsize, (long)fs.f_blocks, (long)fs.f_bfree);
+ if (sizep)
+ size = (fs.f_blocks / 2) * (fs.f_frsize / 512);
+
+ if (freep)
+ free = (fs.f_bfree / 2) * (fs.f_frsize / 512);
+ }
+
+ if (sizep)
+ *sizep = size;
+
+ if (freep)
+ *freep = free;
}
diff --git a/firmware/target/hosted/filesystem-unix.h b/firmware/target/hosted/filesystem-unix.h
new file mode 100644
index 0000000000..a09712d8b0
--- /dev/null
+++ b/firmware/target/hosted/filesystem-unix.h
@@ -0,0 +1,82 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _FILESYSTEM_UNIX_H_
+#define _FILESYSTEM_UNIX_H_
+
+/* Include for file.h and dir.h because mkdir and friends may be here */
+#include <sys/stat.h>
+
+#define strlcpy_from_os strlcpy
+#endif
+
+#ifdef _FILE_H_
+#ifndef _FILESYSTEM_UNIX__FILE_H_
+#define _FILESYSTEM_UNIX__FILE_H_
+
+#include <unistd.h>
+
+#define OS_STAT_T struct stat
+
+#ifndef OSFUNCTIONS_DECLARED
+#define os_open open
+#define os_creat creat
+#define os_close close
+#define os_lseek lseek
+#define os_stat stat
+#define os_fstat fstat
+#define os_fstatat fstatat
+#define os_lstat lstat
+#define os_fsync fsync
+#define os_ftruncate ftruncate
+#define os_remove remove
+#define os_rename rename
+#define os_readlink readlink
+#ifndef os_read
+#define os_read read
+#endif
+#ifndef os_write
+#define os_write write
+#endif
+#endif /* !OSFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_UNIX__FILE_H_ */
+#endif /* _FILE_H_ */
+
+#ifdef _DIR_H_
+#ifndef _FILESYSTEM_UNIX__DIR_H_
+#define _FILESYSTEM_UNIX__DIR_H_
+
+#include <dirent.h>
+
+#define OS_DIR_T DIR
+#define OS_DIRENT_T struct dirent
+
+#ifndef OSFUNCTIONS_DECLARED
+#define os_opendir opendir
+#define os_readdir readdir
+#define os_closedir closedir
+#define os_mkdir mkdir
+#define os_rmdir rmdir
+#define os_dirfd dirfd /* NOTE: might have to wrap on some platforms */
+#endif /* !OSFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_UNIX__DIR_H_ */
+#endif /* _DIR_H_ */
diff --git a/firmware/target/hosted/filesystem-win32.c b/firmware/target/hosted/filesystem-win32.c
new file mode 100644
index 0000000000..19ef1c0aa7
--- /dev/null
+++ b/firmware/target/hosted/filesystem-win32.c
@@ -0,0 +1,479 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#define RB_FILESYSTEM_OS
+#include <stdio.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include "config.h"
+#include "system.h"
+#include "file.h"
+#include "dir.h"
+#include "debug.h"
+#include "pathfuncs.h"
+#include "string-extra.h"
+
+#define SAME_FILE_INFO(lpInfo1, lpInfo2) \
+ ((lpInfo1)->dwVolumeSerialNumber == (lpInfo2)->dwVolumeSerialNumber && \
+ (lpInfo1)->nFileIndexHigh == (lpInfo2)->nFileIndexHigh && \
+ (lpInfo1)->nFileIndexLow == (lpInfo2)->nFileIndexLow)
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+static void win32_last_error_errno(void)
+{
+ switch (GetLastError())
+ {
+ case ERROR_FILE_NOT_FOUND:
+ case ERROR_PATH_NOT_FOUND:
+ errno = ENOENT;
+ break;
+ case ERROR_DIR_NOT_EMPTY:
+ errno = ENOTEMPTY;
+ break;
+ default:
+ errno = EIO;
+ }
+}
+
+#ifdef __MINGW32__
+#include <wchar.h>
+#include "rbunicode.h"
+
+static HANDLE win32_open(const char *ospath);
+static int win32_stat(const char *ospath, LPBY_HANDLE_FILE_INFORMATION lpInfo);
+
+unsigned short * strcpy_utf8ucs2(unsigned short *buffer,
+ const unsigned char *utf8)
+{
+ for (wchar_t *ucs2 = buffer;
+ ((utf8 = utf8decode(utf8, ucs2)), *ucs2); ucs2++);
+ return buffer;
+}
+
+#if 0
+unsigned char * strcpy_ucs2utf8(unsigned char *buffer,
+ const unsigned short *ucs2)
+{
+ for (unsigned char *utf8 = buffer;
+ ((utf8 = utf8encode(*ucs2, utf8)), *ucs2); ucs2++);
+ return buffer;
+}
+
+size_t strlen_utf8ucs2(const unsigned char *utf8)
+{
+ /* This won't properly count multiword ucs2 so use the alternative
+ below for now which doesn't either */
+ size_t length = 0;
+ unsigned short ucschar[2];
+ for (unsigned char c = *utf8; c;
+ ((utf8 = utf8decode(utf8, ucschar)), c = *utf8))
+ length++;
+
+ return length;
+}
+#endif /* 0 */
+
+size_t strlen_utf8ucs2(const unsigned char *utf8)
+{
+ return utf8length(utf8);
+}
+
+size_t strlen_ucs2utf8(const unsigned short *ucs2)
+{
+ size_t length = 0;
+ unsigned char utf8char[4];
+
+ for (unsigned short c = *ucs2; c; (c = *++ucs2))
+ length += utf8encode(c, utf8char) - utf8char;
+
+ return length;
+}
+
+size_t strlcpy_ucs2utf8(char *buffer, const unsigned short *ucs2,
+ size_t bufsize)
+{
+ if (!buffer)
+ bufsize = 0;
+
+ size_t length = 0;
+ unsigned char utf8char[4];
+
+ for (unsigned short c = *ucs2; c; (c = *++ucs2))
+ {
+ /* If the last character won't fit, this won't split it */
+ size_t utf8size = utf8encode(c, utf8char) - utf8char;
+ if ((length += utf8size) < bufsize)
+ buffer = mempcpy(buffer, utf8char, utf8size);
+ }
+
+ /* Above won't ever copy to very end */
+ if (bufsize)
+ *buffer = '\0';
+
+ return length;
+}
+
+#define _toucs2(utf8) \
+ ({ const char *_utf8 = (utf8); \
+ size_t _l = strlen_utf8ucs2(_utf8); \
+ void *_buffer = alloca((_l + 1)*2); \
+ strcpy_utf8ucs2(_buffer, _utf8); })
+
+#define _toutf8(ucs2) \
+ ({ const char *_ucs2 = (ucs2); \
+ size_t _l = strlen_ucs2utf8(_ucs2); \
+ void *_buffer = alloca(_l + 1); \
+ strcpy_ucs2utf8(_buffer, _ucs2); })
+
+int os_open(const char *ospath, int oflag, ...)
+{
+ return _wopen(_toucs2(ospath), oflag __OPEN_MODE_ARG);
+}
+
+int os_creat(const char *ospath, mode_t mode)
+{
+ return _wcreat(_toucs2(ospath), mode);
+}
+
+int os_stat(const char *ospath, struct _stat *s)
+{
+ return _wstat(_toucs2(ospath), s);
+}
+
+int os_remove(const char *ospath)
+{
+ return _wremove(_toucs2(ospath));
+}
+
+int os_rename(const char *osold, const char *osnew)
+{
+ int errnum = errno;
+
+ const wchar_t *wchosold = _toucs2(osold);
+ const wchar_t *wchosnew = _toucs2(osnew);
+
+ int rc = _wrename(wchosold, wchosnew);
+ if (rc < 0 && errno == EEXIST)
+ {
+ /* That didn't work; do cheap POSIX mimic */
+ BY_HANDLE_FILE_INFORMATION info;
+ if (win32_stat(osold, &info))
+ return -1;
+
+ if ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
+ !RemoveDirectoryW(wchosnew))
+ {
+ win32_last_error_errno();
+ return -1;
+ }
+
+ if (MoveFileExW(wchosold, wchosnew, MOVEFILE_REPLACE_EXISTING |
+ MOVEFILE_WRITE_THROUGH))
+ {
+ errno = errnum;
+ return 0;
+ }
+
+ errno = EIO;
+ }
+
+ return rc;
+}
+
+bool os_file_exists(const char *ospath)
+{
+ HANDLE h = win32_open(ospath);
+ if (h == INVALID_HANDLE_VALUE)
+ return false;
+
+ CloseHandle(h);
+ return true;
+}
+
+_WDIR * os_opendir(const char *osdirname)
+{
+ return _wopendir(_toucs2(osdirname));
+}
+
+int os_mkdir(const char *ospath, mode_t mode)
+{
+ return _wmkdir(_toucs2(ospath));
+ (void)mode;
+}
+
+int os_rmdir(const char *ospath)
+{
+ return _wrmdir(_toucs2(ospath));
+}
+
+int os_dirfd(_WDIR *osdirp)
+{
+#ifdef ENOTSUP
+ errno = ENOTSUP
+#else
+ errno = ENOSYS;
+#endif
+ return -1;
+ (void)osdirp;
+}
+
+int os_opendirfd(const char *osdirname)
+{
+ HANDLE h = win32_open(osdirname);
+ if (h == INVALID_HANDLE_VALUE)
+ return -1;
+
+ BY_HANDLE_FILE_INFORMATION info;
+ if (!GetFileInformationByHandle(h, &info))
+ errno = EIO;
+ else if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
+ errno = ENOTDIR;
+ else
+ {
+ /* Convert OS handle to fd; the fd now owns it */
+ int osfd = _open_osfhandle((long)h, O_RDONLY);
+ if (osfd >= 0)
+ return osfd;
+ }
+
+ CloseHandle(h);
+ return -2;
+}
+#endif /* __MINGW32__ */
+
+static size_t win32_path_strip_root(const char *ospath)
+{
+ const char *p = ospath;
+ int c = toupper(*p);
+
+ if (c >= 'A' && c <= 'Z')
+ {
+ /* drive */
+ if ((c = *++p) == ':')
+ return 2;
+ }
+
+ if (c == '\\' && *++p == '\\')
+ {
+ /* UNC */
+ while ((c = *++p) && c != '/' && c != '\\');
+ return p - ospath;
+ }
+
+ return 0;
+}
+
+static HANDLE win32_open(const char *ospath)
+{
+ /* FILE_FLAG_BACKUP_SEMANTICS is required for this to succeed at opening
+ a directory */
+ HANDLE h = CreateFileW(_toucs2(ospath), GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE |
+ FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+ if (h == INVALID_HANDLE_VALUE)
+ win32_last_error_errno();
+
+ return h;
+}
+
+static int win32_fstat(int osfd, HANDLE hFile,
+ LPBY_HANDLE_FILE_INFORMATION lpInfo)
+{
+ /* The file descriptor takes precedence over the win32 file handle */
+ if (osfd >= 0)
+ hFile = (HANDLE)_get_osfhandle(osfd);
+
+ int rc = GetFileInformationByHandle(hFile, lpInfo) ? 0 : -1;
+ if (rc < 0)
+ win32_last_error_errno();
+
+ return rc;
+}
+
+static int win32_stat(const char *ospath, LPBY_HANDLE_FILE_INFORMATION lpInfo)
+{
+ HANDLE h = win32_open(ospath);
+ if (h == INVALID_HANDLE_VALUE)
+ return -1;
+
+ int rc = win32_fstat(-1, h, lpInfo);
+
+ CloseHandle(h);
+
+ return rc;
+}
+
+int os_opendir_and_fd(const char *osdirname, _WDIR **osdirpp,
+ int *osfdp)
+{
+ /* another possible way is to use open() then fdopendir() */
+ *osdirpp = NULL;
+ *osfdp = -1;
+
+ _WDIR *dirp = os_opendir(osdirname);
+ if (!dirp)
+ return -1;
+
+ int rc = 0;
+ int errnum = errno;
+
+ int fd = os_dirfd(dirp);
+ if (fd < 0)
+ {
+ fd = os_opendirfd(osdirname);
+ rc = 1;
+ }
+
+ if (fd < 0)
+ {
+ os_closedir(dirp);
+ return -2;
+ }
+
+ errno = errnum;
+
+ *osdirpp = dirp;
+ *osfdp = fd;
+
+ return rc;
+}
+
+int os_fsamefile(int osfd1, int osfd2)
+{
+ BY_HANDLE_FILE_INFORMATION info1, info2;
+
+ if (!win32_fstat(osfd1, INVALID_HANDLE_VALUE, &info1) ||
+ !win32_fstat(osfd2, INVALID_HANDLE_VALUE, &info2))
+ return -1;
+
+ return SAME_FILE_INFO(&info1, &info2) ? 1 : 0;
+}
+
+int os_relate(const char *ospath1, const char *ospath2)
+{
+ DEBUGF("\"%s\" : \"%s\"\n", ospath1, ospath2);
+
+ if (!ospath2 || !*ospath2)
+ {
+ errno = ospath2 ? ENOENT : EFAULT;
+ return -1;
+ }
+
+ /* First file must stay open for duration so that its stats don't change */
+ HANDLE h1 = win32_open(ospath1);
+ if (h1 == INVALID_HANDLE_VALUE)
+ return -2;
+
+ BY_HANDLE_FILE_INFORMATION info1;
+ if (win32_fstat(-1, h1, &info1))
+ {
+ CloseHandle(h1);
+ return -3;
+ }
+
+ char path2buf[strlen(ospath2) + 1];
+ *path2buf = 0;
+
+ ssize_t len = 0;
+ const char *p = ospath2;
+ size_t rootlen = win32_path_strip_root(ospath2);
+ const char *sepmo = PA_SEP_SOFT;
+
+ if (rootlen)
+ {
+ strmemcpy(path2buf, ospath2, rootlen);
+ ospath2 += rootlen;
+ sepmo = PA_SEP_HARD;
+ }
+
+ int rc = RELATE_DIFFERENT;
+
+ while (1)
+ {
+ if (sepmo != PA_SEP_HARD &&
+ !(len = parse_path_component(&ospath2, &p)))
+ {
+ break;
+ }
+
+ char compname[len + 1];
+ strmemcpy(compname, p, len);
+
+ path_append(path2buf, sepmo, compname, sizeof (path2buf));
+ sepmo = PA_SEP_SOFT;
+
+ int errnum = errno; /* save and restore if not actually failing */
+ BY_HANDLE_FILE_INFORMATION info2;
+
+ if (!win32_stat(path2buf, &info2))
+ {
+ if (SAME_FILE_INFO(&info1, &info2))
+ {
+ rc = RELATE_SAME;
+ }
+ else if (rc == RELATE_SAME)
+ {
+ if (name_is_dot_dot(compname))
+ rc = RELATE_DIFFERENT;
+ else if (!name_is_dot(compname))
+ rc = RELATE_PREFIX;
+ }
+ }
+ else if (errno == ENOENT && !*GOBBLE_PATH_SEPCH(ospath2) &&
+ !name_is_dot_dot(compname))
+ {
+ if (rc == RELATE_SAME)
+ rc = RELATE_PREFIX;
+
+ errno = errnum;
+ break;
+ }
+ else
+ {
+ rc = -4;
+ break;
+ }
+ }
+
+ CloseHandle(h1);
+
+ return rc;
+}
+
+void volume_size(IF_MV(int volume,) unsigned long *sizep, unsigned long *freep)
+{
+ ULARGE_INTEGER free = { .QuadPart = 0 },
+ size = { .QuadPart = 0 };
+
+ char volpath[MAX_PATH];
+ if (os_volume_path(IF_MV(volume, ) volpath, sizeof (volpath)) >= 0)
+ GetDiskFreeSpaceExW(_toucs2(volpath), &free, &size, NULL);
+
+ if (sizep)
+ *sizep = size.QuadPart / 1024;
+
+ if (freep)
+ *freep = free.QuadPart / 1024;
+}
diff --git a/firmware/target/hosted/filesystem-win32.h b/firmware/target/hosted/filesystem-win32.h
new file mode 100644
index 0000000000..1d8f2749f9
--- /dev/null
+++ b/firmware/target/hosted/filesystem-win32.h
@@ -0,0 +1,111 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _FILESYSTEM_WIN32_H_
+#define _FILESYSTEM_WIN32_H_
+
+#ifndef OSFUNCTIONS_DECLARED
+
+#ifdef __MINGW32__
+/* filesystem-win32.c contains some string functions that could be useful
+ * elsewhere; just move them away to unicode.c or something if they prove
+ * so. */
+size_t strlcpy_ucs2utf8(char *buffer, const unsigned short *ucs,
+ size_t bufsize);
+
+#define strlcpy_from_os strlcpy_ucs2utf8
+#endif /* __MINGW32__ */
+
+#endif /* !OSFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_WIN32_H_ */
+
+#ifdef __MINGW32__
+
+#ifdef _FILE_H_
+#ifndef _FILESYSTEM_WIN32__FILE_H_
+#define _FILESYSTEM_WIN32__FILE_H_
+
+#include <unistd.h>
+#include <sys/stat.h>
+
+#define OS_STAT_T struct _stat
+
+#ifndef OSFUNCTIONS_DECLARED
+/* Wrap for off_t <=> long conversions */
+static inline off_t os_filesize_(int osfd)
+ { return _filelength(osfd); }
+static inline int os_ftruncate_(int osfd, off_t length)
+ { return _chsize(osfd, length); }
+
+#define os_filesize os_filesize_
+#define os_ftruncate os_ftruncate_
+#define os_fsync _commit
+#define os_fstat _fstat
+#define os_close close
+#define os_lseek lseek
+#ifndef os_read
+#define os_read read
+#endif
+#ifndef os_write
+#define os_write write
+#endif
+
+/* These need string type conversion from utf8 to ucs2; that's done inside */
+int os_open(const char *ospath, int oflag, ...);
+int os_creat(const char *ospath, mode_t mode);
+int os_stat(const char *ospath, struct _stat *s);
+int os_remove(const char *ospath);
+int os_rename(const char *osold, const char *osnew);
+
+#endif /* !OSFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_WIN32__FILE_H_ */
+#endif /* _FILE_H_ */
+
+#ifdef _DIR_H_
+#ifndef _FILESYSTEM_WIN32__DIR_H_
+#define _FILESYSTEM_WIN32__DIR_H_
+
+#include <dirent.h>
+
+#define OS_DIRENT_CONVERT /* needs string conversion */
+#define OS_DIR_T _WDIR
+#define OS_DIRENT_T struct _wdirent
+
+#ifndef OSFUNCTIONS_DECLARED
+
+_WDIR * os_opendir(const char *osdirname);
+int os_opendirfd(const char *osdirname);
+#define os_readdir _wreaddir
+#define os_closedir _wclosedir
+int os_mkdir(const char *ospath, mode_t mode);
+int os_rmdir(const char *ospath);
+
+#endif /* OSFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_WIN32__DIR_H_ */
+#endif /* _DIR_H_ */
+
+#else /* !__MINGW32__ */
+
+#include "filesystem-unix.h"
+
+#endif /* __MINGW32__ */
diff --git a/firmware/target/hosted/lc-unix.c b/firmware/target/hosted/lc-unix.c
index 6e5f15ec99..810dc9f92c 100644
--- a/firmware/target/hosted/lc-unix.c
+++ b/firmware/target/hosted/lc-unix.c
@@ -50,14 +50,3 @@ void lc_close(void *handle)
{
dlclose(handle);
}
-
-void *lc_open_from_mem(void *addr, size_t blob_size)
-{
- (void)addr;
- (void)blob_size;
- /* we don't support loading code from memory on application builds,
- * it doesn't make sense (since it means writing the blob to disk again and
- * then falling back to load from disk) and requires the ability to write
- * to an executable directory */
- return NULL;
-}
diff --git a/firmware/export/filefuncs.h b/firmware/target/hosted/sdl/app/load_code-sdl-app.c
index 92e97f65b0..686944343f 100644
--- a/firmware/export/filefuncs.h
+++ b/firmware/target/hosted/sdl/app/load_code-sdl-app.c
@@ -7,7 +7,7 @@
* \/ \/ \/ \/ \/
* $Id$
*
- * Copyright (C) 2009 by Maurus Cuelenaere
+ * Copyright (C) 2010 Thomas Martitz
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -18,22 +18,19 @@
* KIND, either express or implied.
*
****************************************************************************/
-
-#ifndef __INCLUDE_FILEFUNCS_H_
-#define __INCLUDE_FILEFUNCS_H_
-
-#include <stdbool.h>
-#include "config.h"
+#define RB_FILESYSTEM_OS
+#include "system.h"
#include "file.h"
-#include "dir.h"
-
-#ifdef HAVE_MULTIVOLUME
-int strip_volume(const char* name, char* namecopy);
-#endif
-
-bool file_exists(const char *file);
-bool dir_exists(const char *path);
+#include "load_code.h"
-extern struct dirinfo dir_get_info(DIR* parent, struct dirent *entry);
+void *lc_open(const char *filename, unsigned char *buf, size_t buf_size)
+{
+ char realpath[MAX_PATH];
+ const char *fpath = handle_special_dirs(filename, 0, realpath,
+ sizeof (realpath));
+ if (!fpath)
+ return NULL;
-#endif /* __INCLUDE_FILEFUNCS_H_ */
+ return os_lc_open(fpath);
+ (void)buf; (void)buf_size;
+}
diff --git a/firmware/target/hosted/sdl/filesystem-sdl.c b/firmware/target/hosted/sdl/filesystem-sdl.c
new file mode 100644
index 0000000000..5a8e2c417a
--- /dev/null
+++ b/firmware/target/hosted/sdl/filesystem-sdl.c
@@ -0,0 +1,55 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#define RB_FILESYSTEM_OS
+#include "config.h"
+#include "system.h"
+#include "thread-sdl.h"
+#include "mutex.h"
+#include "file.h"
+
+#ifdef HAVE_SDL_THREADS
+#define YIELD_THRESHOLD 512
+static bool initialized = false;
+static struct mutex readwrite_mtx;
+
+/* Allow other threads to run while performing I/O */
+ssize_t os_sdl_readwrite(int osfd, void *buf, size_t nbyte, bool dowrite)
+{
+ if (!initialized)
+ {
+ mutex_init(&readwrite_mtx);
+ initialized = true;
+ }
+
+ mutex_lock(&readwrite_mtx);
+
+ void *mythread = nbyte > YIELD_THRESHOLD ? sim_thread_unlock() : NULL;
+
+ ssize_t rc = dowrite ? write(osfd, buf, nbyte) : read(osfd, buf, nbyte);
+
+ if (mythread)
+ sim_thread_lock(mythread);
+
+ mutex_unlock(&readwrite_mtx);
+ return rc;
+}
+
+#endif /* HAVE_SDL_THREADS */
diff --git a/firmware/target/hosted/sdl/filesystem-sdl.h b/firmware/target/hosted/sdl/filesystem-sdl.h
new file mode 100644
index 0000000000..934b43b34b
--- /dev/null
+++ b/firmware/target/hosted/sdl/filesystem-sdl.h
@@ -0,0 +1,37 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _FILESYSTEM_SDL_H_
+#define _FILESYSTEM_SDL_H_
+
+#ifdef HAVE_SDL_THREADS
+#undef os_read
+#undef os_write
+
+ssize_t os_sdl_readwrite(int osfd, void *buf, size_t nbyte, bool dowrite);
+
+#define os_read(osfd, buf, nbyte) \
+ os_sdl_readwrite((osfd), (buf), (nbyte), false)
+#define os_write(osfd, buf, nbyte) \
+ os_sdl_readwrite((osfd), (void *)(buf), (nbyte), true)
+
+#endif /* HAVE_SDL_THREADS */
+
+#endif /* _FILESYSTEM_SDL_H_ */
diff --git a/firmware/target/hosted/sdl/load_code-sdl.c b/firmware/target/hosted/sdl/load_code-sdl.c
new file mode 100644
index 0000000000..ee29853ab5
--- /dev/null
+++ b/firmware/target/hosted/sdl/load_code-sdl.c
@@ -0,0 +1,52 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 Daniel Stenberg
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#define RB_FILESYSTEM_OS
+#include <SDL_loadso.h>
+#include "system.h"
+#include "load_code.h"
+#include "filesystem-sdl.h"
+#include "debug.h"
+
+void * os_lc_open(const char *ospath)
+{
+ void *handle = SDL_LoadObject(ospath);
+ if (handle == NULL)
+ {
+ DEBUGF("%s(\"%s\") failed\n", __func__, ospath);
+ DEBUGF(" SDL error '%s'\n", SDL_GetError());
+ }
+
+ return handle;
+}
+
+void * lc_get_header(void *handle)
+{
+ char *ret = SDL_LoadFunction(handle, "__header");
+ if (ret == NULL)
+ ret = SDL_LoadFunction(handle, "___header");
+
+ return ret;
+}
+
+void lc_close(void *handle)
+{
+ SDL_UnloadObject(handle);
+}
diff --git a/firmware/target/hosted/sdl/system-sdl.c b/firmware/target/hosted/sdl/system-sdl.c
index fdf79d9333..aa322ddf3a 100644
--- a/firmware/target/hosted/sdl/system-sdl.c
+++ b/firmware/target/hosted/sdl/system-sdl.c
@@ -51,6 +51,8 @@
#endif
+#define SIMULATOR_DEFAULT_ROOT "simdisk"
+
SDL_Surface *gui_surface;
bool background = true; /* use backgrounds by default */
@@ -63,7 +65,7 @@ bool debug_buttons = false;
bool lcd_display_redraw = true; /* Used for player simulator */
char having_new_lcd = true; /* Used for player simulator */
bool sim_alarm_wakeup = false;
-const char *sim_root_dir = NULL;
+const char *sim_root_dir = SIMULATOR_DEFAULT_ROOT;
static SDL_Thread *evt_thread = NULL;
diff --git a/firmware/target/hosted/sdl/system-sim.h b/firmware/target/hosted/sdl/system-sim.h
new file mode 100644
index 0000000000..16c0cdde52
--- /dev/null
+++ b/firmware/target/hosted/sdl/system-sim.h
@@ -0,0 +1,32 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _SYSTEM_SIM_H_
+#define _SYSTEM_SIM_H_
+
+#ifdef WIN32
+#include <time.h>
+struct tm * localtime_r(const time_t *restrict timer,
+ struct tm *restrict result);
+struct tm * gmtime_r(const time_t *restrict timer,
+ struct tm *restrict result);
+#endif /* WIN32 */
+
+#endif /* _SYSTEM_SIM_H_ */
diff --git a/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c b/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c
index 9c0d1982ad..0e74444cf3 100644
--- a/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c
+++ b/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c
@@ -26,7 +26,6 @@
#include "ata_idle_notify.h"
#include "ata-sd-target.h"
#include "disk.h"
-#include "fat.h"
#include "led.h"
#include "sdmmc.h"
#include "logf.h"
@@ -1467,34 +1466,28 @@ static void sd_thread(void)
{
#ifdef HAVE_HOTSWAP
case SYS_HOTSWAP_INSERTED:
- case SYS_HOTSWAP_EXTRACTED:
- fat_lock(); /* lock-out FAT activity first -
- prevent deadlocking via disk_mount that
- would cause a reverse-order attempt with
- another thread */
- mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
- into driver that bypass the fat cache */
+ case SYS_HOTSWAP_EXTRACTED:;
+ int success = 1;
- /* We now have exclusive control of fat cache and ata */
+ disk_unmount(sd_drive_nr); /* release "by force" */
- disk_unmount(sd_drive_nr); /* release "by force", ensure file
- descriptors aren't leaked and any busy
- ones are invalid if mounting */
+ mutex_lock(&sd_mtx); /* lock-out card activity */
/* Force card init for new card, re-init for re-inserted one or
* clear if the last attempt to init failed with an error. */
card.initialized = 0;
+ mutex_unlock(&sd_mtx);
+
if(ev.id == SYS_HOTSWAP_INSERTED)
- disk_mount(sd_drive_nr);
+ success = disk_mount(sd_drive_nr); /* 0 if fail */
- queue_broadcast(SYS_FS_CHANGED, 0);
+ if(success)
+ queue_broadcast(SYS_FS_CHANGED, 0);
- /* Access is now safe */
- mutex_unlock(&sd_mtx);
- fat_unlock();
break;
-#endif
+#endif /* HAVE_HOTSWAP */
+
case SYS_TIMEOUT:
if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
idle_notified = false;
diff --git a/firmware/usbstack/usb_storage.c b/firmware/usbstack/usb_storage.c
index 59ada3bdd8..48e9442420 100644
--- a/firmware/usbstack/usb_storage.c
+++ b/firmware/usbstack/usb_storage.c
@@ -355,10 +355,7 @@ static bool check_disk_present(IF_MD_NONVOID(int volume))
#ifdef USB_USE_RAMDISK
return true;
#else
- unsigned char* sector = fat_get_sector_buffer();
- bool success = storage_read_sectors(IF_MD(volume,)0,1,sector) == 0;
- fat_release_sector_buffer();
- return success;
+ return disk_present(IF_MD(volume));
#endif
}
diff --git a/tools/checkwps/SOURCES b/tools/checkwps/SOURCES
index 828b7965ea..425e8de7b9 100644
--- a/tools/checkwps/SOURCES
+++ b/tools/checkwps/SOURCES
@@ -1,13 +1,21 @@
+#undef unix /* messes up filesystem-unix.c below */
../../apps/gui/skin_engine/skin_parser.c
../../apps/gui/skin_engine/skin_backdrops.c
../../apps/gui/viewport.c
../../apps/misc.c
../../firmware/common/strlcpy.c
-checkwps.c
-
+../../firmware/common/pathfuncs.c
+../../firmware/asm/mempcpy.c
+../../firmware/target/hosted/filesystem-unix.c
#ifdef APPLICATION
-../../firmware/common/rbpaths.c
+../../firmware/target/hosted/filesystem-app.c
+#else
+../../uisimulator/common/filesystem-sim.c
#endif
+#ifdef DEBUG
+../../firmware/debug.c
+#endif
+checkwps.c
#ifdef HAVE_LCD_BITMAP
../../apps/recorder/bmp.c
diff --git a/tools/checkwps/checkwps.c b/tools/checkwps/checkwps.c
index 10e505f006..c2cadc7444 100644
--- a/tools/checkwps/checkwps.c
+++ b/tools/checkwps/checkwps.c
@@ -39,11 +39,14 @@ bool debug_wps = true;
int wps_verbose_level = 0;
char *skin_buffer;
-int errno;
-
+const char *sim_root_dir = ".";
const struct settings_list *settings;
const int nb_settings = 0;
+#ifdef SIMULATOR
+#error beep beep
+#endif
+
/* static endianness conversion */
#define SWAP_16(x) ((typeof(x))(unsigned short)(((unsigned short)(x) >> 8) | \
((unsigned short)(x) << 8)))
diff --git a/tools/checkwps/file.h b/tools/checkwps/file.h
deleted file mode 100644
index a14d7f5b91..0000000000
--- a/tools/checkwps/file.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#ifndef MAX_PATH
-#define MAX_PATH 260
-#endif
-
-/* Wrapper - required for O_RDONLY */
-
-#include <fcntl.h>
-
-extern ssize_t read(int fd, void *buf, size_t count);
-extern ssize_t write(int fd, const void *buf, size_t count);
-extern off_t lseek(int fildes, off_t offset, int whence);
-extern int close(int fd);
-
-/* strlcpy doesn't belong here (it's in string.h in the rockbox sources),
- * but this avoids complicated magic to override the system string.h */
-size_t strlcpy(char *dst, const char *src, size_t siz);
diff --git a/tools/database/SOURCES b/tools/database/SOURCES
index 5c9b971583..71593bba11 100644
--- a/tools/database/SOURCES
+++ b/tools/database/SOURCES
@@ -1,15 +1,21 @@
+#undef unix /* messes up filesystem-unix.c below */
database.c
../../apps/misc.c
../../apps/tagcache.c
../../firmware/common/crc32.c
-../../firmware/common/filefuncs.c
+../../firmware/common/pathfuncs.c
../../firmware/common/strlcpy.c
../../firmware/common/strcasestr.c
../../firmware/common/structec.c
../../firmware/common/unicode.c
../../firmware/target/hosted/debug-hosted.c
../../firmware/logf.c
-../../uisimulator/common/io.c
+../../firmware/target/hosted/filesystem-unix.c
+#ifdef APPLICATION
+../../firmware/target/hosted/filesystem-app.c
+#else /* !APPLICATION */
+../../uisimulator/common/filesystem-sim.c
+#endif /* APPLICATION */
#if CONFIG_CODEC != SWCODEC
../../lib/rbcodec/metadata/id3tags.c
../../lib/rbcodec/metadata/metadata.c
diff --git a/tools/root.make b/tools/root.make
index 4d58e26e8e..4e0ca7c4f0 100644
--- a/tools/root.make
+++ b/tools/root.make
@@ -356,22 +356,22 @@ endif
ifeq (,$(findstring android, $(APP_TYPE)))
-simext:
+simext1:
$(SILENT)mkdir -p $@
bininstall: $(BUILDDIR)/$(BINARY)
@echo "Installing your rockbox binary in your '$(RBPREFIX)' dir"
$(SILENT)cp $(BINARY) "$(RBPREFIX)/.rockbox/"
-install: simext
+install: simext1
@echo "Installing your build in your '$(RBPREFIX)' dir"
$(SILENT)$(TOOLSDIR)/buildzip.pl $(VERBOSEOPT) --app=$(APPLICATION) -m "$(MODELNAME)" -i "$(TARGET_ID)" $(INSTALL) -z "zip -r0" -r "$(ROOTDIR)" --rbdir="$(RBDIR)" -f 0 $(TARGET) $(BINARY)
-fullinstall: simext
+fullinstall: simext1
@echo "Installing a full setup in your '$(RBPREFIX)' dir"
$(SILENT)$(TOOLSDIR)/buildzip.pl $(VERBOSEOPT) --app=$(APPLICATION) -m "$(MODELNAME)" -i "$(TARGET_ID)" $(INSTALL) -z "zip -r0" -r "$(ROOTDIR)" --rbdir="$(RBDIR)" -f 2 $(TARGET) $(BINARY)
-symlinkinstall: simext
+symlinkinstall: simext1
@echo "Installing a full setup with links in your '$(RBPREFIX)' dir"
$(SILENT)$(TOOLSDIR)/buildzip.pl $(VERBOSEOPT) --app=$(APPLICATION) -m "$(MODELNAME)" -i "$(TARGET_ID)" $(INSTALL) -z "zip -r0" -r "$(ROOTDIR)" --rbdir="$(RBDIR)" -f 2 $(TARGET) $(BINARY) -l
endif
diff --git a/uisimulator/common/SOURCES b/uisimulator/common/SOURCES
index 939f1638c3..9833753236 100644
--- a/uisimulator/common/SOURCES
+++ b/uisimulator/common/SOURCES
@@ -1,4 +1,5 @@
#ifdef SIMULATOR
+
lcd-common.c
#ifdef HAVE_LCD_CHARCELLS
font-player.c
@@ -8,8 +9,20 @@ sim_icons.c
sim_tasks.c
fmradio.c
backlight-sim.c
-stubs.c
powermgmt-sim.c
+filesystem-sim.c
+
+#ifdef WIN32
+time-win32.c
+#endif
+
+#ifndef __PCTOOL__
+load_code-sim.c
#endif
-io.c
+stubs.c
+
+#else
+dummylib.c /* for now, so the lib actually builds */
+#endif /* SIMULATOR */
+
diff --git a/uisimulator/common/dummylib.c b/uisimulator/common/dummylib.c
new file mode 100644
index 0000000000..9747edf3a1
--- /dev/null
+++ b/uisimulator/common/dummylib.c
@@ -0,0 +1 @@
+/* this exists to get libuisimulator to make so that the sdl app may link */
diff --git a/uisimulator/common/filesystem-sim.c b/uisimulator/common/filesystem-sim.c
new file mode 100644
index 0000000000..766beb3fda
--- /dev/null
+++ b/uisimulator/common/filesystem-sim.c
@@ -0,0 +1,833 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 Daniel Stenberg
+ * Copyright (C) 2014 Michael Sevakis
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#define RB_FILESYSTEM_OS
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <time.h>
+#include <errno.h>
+#include <limits.h>
+#include "config.h"
+#include "system.h"
+#include "file.h"
+#include "dir.h"
+#include "file_internal.h"
+#include "pathfuncs.h"
+#include "string-extra.h"
+#include "debug.h"
+
+#ifndef os_fstatat
+#define USE_OSDIRNAME /* we need to remember the open directory path */
+#endif
+
+extern const char *sim_root_dir;
+
+/* Windows (and potentially other OSes) distinguish binary and text files.
+ * Define a dummy for the others. */
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+struct filestr_desc
+{
+ int osfd; /* The host OS file descriptor */
+ bool mounted; /* Is host volume still mounted? */
+#ifdef HAVE_MULTIVOLUME
+ int volume; /* The virtual volume number */
+#endif
+} openfiles[MAX_OPEN_FILES] =
+{
+ [0 ... MAX_OPEN_FILES-1] = { .osfd = -1 }
+};
+
+static struct filestr_desc * alloc_filestr(int *fildesp)
+{
+ for (unsigned int i = 0; i < MAX_OPEN_FILES; i++)
+ {
+ struct filestr_desc *filestr = &openfiles[i];
+ if (filestr->osfd < 0)
+ {
+ *fildesp = i;
+ return filestr;
+ }
+ }
+
+ return NULL;
+}
+
+static struct filestr_desc * get_filestr(int fildes)
+{
+ struct filestr_desc *filestr = &openfiles[fildes];
+
+ if ((unsigned int)fildes >= MAX_OPEN_FILES || filestr->osfd < 0)
+ filestr = NULL;
+ else if (filestr->mounted)
+ return filestr;
+
+ errno = filestr ? ENXIO : EBADF;
+ DEBUGF("fildes %d: %s\n", fildes, strerror(errno));
+ return NULL;
+}
+
+struct dirstr_desc
+{
+ int osfd; /* Host OS directory file descriptor */
+ bool osfd_opened; /* Host fd is another open file */
+ OS_DIR_T *osdirp; /* Host OS directory stream */
+#ifdef USE_OSDIRNAME
+ char *osdirname; /* Host OS directory path */
+#endif
+ struct sim_dirent entry; /* Rockbox directory entry */
+#ifdef HAVE_MULTIVOLUME
+ int volume; /* Virtual volume number */
+ int volumecounter; /* Counter for root volume entries */
+#endif
+ bool mounted; /* Is the virtual volume still mounted? */
+} opendirs[MAX_OPEN_DIRS];
+
+static struct dirstr_desc * alloc_dirstr(void)
+{
+ for (unsigned int i = 0; i < MAX_OPEN_DIRS; i++)
+ {
+ struct dirstr_desc *dirstr = &opendirs[i];
+ if (dirstr->osdirp == NULL)
+ return dirstr;
+ }
+
+ return NULL;
+}
+
+static struct dirstr_desc * get_dirstr(DIR *dirp)
+{
+ struct dirstr_desc *dirstr = (struct dirstr_desc *)dirp;
+
+ if (!PTR_IN_ARRAY(opendirs, dirstr, MAX_OPEN_DIRS) || !dirstr->osdirp)
+ dirstr = NULL;
+ else if (dirstr->mounted)
+ return dirstr;
+
+ errno = dirstr ? ENXIO : EBADF;
+ DEBUGF("dir #%d: %s\n", (int)(dirstr - opendirs), strerror(errno));
+ return NULL;
+}
+
+static int close_dirstr(struct dirstr_desc *dirstr)
+{
+ OS_DIR_T *osdirp = dirstr->osdirp;
+
+ dirstr->mounted = false;
+
+#ifdef USE_OSDIRNAME
+ free(dirstr->osdirname);
+#endif
+ if (dirstr->osfd_opened)
+ {
+ os_close(dirstr->osfd);
+ dirstr->osfd_opened = false;
+ }
+
+ int rc = os_closedir(osdirp);
+ dirstr->osdirp = NULL;
+
+ return rc;
+}
+
+#ifdef HAVE_MULTIVOLUME
+static int readdir_volume_inner(struct dirstr_desc *dirstr,
+ struct sim_dirent *entry)
+{
+ /* Volumes (secondary file systems) get inserted into the system root
+ * directory. If the path specified volume 0, enumeration will not
+ * include other volumes, but just its own files and directories.
+ *
+ * Fake special directories, which don't really exist, that will get
+ * redirected upon opendir()
+ */
+ while (++dirstr->volumecounter < NUM_VOLUMES)
+ {
+ /* on the system root */
+ if (!volume_present(dirstr->volumecounter))
+ continue;
+
+ get_volume_name(dirstr->volumecounter, entry->d_name);
+ return 1;
+ }
+
+ /* do normal directory entry fetching */
+ return 0;
+}
+#endif /* HAVE_MULTIVOLUME */
+
+static inline int readdir_volume(struct dirstr_desc *dirstr,
+ struct sim_dirent *entry)
+{
+#ifdef HAVE_MULTIVOLUME
+ if (dirstr->volumecounter < NUM_VOLUMES)
+ return readdir_volume_inner(dirstr, entry);
+#endif /* HAVE_MULTIVOLUME */
+
+ /* do normal directory entry fetching */
+ return 0;
+ (void)dirstr; (void)entry;
+}
+
+
+/** Internal functions **/
+
+#ifdef HAVE_MULTIDRIVE
+/**
+ * Handle drive extraction by pretending the files' volumes no longer exist
+ * and invalidating their I/O for the remainder of their lifetimes as would
+ * happen on a native target
+ */
+void sim_ext_extracted(int drive)
+{
+ for (unsigned int i = 0; i < MAX_OPEN_FILES; i++)
+ {
+ struct filestr_desc *filestr = &openfiles[i];
+ if (filestr->osfd >= 0 && volume_drive(filestr->volume) == drive)
+ filestr->mounted = false;
+ }
+
+ for (unsigned int i = 0; i < MAX_OPEN_DIRS; i++)
+ {
+ struct dirstr_desc *dirstr = &opendirs[i];
+ if (dirstr->osdirp && volume_drive(dirstr->volume) == drive)
+ dirstr->mounted = false;
+ }
+
+ (void)drive;
+}
+#endif /* HAVE_MULTIDRIVE */
+
+/**
+ * Provides target-like path parsing behavior with single and multiple volumes
+ * while performing minimal transforming of the input.
+ *
+ * Paths are sandboxed to the simulated namespace:
+ * e.g. "{simdir}/foo/../../../../bar" becomes "{simdir}/foo/../bar"
+ */
+int sim_get_os_path(char *buffer, const char *path, size_t bufsize)
+{
+ #define ADVBUF(amt) \
+ ({ buffer += (amt); bufsize -= (amt); })
+
+ #define PPP_SHOWPATHS 0
+
+ if (!path_is_absolute(path))
+ {
+ DEBUGF("ERROR: path is not absolute: \"%s\"\n", path);
+ errno = ENOENT;
+ return -1;
+ }
+
+#if PPP_SHOWPATHS
+ const char *const buffer0 = buffer;
+ DEBUGF("PPP (pre): \"%s\"\n", path);
+#endif
+
+ /* Prepend sim root */
+ size_t size = strlcpy(buffer, sim_root_dir, bufsize);
+ if (size >= bufsize)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ ADVBUF(size);
+
+#ifdef HAVE_MULTIVOLUME
+ /* Track the last valid volume spec encountered */
+ int volume = -1;
+ bool sysroot = true;
+
+ /* Basename of sim dir to switch back to simdisk from ext */
+ #define DIRBASE_FMT ".." PATH_SEPSTR "%s"
+ ssize_t dirbase_len = 0;
+ char dirbase[size + 3 + 1];
+
+ /* Basename of ext directory to switch to alternate volume */
+ #define SIMEXT_FMT ".." PATH_SEPSTR "simext%d"
+ char extbuf[sizeof (SIMEXT_FMT) + 20 + 1];
+#endif /* HAVE_MULTIVOLUME */
+
+ int level = 0;
+ bool done = false;
+ while (!done)
+ {
+ const char *p;
+ ssize_t len = parse_path_component(&path, &p);
+
+
+ switch (len)
+ {
+ case 0:
+ done = true;
+ if (path[-1] != PATH_SEPCH)
+ continue;
+
+ /* Path ends with a separator; preserve that */
+ p = &path[-1];
+ len = 1;
+ break;
+
+ case 1:
+ case 2:
+ if (p[0] == '.')
+ {
+ if (len == 1)
+ break;
+
+ if (p[1] == '.')
+ goto is_dot_dot;
+ }
+
+ default:
+ level++;
+
+ #ifdef HAVE_MULTIVOLUME
+ if (level != 1)
+ break; /* Volume spec only valid @ root level */
+
+ const char *next;
+ volume = path_strip_volume(p, &next, true);
+
+ if (next > p)
+ {
+ #ifdef HAVE_MULTIDRIVE
+ /* Feign failure if the volume isn't "mounted" */
+ if (!volume_present(volume))
+ {
+ errno = ENXIO;
+ return -1;
+ }
+ #endif /* HAVE_MULTIDRIVE */
+
+ sysroot = false;
+
+ if (volume == 0)
+ continue;
+
+ p = extbuf;
+ len = snprintf(extbuf, sizeof (extbuf), SIMEXT_FMT, volume);
+ }
+ #endif /* HAVE_MULTIVOLUME */
+ break;
+
+ is_dot_dot:
+ if (level <= 0)
+ continue; /* Can't go above root; erase */
+
+ level--;
+
+ #ifdef HAVE_MULTIVOLUME
+ if (level == 0)
+ {
+ int v = volume;
+ bool sr = sysroot;
+ volume = -1;
+ sysroot = true;
+
+ if (v <= 0)
+ {
+ if (sr)
+ break;
+
+ continue;
+ }
+
+ /* Going up from a volume root and back down to the sys root */
+ if (dirbase_len == 0)
+ {
+ /* Get the main simdisk directory so it can be reentered */
+ char tmpbuf[sizeof (dirbase)];
+ #ifdef WIN32
+ path_correct_separators(tmpbuf, sim_root_dir);
+ path_strip_drive(tmpbuf, &p, false);
+ #else
+ p = tmpbuf;
+ strcpy(tmpbuf, sim_root_dir);
+ #endif
+ size = path_basename(p, &p);
+ ((char *)p)[size] = '\0';
+
+ if (size == 0 || is_dotdir_name(p))
+ {
+ /* This is nonsense and won't work */
+ DEBUGF("ERROR: sim root dir basname is dotdir or"
+ " empty: \"%s\"\n", sim_root_dir);
+ errno = ENOENT;
+ return -1;
+ }
+
+ dirbase_len = snprintf(dirbase, sizeof (dirbase),
+ DIRBASE_FMT, p);
+ }
+
+ p = dirbase;
+ len = dirbase_len;
+ break;
+ }
+ #endif /* HAVE_MULTIVOLUME */
+ break;
+ } /* end switch */
+
+ char compname[len + 1];
+ strmemcpy(compname, p, len);
+
+ size = path_append(buffer, PA_SEP_HARD, compname, bufsize);
+ if (size >= bufsize)
+ {
+ errno = ENAMETOOLONG;
+ return -1;
+ }
+ ADVBUF(size);
+ }
+
+#if PPP_SHOWPATHS
+ DEBUGF("PPP (post): \"%s\"" IF_MV(" vol:%d") "\n",
+ buffer0 IF_MV(, volume));
+#endif
+
+ return IF_MV(volume) +1;
+}
+
+
+/** File functions **/
+
+int sim_open(const char *path, int oflag, ...)
+{
+ int fildes;
+ struct filestr_desc *filestr = alloc_filestr(&fildes);
+ if (!filestr)
+ {
+ errno = EMFILE;
+ return -1;
+ }
+
+ char ospath[SIM_TMPBUF_MAX_PATH];
+ int pprc = sim_get_os_path(ospath, path, sizeof (ospath));
+ if (pprc < 0)
+ return -2;
+
+ filestr->osfd = os_open(ospath, oflag | O_BINARY __OPEN_MODE_ARG);
+ if (filestr->osfd < 0)
+ return -3;
+
+#ifdef HAVE_MULTIVOLUME
+ filestr->volume = MAX(pprc - 1, 0);
+#endif
+ filestr->mounted = true;
+ return fildes;
+}
+
+int sim_creat(const char *path, mode_t mode)
+{
+ return sim_open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
+}
+
+int sim_close(int fildes)
+{
+ struct filestr_desc *filestr = &openfiles[fildes];
+ if ((unsigned int)fildes >= MAX_OPEN_FILES || filestr->osfd < 0)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ int osfd = filestr->osfd;
+ filestr->osfd = -1;
+ return os_close(osfd);
+}
+
+int sim_ftruncate(int fildes, off_t length)
+{
+ struct filestr_desc *filestr = get_filestr(fildes);
+ if (!filestr)
+ return -1;
+
+ off_t size = os_filesize(filestr->osfd);
+ if (size < 0)
+ return -1;
+
+ if (length >= size)
+ return 0;
+
+ int rc = os_ftruncate(filestr->osfd, length);
+
+#ifdef HAVE_DIRCACHE
+ if (rc >= 0)
+ dircache_ftruncate(xxxxxx);
+#endif
+
+ return rc;
+}
+
+int sim_fsync(int fildes)
+{
+ struct filestr_desc *filestr = get_filestr(fildes);
+ if (!filestr)
+ return -1;
+
+ int rc = os_fsync(filestr->osfd);
+
+#ifdef HAVE_DIRCACHE
+ if (rc >= 0)
+ dircache_fsync(xxxxxx);
+#endif
+
+ return rc;
+}
+
+off_t sim_lseek(int fildes, off_t offset, int whence)
+{
+ struct filestr_desc *filestr = get_filestr(fildes);
+ if (!filestr)
+ return -1;
+
+ return os_lseek(filestr->osfd, offset, whence);
+}
+
+ssize_t sim_read(int fildes, void *buf, size_t nbyte)
+{
+ struct filestr_desc *filestr = get_filestr(fildes);
+ if (!filestr)
+ return -1;
+
+ return os_read(filestr->osfd, buf, nbyte);
+}
+
+ssize_t sim_write(int fildes, const void *buf, size_t nbyte)
+{
+ struct filestr_desc *filestr = get_filestr(fildes);
+ if (!filestr)
+ return -1;
+
+ return os_write(filestr->osfd, buf, nbyte);
+}
+
+int sim_remove(const char *path)
+{
+ char ospath[SIM_TMPBUF_MAX_PATH];
+ if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0)
+ return -1;
+
+ int rc = os_remove(ospath);
+
+#ifdef HAVE_DIRCACHE
+ if (rc >= 0)
+ dircache_remove(xxxxxx);
+#endif
+
+ return rc;
+}
+
+int sim_rename(const char *old, const char *new)
+{
+ char osold[SIM_TMPBUF_MAX_PATH];
+ int pprc1 = sim_get_os_path(osold, old, sizeof (osold));
+ if (pprc1 < 0)
+ return -1;
+
+ char osnew[SIM_TMPBUF_MAX_PATH];
+ int pprc2 = sim_get_os_path(osnew, new, sizeof (osnew));
+ if (pprc2 < 0)
+ return -1;
+
+ if (MAX(pprc1 - 1, 0) != MAX(pprc2 - 1, 0))
+ {
+ /* Pretend they're different devices */
+ errno = EXDEV;
+ return -1;
+ }
+
+ int rc = os_rename(osold, osnew);
+
+#ifdef HAVE_DIRCACHE
+ if (rc >= 0)
+ dircache_rename(xxxxxx);
+#endif
+
+ return rc;
+}
+
+off_t sim_filesize(int fildes)
+{
+ struct filestr_desc *filestr = get_filestr(fildes);
+ if (!filestr)
+ return -1;
+
+ return os_filesize(filestr->osfd);
+}
+
+int sim_fsamefile(int fildes1, int fildes2)
+{
+ struct filestr_desc *filestr1 = get_filestr(fildes1);
+ if (!filestr1)
+ return -1;
+
+ struct filestr_desc *filestr2 = get_filestr(fildes2);
+ if (!filestr2)
+ return -1;
+
+ if (filestr1 == filestr2)
+ return 1;
+
+ return os_fsamefile(filestr1->osfd, filestr2->osfd);
+}
+
+int sim_relate(const char *path1, const char *path2)
+{
+ char ospath1[SIM_TMPBUF_MAX_PATH];
+ if (sim_get_os_path(ospath1, path1, sizeof (ospath1)) < 0)
+ return -1;
+
+ char ospath2[SIM_TMPBUF_MAX_PATH];
+ if (sim_get_os_path(ospath2, path2, sizeof (ospath2)) < 0)
+ return -1;
+
+ return os_relate(ospath1, ospath2);
+}
+
+bool sim_file_exists(const char *path)
+{
+ char ospath[SIM_TMPBUF_MAX_PATH];
+ if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0)
+ return false;
+
+ return os_file_exists(ospath);
+}
+
+
+/** Directory functions **/
+DIR * sim_opendir(const char *dirname)
+{
+ struct dirstr_desc *dirstr = alloc_dirstr();
+ if (!dirstr)
+ {
+ errno = EMFILE;
+ return NULL;
+ }
+
+ char osdirname[SIM_TMPBUF_MAX_PATH];
+ int pprc = sim_get_os_path(osdirname, dirname, sizeof (osdirname));
+ if (pprc < 0)
+ return NULL;
+
+ int rc = os_opendir_and_fd(osdirname, &dirstr->osdirp, &dirstr->osfd);
+ if (rc < 0)
+ return NULL;
+
+ dirstr->osfd_opened = rc > 0;
+
+#ifdef USE_OSDIRNAME
+ dirstr->osdirname = strdup(osdirname);
+ if (!dirstr->osdirname)
+ {
+ close_dirstr(dirstr);
+ return NULL;
+ }
+#endif
+
+ dirstr->entry.d_name[0] = 0; /* Mark as invalid */
+#ifdef HAVE_MULTIVOLUME
+ dirstr->volume = MAX(pprc - 1, 0);
+ dirstr->volumecounter = pprc == 0 ? 0 : INT_MAX;
+#endif
+ dirstr->mounted = true;
+ return (DIR *)dirstr; /* A-Okay */
+}
+
+int sim_closedir(DIR *dirp)
+{
+ struct dirstr_desc *dirstr = (struct dirstr_desc *)dirp;
+ if (!PTR_IN_ARRAY(opendirs, dirstr, MAX_OPEN_DIRS) || !dirstr->osdirp)
+ {
+ errno = EBADF;
+ return -1;
+ }
+
+ return close_dirstr(dirstr);
+}
+
+struct sim_dirent * sim_readdir(DIR *dirp)
+{
+ struct dirstr_desc *dirstr = get_dirstr(dirp);
+ if (!dirstr)
+ return NULL;
+
+ struct sim_dirent *entry = &dirstr->entry;
+ entry->info.osdirent = NULL;
+
+ if (readdir_volume(dirstr, entry))
+ return entry;
+
+ OS_DIRENT_T *osdirent = os_readdir(dirstr->osdirp);
+ if (!osdirent)
+ return NULL;
+
+ size_t size = sizeof (entry->d_name);
+ if (strlcpy_from_os(entry->d_name, osdirent->d_name, size) >= size)
+ FILE_ERROR_RETURN(ENAMETOOLONG, NULL);
+
+ entry->info.osdirent = osdirent;
+ return entry;
+}
+
+int sim_mkdir(const char *path)
+{
+ char ospath[SIM_TMPBUF_MAX_PATH];
+ if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0)
+ return -1;
+
+ int rc = os_mkdir(ospath __MKDIR_MODE_ARG);
+
+#ifdef HAVE_DIRCACHE
+ if (rc >= 0)
+ dircache_mkdir(xxxxxx);
+#endif
+
+ return rc;
+}
+
+int sim_rmdir(const char *path)
+{
+ char ospath[SIM_TMPBUF_MAX_PATH];
+ if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0)
+ return -1;
+
+ int rc = os_rmdir(ospath);
+
+#ifdef HAVE_DIRCACHE
+ if (rc >= 0)
+ dircache_rmdir(xxxxxx);
+#endif
+
+ return rc;
+}
+
+int sim_samedir(DIR *dirp1, DIR *dirp2)
+{
+ struct dirstr_desc *dirstr1 = get_dirstr(dirp1);
+ if (!dirstr1)
+ return -1;
+
+ struct dirstr_desc *dirstr2 = get_dirstr(dirp2);
+ if (!dirstr2)
+ return -1;
+
+ return os_fsamefile(dirstr1->osfd, dirstr2->osfd);
+}
+
+bool sim_dir_exists(const char *dirname)
+{
+ char osdirname[SIM_TMPBUF_MAX_PATH];
+ if (sim_get_os_path(osdirname, dirname, sizeof (osdirname)) < 0)
+ return false;
+
+ OS_DIR_T *dirp = os_opendir(osdirname);
+ if (!dirp)
+ return false;
+
+ os_closedir(dirp);
+ return true;
+}
+
+struct dirinfo dir_get_info(DIR *dirp, struct sim_dirent *entry)
+{
+ int rc;
+ struct dirstr_desc *dirstr = get_dirstr(dirp);
+ if (!dirstr)
+ FILE_ERROR(ERRNO, RC);
+
+ if (entry->d_name[0] == 0)
+ FILE_ERROR(ENOENT, RC);
+
+ OS_DIRENT_T *osdirent = entry->info.osdirent;
+ if (osdirent == NULL)
+ return (struct dirinfo){ .attribute = ATTR_MOUNT_POINT };
+
+ struct dirinfo info;
+ info.attribute = 0;
+
+ OS_STAT_T s;
+
+#ifdef os_fstatat
+ if (os_fstatat(dirstr->osfd, entry->d_name, &s, 0))
+#else /* no fstatat; build file name for stat() */
+ char statpath[SIM_TMPBUF_MAX_PATH];
+ if (path_append(statpath, dirstr->osdirname, entry->d_name,
+ sizeof (statpath)) >= sizeof (statpath))
+ {
+ FILE_ERROR(ENAMETOOLONG, RC);
+ }
+
+ if (os_stat(statpath, &s)) /* get info */
+#endif /* os_fstatat */
+ {
+ /* File size larger than 2 GB? */
+ #ifdef EOVERFLOW
+ if (errno == EOVERFLOW)
+ DEBUGF("stat overflow for \"%s\"\n", entry->d_name);
+ #endif
+ FILE_ERROR(ERRNO, RC);
+ }
+
+ if (S_ISDIR(s.st_mode))
+ info.attribute |= ATTR_DIRECTORY;
+
+ info.size = s.st_size;
+
+ struct tm tm;
+ if (localtime_r(&s.st_mtime, &tm) == NULL)
+ FILE_ERROR(ERRNO, RC);
+
+ info.mtime = mktime(&tm);
+
+ return info;
+
+file_error:
+ return (struct dirinfo){ .size = 0 };
+}
+
+int os_volume_path(IF_MV(int volume, ) char *buffer, size_t bufsize)
+{
+ if (!buffer || !bufsize IF_MV( || !volume_present(volume) ))
+ return -1;
+
+ char tmpbuf[SIM_TMPBUF_MAX_PATH];
+ tmpbuf[0] = '\0';
+
+#ifdef HAVE_MULTIVOLUME
+ char volname[VOL_MAX_LEN + 1];
+ get_volume_name(volume, volname);
+
+ if (path_append(tmpbuf, PA_SEP_HARD, volname, sizeof (volname))
+ >= sizeof (volname))
+ return -1;
+#endif /* HAVE_MULTIVOLUME */
+
+ if (path_append(tmpbuf, PA_SEP_HARD, ".", sizeof (tmpbuf))
+ >= sizeof (tmpbuf))
+ return -1;
+
+ return sim_get_os_path(buffer, tmpbuf, bufsize);
+}
diff --git a/uisimulator/common/filesystem-sim.h b/uisimulator/common/filesystem-sim.h
new file mode 100644
index 0000000000..7c46c449d8
--- /dev/null
+++ b/uisimulator/common/filesystem-sim.h
@@ -0,0 +1,108 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#ifndef _FILESYSTEM_SIM_H_
+#define _FILESYSTEM_SIM_H_
+
+#if defined(PLUGIN) || defined(CODEC)
+/* Prevent often-problematic plugin namespace pollution */
+#define FILEFUNCTIONS_DECLARED
+#define FILEFUNCTIONS_DEFINED
+#define DIRFUNCTIONS_DECLARED
+#define DIRFUNCTIONS_DEFINED
+#define OSFUNCTIONS_DECLARED
+#endif /* PLUGIN || CODEC */
+
+#ifndef OSFUNCTIONS_DECLARED
+#define FS_PREFIX(_x_) sim_ ## _x_
+
+/* path sandboxing and volume redirector */
+int sim_get_os_path(char *buffer, const char *path, size_t bufsize);
+#define SIM_TMPBUF_MAX_PATH (MAX_PATH+1)
+
+#endif /* !OSFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_SIM_H_ */
+
+#include "filesystem-sdl.h"
+#ifdef WIN32
+#include "filesystem-win32.h"
+#else /* !WIN32 */
+#include "filesystem-unix.h"
+#endif /* WIN32 */
+#include "filesystem-hosted.h"
+
+#ifdef _FILE_H_
+#ifndef _FILESYSTEM_SIM_H__FILE_H_
+#define _FILESYSTEM_SIM_H__FILE_H_
+
+#ifdef RB_FILESYSTEM_OS
+#define FILEFUNCTIONS_DEFINED
+#endif
+
+#ifndef FILEFUNCTIONS_DECLARED
+int sim_open(const char *name, int oflag, ...);
+int sim_creat(const char *name, mode_t mode);
+int sim_close(int fildes);
+int sim_ftruncate(int fildes, off_t length);
+int sim_fsync(int fildes);
+off_t sim_lseek(int fildes, off_t offset, int whence);
+ssize_t sim_read(int fildes, void *buf, size_t nbyte);
+ssize_t sim_write(int fildes, const void *buf, size_t nbyte);
+int sim_remove(const char *path);
+int sim_rename(const char *old, const char *new);
+off_t sim_filesize(int fildes);
+int sim_fsamefile(int fildes1, int fildes2);
+int sim_relate(const char *path1, const char *path2);
+bool sim_file_exists(const char *path);
+#endif /* !FILEFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_SIM_H__FILE_H_ */
+#endif /* _FILE_H_ */
+
+#ifdef _DIR_H_
+#ifndef _FILESYSTEM_SIM_H__DIR_H_
+#define _FILESYSTEM_SIM_H__DIR_H_
+
+#ifdef RB_FILESYSTEM_OS
+#define DIRFUNCTIONS_DEFINED
+#else
+#define dirent DIRENT
+#endif
+
+#define DIRENT sim_dirent
+
+struct dirinfo_native
+{
+ void *osdirent;
+};
+
+#ifndef DIRFUNCTIONS_DECLARED
+DIR * sim_opendir(const char *dirname);
+struct sim_dirent * sim_readdir(DIR *dirp);
+int sim_closedir(DIR *dirp);
+int sim_mkdir(const char *path);
+int sim_rmdir(const char *path);
+int sim_samedir(DIR *dirp1, DIR *dirp2);
+bool sim_dir_exists(const char *dirname);
+#endif /* !DIRFUNCTIONS_DECLARED */
+
+#endif /* _FILESYSTEM_SIM_H__DIR_H_ */
+#endif /* _DIR_H_ */
diff --git a/uisimulator/common/io.c b/uisimulator/common/io.c
deleted file mode 100644
index 6662e9ffda..0000000000
--- a/uisimulator/common/io.c
+++ /dev/null
@@ -1,729 +0,0 @@
-/***************************************************************************
- * __________ __ ___.
- * Open \______ \ ____ ____ | | _\_ |__ _______ ___
- * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
- * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
- * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
- * \/ \/ \/ \/ \/
- * $Id$
- *
- * Copyright (C) 2002 Daniel Stenberg
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
- * KIND, either express or implied.
- *
- ****************************************************************************/
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <sys/stat.h>
-#include <time.h>
-#include <errno.h>
-#include "config.h"
-#include "system.h"
-#include "ata_idle_notify.h"
-#include "mv.h"
-
-#define HAVE_STATVFS (!defined(WIN32))
-#define HAVE_LSTAT (!defined(WIN32))
-
-#if HAVE_STATVFS
-#include <sys/statvfs.h>
-#endif
-
-#ifdef WIN32
-#include <windows.h>
-#endif
-
-#ifndef _MSC_VER
-#include <dirent.h>
-#include <unistd.h>
-#else
-#include "dir-win32.h"
-#endif
-
-#include <fcntl.h>
-#ifdef HAVE_SDL_THREADS
-#include "thread-sdl.h"
-#else
-#define sim_thread_unlock() NULL
-#define sim_thread_lock(a)
-#endif
-#include "thread.h"
-#include "kernel.h"
-#include "debug.h"
-#include "ata.h" /* for IF_MV et al. */
-#include "rbpaths.h"
-#include "load_code.h"
-
-/* keep this in sync with file.h! */
-#undef MAX_PATH /* this avoids problems when building simulator */
-#define MAX_PATH 260
-#define MAX_OPEN_FILES 11
-
-/* Windows (and potentially other OSes) distinguish binary and text files.
- * Define a dummy for the others. */
-#ifndef O_BINARY
-#define O_BINARY 0
-#endif
-
-/* Unicode compatibility for win32 */
-#if defined __MINGW32__
-/* Rockbox unicode functions */
-extern const unsigned char* utf8decode(const unsigned char *utf8,
- unsigned short *ucs);
-extern unsigned char* utf8encode(unsigned long ucs, unsigned char *utf8);
-
-/* Static buffers for the conversion results. This isn't thread safe,
- * but it's sufficient for rockbox. */
-static unsigned char convbuf1[3*MAX_PATH];
-static unsigned char convbuf2[3*MAX_PATH];
-
-static wchar_t* utf8_to_ucs2(const unsigned char *utf8, void *buffer)
-{
- wchar_t *ucs = buffer;
-
- while (*utf8)
- utf8 = utf8decode(utf8, ucs++);
-
- *ucs = 0;
- return buffer;
-}
-static unsigned char *ucs2_to_utf8(const wchar_t *ucs, unsigned char *buffer)
-{
- unsigned char *utf8 = buffer;
-
- while (*ucs)
- utf8 = utf8encode(*ucs++, utf8);
-
- *utf8 = 0;
- return buffer;
-}
-
-#define UTF8_TO_OS(a) utf8_to_ucs2(a,convbuf1)
-#define OS_TO_UTF8(a) ucs2_to_utf8(a,convbuf1)
-#define DIR_T _WDIR
-#define DIRENT_T struct _wdirent
-#define STAT_T struct _stat
-extern int _wmkdir(const wchar_t*);
-extern int _wrmdir(const wchar_t*);
-#define MKDIR(a,b) (_wmkdir)(UTF8_TO_OS(a))
-#define RMDIR(a) (_wrmdir)(UTF8_TO_OS(a))
-#define OPENDIR(a) (_wopendir)(UTF8_TO_OS(a))
-#define READDIR(a) (_wreaddir)(a)
-#define CLOSEDIR(a) (_wclosedir)(a)
-#define STAT(a,b) (_wstat)(UTF8_TO_OS(a),b)
-/* empty variable parameter list doesn't work for variadic macros,
- * so pretend the second parameter is variable too */
-#define OPEN(a,...) (_wopen)(UTF8_TO_OS(a), __VA_ARGS__)
-#define CLOSE(a) (close)(a)
-#define REMOVE(a) (_wremove)(UTF8_TO_OS(a))
-#define RENAME(a,b) (_wrename)(UTF8_TO_OS(a),utf8_to_ucs2(b,convbuf2))
-/* readlink isn't used in the sim yet (FIXME) */
-#define READLINK(a,b,c) ({ fprintf(stderr, "no readlink on windows yet"); abort(); })
-#else /* !__MINGW32__ */
-
-#define UTF8_TO_OS(a) (a)
-#define OS_TO_UTF8(a) (a)
-#define DIR_T DIR
-#define DIRENT_T struct dirent
-#define STAT_T struct stat
-#define MKDIR(a,b) (mkdir)(a,b)
-#define RMDIR(a) (rmdir)(a)
-#define OPENDIR(a) (opendir)(a)
-#define READDIR(a) (readdir)(a)
-#define CLOSEDIR(a) (closedir)(a)
-#define STAT(a,b) (stat)(a,b)
-/* empty variable parameter list doesn't work for variadic macros,
- * so pretend the second parameter is variable too */
-#define OPEN(a, ...) (open)(a, __VA_ARGS__)
-#define CLOSE(x) (close)(x)
-#define REMOVE(a) (remove)(a)
-#define RENAME(a,b) (rename)(a,b)
-#define READLINK(a,b,c) (readlink)(a,b,c)
-
-#endif /* !__MINGW32__ */
-
-
-#ifdef HAVE_DIRCACHE
-int dircache_get_entry_id(const char *filename);
-void dircache_add_file(const char *name, long startcluster);
-void dircache_remove(const char *name);
-void dircache_rename(const char *oldname, const char *newname);
-#endif
-
-#ifndef APPLICATION
-
-#define SIMULATOR_DEFAULT_ROOT "simdisk"
-extern const char *sim_root_dir;
-
-static int num_openfiles = 0;
-
-/* from dir.h */
-struct dirinfo {
- int attribute;
- long size;
- unsigned short wrtdate;
- unsigned short wrttime;
-};
-
-struct sim_dirent {
- unsigned char d_name[MAX_PATH];
- struct dirinfo info;
- long startcluster;
-};
-
-struct dirstruct {
- void *dir; /* actually a DIR* dir */
- char *name;
-} SIM_DIR;
-
-struct mydir {
- DIR_T *dir;
- IF_MV(int volumes_returned);
- char *name;
-};
-
-typedef struct mydir MYDIR;
-
-static unsigned int rockbox2sim(int opt)
-{
-#if 0
-/* this shouldn't be needed since we use the host's versions */
- int newopt = O_BINARY;
-
- if(opt & 1)
- newopt |= O_WRONLY;
- if(opt & 2)
- newopt |= O_RDWR;
- if(opt & 4)
- newopt |= O_CREAT;
- if(opt & 8)
- newopt |= O_APPEND;
- if(opt & 0x10)
- newopt |= O_TRUNC;
-
- return newopt;
-#else
- return opt|O_BINARY;
-#endif
-}
-
-#endif /* APPLICATION */
-
-/** Simulator I/O engine routines **/
-#define IO_YIELD_THRESHOLD 512
-
-enum io_dir
-{
- IO_READ,
- IO_WRITE,
-};
-
-struct sim_io
-{
- struct mutex sim_mutex; /* Rockbox mutex */
- int cmd; /* The command to perform */
- int ready; /* I/O ready flag - 1= ready */
- int fd; /* The file to read/write */
- void *buf; /* The buffer to read/write */
- size_t count; /* Number of bytes to read/write */
- size_t accum; /* Acculated bytes transferred */
-};
-
-static struct sim_io io;
-
-int ata_init(void)
-{
- /* Initialize the rockbox kernel objects on a rockbox thread */
- mutex_init(&io.sim_mutex);
- io.accum = 0;
- return 1;
-}
-
-int ata_spinup_time(void)
-{
- return HZ;
-}
-
-static ssize_t io_trigger_and_wait(enum io_dir cmd)
-{
- void *mythread = NULL;
- ssize_t result;
-
- if (io.count > IO_YIELD_THRESHOLD ||
- (io.accum += io.count) >= IO_YIELD_THRESHOLD)
- {
- /* Allow other rockbox threads to run */
- io.accum = 0;
- mythread = sim_thread_unlock();
- }
-
- switch (cmd)
- {
- case IO_READ:
- result = read(io.fd, io.buf, io.count);
- break;
- case IO_WRITE:
- result = write(io.fd, io.buf, io.count);
- break;
- /* shut up gcc */
- default:
- result = -1;
- }
-
- call_storage_idle_notifys(false);
-
- /* Regain our status as current */
- if (mythread != NULL)
- {
- sim_thread_lock(mythread);
- }
-
- return result;
-}
-
-
-ssize_t sim_read(int fd, void *buf, size_t count)
-{
- ssize_t result;
-
- mutex_lock(&io.sim_mutex);
-
- /* Setup parameters */
- io.fd = fd;
- io.buf = buf;
- io.count = count;
-
- result = io_trigger_and_wait(IO_READ);
-
- mutex_unlock(&io.sim_mutex);
-
- return result;
-}
-
-
-ssize_t sim_write(int fd, const void *buf, size_t count)
-{
- ssize_t result;
-
- mutex_lock(&io.sim_mutex);
-
- io.fd = fd;
- io.buf = (void*)buf;
- io.count = count;
-
- result = io_trigger_and_wait(IO_WRITE);
-
- mutex_unlock(&io.sim_mutex);
-
- return result;
-}
-
-#if !defined(APPLICATION)
-
-static const char *handle_special_links(const char* link)
-{
-#ifdef HAVE_MULTIDRIVE
- static char buffer[MAX_PATH]; /* sufficiently big */
- char vol_string[VOL_ENUM_POS + 8];
- int len = sprintf(vol_string, VOL_NAMES, 1);
-
- /* link might be passed with or without HOME_DIR expanded. To handle
- * both perform substring matching (VOL_NAMES is unique enough) */
- const char *begin = strstr(link, vol_string);
- if (begin)
- {
- /* begin now points to the start of vol_string within link,
- * we want to copy the remainder of the paths, prefixed by
- * the actual mount point (the remainder might be "") */
- snprintf(buffer, sizeof(buffer), "%s/../simext/%s",
- sim_root_dir ?: SIMULATOR_DEFAULT_ROOT, begin + len);
- return buffer;
- }
- else
-#endif
- return link;
-}
-
-
-static const char *get_sim_pathname(const char *name)
-{
- static char buffer[MAX_PATH]; /* sufficiently big */
-
- if(name[0] == '/')
- {
- snprintf(buffer, sizeof(buffer), "%s%s",
- sim_root_dir ?: SIMULATOR_DEFAULT_ROOT, name);
- return handle_special_links(buffer);
- }
- fprintf(stderr, "WARNING, bad file name lacks slash: %s\n", name);
- return name;
-}
-
-
-MYDIR *sim_opendir(const char *name)
-{
- DIR_T *dir;
- dir = (DIR_T *) OPENDIR(get_sim_pathname(name));
-
- if (dir)
- {
- MYDIR *my = (MYDIR *)malloc(sizeof(MYDIR));
- my->dir = dir;
- my->name = (char *)malloc(strlen(name)+1);
- strcpy(my->name, name);
- IF_MV(my->volumes_returned = 0);
-
- return my;
- }
- /* failed open, return NULL */
- return (MYDIR *)0;
-}
-
-#if defined(WIN32)
-static inline struct tm* localtime_r (const time_t *clock, struct tm *result) {
- if (!clock || !result) return NULL;
- memcpy(result,localtime(clock),sizeof(*result));
- return result;
-}
-#endif
-
-struct sim_dirent *sim_readdir(MYDIR *dir)
-{
- char buffer[MAX_PATH]; /* sufficiently big */
- static struct sim_dirent secret;
- STAT_T s;
- struct tm tm;
- DIRENT_T *x11;
-
-#ifdef EOVERFLOW
-read_next:
-#endif
-
-#define ATTR_LINK 0x80 /* see dir.h */
-
- secret.info.attribute = 0;
-#ifdef HAVE_MULTIVOLUME
- if (dir->name[0] == '/' && dir->name[1] == '\0'
- && dir->volumes_returned++ < (NUM_VOLUMES-1)
- && volume_present(dir->volumes_returned))
- {
- sprintf((char *)secret.d_name, VOL_NAMES, dir->volumes_returned);
- secret.info.attribute = ATTR_LINK;
- /* build file name for stat() which is the actual mount point */
- snprintf(buffer, sizeof(buffer), "%s/../simext",
- sim_root_dir ?: SIMULATOR_DEFAULT_ROOT);
- }
- else
-#endif
- {
- x11 = READDIR(dir->dir);
-
- if(!x11)
- return (struct sim_dirent *)0;
-
- strcpy((char *)secret.d_name, OS_TO_UTF8(x11->d_name));
- /* build file name for stat() */
- snprintf(buffer, sizeof(buffer), "%s/%s",
- get_sim_pathname(dir->name), secret.d_name);
- }
-
- if (STAT(buffer, &s)) /* get info */
- {
-#ifdef EOVERFLOW
- /* File size larger than 2 GB? */
- if (errno == EOVERFLOW)
- {
- DEBUGF("stat() overflow for %s. Skipping\n", buffer);
- goto read_next;
- }
-#endif
-
- return NULL;
- }
-
-#define ATTR_DIRECTORY 0x10
-
- if (S_ISDIR(s.st_mode))
- secret.info.attribute = ATTR_DIRECTORY;
-
- secret.info.size = s.st_size;
-
- if (localtime_r(&(s.st_mtime), &tm) == NULL)
- return NULL;
- secret.info.wrtdate = ((tm.tm_year - 80) << 9) |
- ((tm.tm_mon + 1) << 5) |
- tm.tm_mday;
- secret.info.wrttime = (tm.tm_hour << 11) |
- (tm.tm_min << 5) |
- (tm.tm_sec >> 1);
-
-#if HAVE_LSTAT
- if (!lstat(buffer, &s) && S_ISLNK(s.st_mode))
- {
- secret.info.attribute |= ATTR_LINK;
- }
-#endif
-
- return &secret;
-}
-
-void sim_closedir(MYDIR *dir)
-{
- free(dir->name);
- CLOSEDIR(dir->dir);
-
- free(dir);
-}
-
-int sim_open(const char *name, int o, ...)
-{
- int opts = rockbox2sim(o);
- int ret;
- if (num_openfiles >= MAX_OPEN_FILES)
- return -2;
-
- if (opts & O_CREAT)
- {
- va_list ap;
- va_start(ap, o);
- mode_t mode = va_arg(ap, unsigned int);
- ret = OPEN(get_sim_pathname(name), opts, mode);
-#ifdef HAVE_DIRCACHE
- if (ret >= 0 && (dircache_get_entry_id(name) < 0))
- dircache_add_file(name, 0);
-#endif
- va_end(ap);
- }
- else
- ret = OPEN(get_sim_pathname(name), opts);
-
- if (ret >= 0)
- num_openfiles++;
- return ret;
-}
-
-int sim_close(int fd)
-{
- int ret;
- ret = CLOSE(fd);
- if (ret == 0)
- num_openfiles--;
- return ret;
-}
-
-int sim_creat(const char *name, mode_t mode)
-{
- int ret = OPEN(get_sim_pathname(name),
- O_BINARY | O_WRONLY | O_CREAT | O_TRUNC, mode);
-#ifdef HAVE_DIRCACHE
- if (ret >= 0 && (dircache_get_entry_id(name) < 0))
- dircache_add_file(name, 0);
-#endif
- return ret;
-}
-
-int sim_mkdir(const char *name)
-{
- return MKDIR(get_sim_pathname(name), 0777);
-}
-
-int sim_rmdir(const char *name)
-{
- return RMDIR(get_sim_pathname(name));
-}
-
-int sim_remove(const char *name)
-{
- int ret = REMOVE(get_sim_pathname(name));
-#ifdef HAVE_DIRCACHE
- if (ret >= 0)
- dircache_remove(name);
-#endif
- return ret;
-}
-
-int sim_rename(const char *oldname, const char *newname)
-{
- char sim_old[MAX_PATH];
- char sim_new[MAX_PATH];
-#ifdef HAVE_DIRCACHE
- dircache_rename(oldname, newname);
-#endif
- // This is needed as get_sim_pathname() has a static buffer
- strncpy(sim_old, get_sim_pathname(oldname), MAX_PATH);
- strncpy(sim_new, get_sim_pathname(newname), MAX_PATH);
- return RENAME(sim_old, sim_new);
-}
-
-/* rockbox off_t may be different from system off_t */
-long sim_lseek(int fildes, long offset, int whence)
-{
- return lseek(fildes, offset, whence);
-}
-
-#else
-#define get_sim_pathname(x) x
-#endif
-
-long filesize(int fd)
-{
-#ifdef WIN32
- return _filelength(fd);
-#else
- struct stat buf;
-
- if (!fstat(fd, &buf))
- return buf.st_size;
- else
- return -1;
-#endif
-}
-
-void fat_size(IF_MV(int volume,) unsigned long* size, unsigned long* free)
-{
-#ifdef HAVE_MULTIVOLUME
- if (volume != 0) {
- /* debugf("io.c: fat_size(volume=%d); simulator only supports volume 0\n",volume); */
-
- if (size) *size = 0;
- if (free) *free = 0;
- return;
- }
-#endif
-
-#ifdef WIN32
- long secperclus, bytespersec, free_clusters, num_clusters;
-
- if (GetDiskFreeSpace(NULL, &secperclus, &bytespersec, &free_clusters,
- &num_clusters)) {
- if (size)
- *size = num_clusters * secperclus / 2 * (bytespersec / 512);
- if (free)
- *free = free_clusters * secperclus / 2 * (bytespersec / 512);
- } else
-#elif HAVE_STATVFS
- struct statvfs vfs;
-
- if (!statvfs(".", &vfs)) {
- DEBUGF("statvfs: frsize=%d blocks=%ld free=%ld\n",
- (int)vfs.f_frsize, (long)vfs.f_blocks, (long)vfs.f_bfree);
- if (size)
- *size = vfs.f_blocks / 2 * (vfs.f_frsize / 512);
- if (free)
- *free = vfs.f_bfree / 2 * (vfs.f_frsize / 512);
- } else
-#endif
- {
- if (size)
- *size = 0;
- if (free)
- *free = 0;
- }
-}
-
-int sim_fsync(int fd)
-{
-#ifdef WIN32
- return _commit(fd);
-#else
- return fsync(fd);
-#endif
-}
-
-#ifndef __PCTOOL__
-
-#include <SDL_loadso.h>
-void *lc_open(const char *filename, unsigned char *buf, size_t buf_size)
-{
- (void)buf;
- (void)buf_size;
- void *handle = SDL_LoadObject(get_sim_pathname(filename));
- if (handle == NULL)
- {
- DEBUGF("failed to load %s\n", filename);
- DEBUGF("lc_open(%s): %s\n", filename, SDL_GetError());
- }
- return handle;
-}
-
-void *lc_get_header(void *handle)
-{
- char *ret = SDL_LoadFunction(handle, "__header");
- if (ret == NULL)
- ret = SDL_LoadFunction(handle, "___header");
-
- return ret;
-}
-
-void lc_close(void *handle)
-{
- SDL_UnloadObject(handle);
-}
-
-void *lc_open_from_mem(void *addr, size_t blob_size)
-{
-#ifndef SIMULATOR
- (void)addr;
- (void)blob_size;
- /* we don't support loading code from memory on application builds,
- * it doesn't make sense (since it means writing the blob to disk again and
- * then falling back to load from disk) and requires the ability to write
- * to an executable directory */
- return NULL;
-#else
- /* support it in the sim for the sake of simulating */
- int fd, i;
- char temp_filename[MAX_PATH];
-
- /* We have to create the dynamic link library file from ram so we
- can simulate the codec loading. With voice and crossfade,
- multiple codecs may be loaded at the same time, so we need
- to find an unused filename */
- for (i = 0; i < 10; i++)
- {
- snprintf(temp_filename, sizeof(temp_filename),
- ROCKBOX_DIR "/libtemp_binary_%d.dll", i);
- fd = open(temp_filename, O_WRONLY|O_CREAT|O_TRUNC, 0700);
- if (fd >= 0)
- break; /* Created a file ok */
- }
-
- if (fd < 0)
- {
- DEBUGF("open failed\n");
- return NULL;
- }
-
- if (write(fd, addr, blob_size) < (ssize_t)blob_size)
- {
- DEBUGF("Write failed\n");
- close(fd);
- remove(temp_filename);
- return NULL;
- }
-
- close(fd);
- return lc_open(temp_filename, NULL, 0);
-#endif
-}
-
-#endif /* __PCTOOL__ */
-
-/* rockbox off_t may be different from system off_t */
-int sim_ftruncate(int fd, long length)
-{
-#ifdef WIN32
- return _chsize(fd, length);
-#else
- return ftruncate(fd, length);
-#endif
-}
diff --git a/uisimulator/common/load_code-sim.c b/uisimulator/common/load_code-sim.c
new file mode 100644
index 0000000000..59ca97a259
--- /dev/null
+++ b/uisimulator/common/load_code-sim.c
@@ -0,0 +1,72 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 Daniel Stenberg
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#define RB_FILESYSTEM_OS
+#include <stdio.h>
+#include "config.h"
+#include "system.h"
+#include "file.h"
+#include "load_code.h"
+#include "rbpaths.h"
+#include "debug.h"
+
+void * lc_open(const char *filename, unsigned char *buf, size_t buf_size)
+{
+ char osfilename[SIM_TMPBUF_MAX_PATH];
+ if (sim_get_os_path(osfilename, filename, sizeof (osfilename)) < 0)
+ return NULL;
+
+ return os_lc_open(osfilename);
+ (void)buf; (void)buf_size;
+}
+
+void * lc_open_from_mem(void *addr, size_t blob_size)
+{
+ /* We have to create the dynamic link library file from ram so we can
+ simulate code loading. Multiple binaries may be loaded at the same
+ time, so we need to find an unused filename. */
+ int fd;
+ char tempfile[SIM_TMPBUF_MAX_PATH];
+ for (unsigned int i = 0; i < 10; i++)
+ {
+ snprintf(tempfile, sizeof(tempfile),
+ ROCKBOX_DIR "/libtemp_binary_%d.dll", i);
+ fd = sim_open(tempfile, O_WRONLY|O_CREAT|O_TRUNC, 0700);
+ if (fd >= 0)
+ break; /* Created a file ok */
+ }
+
+ if (fd < 0)
+ {
+ DEBUGF("open failed\n");
+ return NULL;
+ }
+
+ if (sim_write(fd, addr, blob_size) != (ssize_t)blob_size)
+ {
+ DEBUGF("Write failed\n");
+ sim_close(fd);
+ sim_remove(tempfile);
+ return NULL;
+ }
+
+ sim_close(fd);
+ return lc_open(tempfile, NULL, 0);
+}
diff --git a/uisimulator/common/sim_tasks.c b/uisimulator/common/sim_tasks.c
index 1299a69302..003b993740 100644
--- a/uisimulator/common/sim_tasks.c
+++ b/uisimulator/common/sim_tasks.c
@@ -28,6 +28,11 @@
#include "thread.h"
#include "debug.h"
#include "usb.h"
+#include "mv.h"
+#include "ata_idle_notify.h"
+#ifdef WIN32
+#include <windows.h>
+#endif
static void sim_thread(void);
static long sim_thread_stack[DEFAULT_STACK_SIZE/sizeof(long)];
@@ -46,6 +51,10 @@ enum {
#endif
};
+#ifdef HAVE_MULTIDRIVE
+extern void sim_ext_extracted(int drive);
+#endif
+
void sim_thread(void)
{
struct queue_event ev;
@@ -54,9 +63,13 @@ void sim_thread(void)
while (1)
{
- queue_wait(&sim_queue, &ev);
+ queue_wait_w_tmo(&sim_queue, &ev, 5*HZ);
switch(ev.id)
{
+ case SYS_TIMEOUT:
+ call_storage_idle_notifys(false);
+ break;
+
case SIM_SCREENDUMP:
screen_dump();
#ifdef HAVE_REMOTE_LCD
@@ -102,6 +115,7 @@ void sim_thread(void)
#ifdef HAVE_MULTIDRIVE
case SIM_EXT_INSERTED:
case SIM_EXT_EXTRACTED:
+ sim_ext_extracted(ev.data);
queue_broadcast(ev.id == SIM_EXT_INSERTED ?
SYS_HOTSWAP_INSERTED : SYS_HOTSWAP_EXTRACTED, 0);
sleep(HZ/20);
@@ -174,11 +188,13 @@ static bool is_ext_inserted;
void sim_trigger_external(bool inserted)
{
+ is_ext_inserted = inserted;
+
+ int drive = 1; /* Can do others! */
if (inserted)
- queue_post(&sim_queue, SIM_EXT_INSERTED, 0);
+ queue_post(&sim_queue, SIM_EXT_INSERTED, drive);
else
- queue_post(&sim_queue, SIM_EXT_EXTRACTED, 0);
- is_ext_inserted = inserted;
+ queue_post(&sim_queue, SIM_EXT_EXTRACTED, drive);
}
bool hostfs_present(int drive)
@@ -203,7 +219,13 @@ bool volume_present(int volume)
/* volume == drive for now */
return hostfs_present(volume);
}
-#endif
+
+int volume_drive(int volume)
+{
+ /* volume == drive for now */
+ return volume;
+}
+#endif /* HAVE_MULTIVOLUME */
#if (CONFIG_STORAGE & STORAGE_MMC)
bool mmc_touched(void)
@@ -212,4 +234,4 @@ bool mmc_touched(void)
}
#endif
-#endif
+#endif /* CONFIG_STORAGE & STORAGE_MMC */
diff --git a/uisimulator/common/stubs.c b/uisimulator/common/stubs.c
index 18f60ce6b3..922b9e8da0 100644
--- a/uisimulator/common/stubs.c
+++ b/uisimulator/common/stubs.c
@@ -153,10 +153,9 @@ int fat_startsector(void)
return 63;
}
-bool fat_ismounted(int volume)
+int ata_spinup_time(void)
{
- (void)volume;
- return true;
+ return 100;
}
int storage_spinup_time(void)
diff --git a/uisimulator/common/time-win32.c b/uisimulator/common/time-win32.c
new file mode 100644
index 0000000000..1de69c35e6
--- /dev/null
+++ b/uisimulator/common/time-win32.c
@@ -0,0 +1,61 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * 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
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <time.h>
+#include <errno.h>
+#include "system.h"
+#include "debug.h"
+
+/* These functions we like but Windows doesn't because it implements the
+ non-"_r" versions with thread-local storage in its multithreaded libs */
+
+struct tm * localtime_r(const time_t *restrict timer,
+ struct tm *restrict result)
+{
+ if (!timer || !result)
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ struct tm *tm = localtime(timer);
+ if (!tm)
+ return NULL;
+
+ *result = *tm;
+ return result;
+}
+
+struct tm * gmtime_r(const time_t *restrict timer,
+ struct tm *restrict result)
+{
+ if (!timer || !result)
+ {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ struct tm *tm = gmtime(timer);
+ if (!tm)
+ return NULL;
+
+ *result = *tm;
+ return result;
+}