summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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 cl