summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2013-08-05 22:02:45 -0400
committerMichael Sevakis <jethead71@rockbox.org>2014-08-30 03:48:23 +0200
commit7d1a47cf13726c95ac46027156cc12dd9da5b855 (patch)
treeeb20d07656806479a8e1fea25887a490ea30d1d8
parent95a4c3afcd53a1f8b835dec33de51f9c304de4d9 (diff)
downloadrockbox-7d1a47cf13726c95ac46027156cc12dd9da5b855.tar.gz
rockbox-7d1a47cf13726c95ac46027156cc12dd9da5b855.zip
Rewrite filesystem code (WIP)
This patch redoes the filesystem code from the FAT driver up to the clipboard code in onplay.c. Not every aspect of this is finished therefore it is still "WIP". I don't wish to do too much at once (haha!). What is left to do is get dircache back in the sim and find an implementation for the dircache indicies in the tagcache and playlist code or do something else that has the same benefit. Leaving these out for now does not make anything unusable. All the basics are done. Phone app code should probably get vetted (and app path handling just plain rewritten as environment expansions); the SDL app and Android run well. Main things addressed: 1) Thread safety: There is none right now in the trunk code. Most of what currently works is luck when multiple threads are involved or multiple descriptors to the same file are open. 2) POSIX compliance: Many of the functions behave nothing like their counterparts on a host system. This leads to inconsistent code or very different behavior from native to hosted. One huge offender was rename(). Going point by point would fill a book. 3) Actual running RAM usage: Many targets will use less RAM and less stack space (some more RAM because I upped the number of cache buffers for large memory). There's very little memory lying fallow in rarely-used areas (see 'Key core changes' below). Also, all targets may open the same number of directory streams whereas before those with less than 8MB RAM were limited to 8, not 12 implying those targets will save slightly less. 4) Performance: The test_disk plugin shows markedly improved performance, particularly in the area of (uncached) directory scanning, due partly to more optimal directory reading and to a better sector cache algorithm. Uncached times tend to be better while there is a bit of a slowdown in dircache due to it being a bit heavier of an implementation. It's not noticeable by a human as far as I can say. Key core changes: 1) Files and directories share core code and data structures. 2) The filesystem code knows which descriptors refer to same file. This ensures that changes from one stream are appropriately reflected in every open descriptor for that file (fileobj_mgr.c). 3) File and directory cache buffers are borrowed from the main sector cache. This means that when they are not in use by a file, they are not wasted, but used for the cache. Most of the time, only a few of them are needed. It also means that adding more file and directory handles is less expensive. All one must do in ensure a large enough cache to borrow from. 4) Relative path components are supported and the namespace is unified. It does not support full relative paths to an implied current directory; what is does support is use of "." and "..". Adding the former would not be very difficult. The namespace is unified in the sense that volumes may be specified several times along with relative parts, e.g.: "/<0>/foo/../../<1>/bar" :<=> "/<1>/bar". 5) Stack usage is down due to sharing of data, static allocation and less duplication of strings on the stack. This requires more serialization than I would like but since the number of threads is limited to a low number, the tradoff in favor of the stack seems reasonable. 6) Separates and heirarchicalizes (sic) the SIM and APP filesystem code. SIM path and volume handling is just like the target. Some aspects of the APP file code get more straightforward (e.g. no path hashing is needed). Dircache: Deserves its own section. Dircache is new but pays homage to the old. The old one was not compatible and so it, since it got redone, does all the stuff it always should have done such as: 1) It may be update and used at any time during the build process. No longer has one to wait for it to finish building to do basic file management (create, remove, rename, etc.). 2) It does not need to be either fully scanned or completely disabled; it can be incomplete (i.e. overfilled, missing paths), still be of benefit and be correct. 3) Handles mounting and dismounting of individual volumes which means a full rebuild is not needed just because you pop a new SD card in the slot. Now, because it reuses its freed entry data, may rebuild only that volume. 4) Much more fundamental to the file code. When it is built, it is the keeper of the master file list whether enabled or not ("disabled" is just a state of the cache). Its must always to ready to be started and bind all streams opened prior to being enabled. 5) Maintains any short filenames in OEM format which means that it does not need to be rebuilt when changing the default codepage. Miscellaneous Compatibility: 1) Update any other code that would otherwise not work such as the hotswap mounting code in various card drivers. 2) File management: Clipboard needed updating because of the behavioral changes. Still needs a little more work on some finer points. 3) Remove now-obsolete functionality such as the mutex's "no preempt" flag (which was only for the prior FAT driver). 4) struct dirinfo uses time_t rather than raw FAT directory entry time fields. I plan to follow up on genericizing everything there (i.e. no FAT attributes). 5) unicode.c needed some redoing so that the file code does not try try to load codepages during a scan, which is actually a problem with the current code. The default codepage, if any is required, is now kept in RAM separarately (bufalloced) from codepages specified to iso_decode() (which must not be bufalloced because the conversion may be done by playback threads). Brings with it some additional reusable core code: 1) Revised file functions: Reusable code that does things such as safe path concatenation and parsing without buffer limitations or data duplication. Variants that copy or alter the input path may be based off these. To do: 1) Put dircache functionality back in the sim. Treating it internally as a different kind of file system seems the best approach at this time. 2) Restore use of dircache indexes in the playlist and database or something effectively the same. Since the cache doesn't have to be complete in order to be used, not getting a hit on the cache doesn't unambiguously say if the path exists or not. Change-Id: Ia30f3082a136253e3a0eae0784e3091d138915c8 Reviewed-on: http://gerrit.rockbox.org/566 Reviewed-by: Michael Sevakis <jethead71@rockbox.org> Tested: Michael Sevakis <jethead71@rockbox.org>
-rw-r--r--apps/bookmark.c8
-rw-r--r--apps/codecs.c2
-rw-r--r--apps/debug_menu.c86
-rw-r--r--apps/filetree.c4
-rw-r--r--apps/main.c102
-rw-r--r--apps/menus/display_menu.c20
-rw-r--r--apps/menus/main_menu.c18
-rw-r--r--apps/menus/settings_menu.c6
-rw-r--r--apps/misc.c16
-rw-r--r--apps/onplay.c844
-rwxr-xr-xapps/playlist.c237
-rw-r--r--apps/playlist_catalog.c2
-rw-r--r--apps/plugin.c264
-rw-r--r--apps/plugin.h45
-rw-r--r--apps/plugins/properties.c14
-rw-r--r--apps/radio/presets.c2
-rw-r--r--apps/radio/radioart.c2
-rw-r--r--apps/recorder/albumart.c2
-rw-r--r--apps/recorder/recording.c2
-rw-r--r--apps/root_menu.c5
-rw-r--r--apps/scrobbler.c2
-rw-r--r--apps/settings.c8
-rw-r--r--apps/settings_list.c6
-rw-r--r--apps/shortcuts.c2
-rw-r--r--apps/tagcache.c21
-rw-r--r--apps/tree.c55
-rw-r--r--bootloader/creativezvm.c5
-rw-r--r--bootloader/gigabeat-s.c16
-rw-r--r--bootloader/gigabeat.c3
-rw-r--r--bootloader/iaudio_coldfire.c2
-rw-r--r--bootloader/imx233.c3
-rw-r--r--bootloader/ipod.c10
-rw-r--r--bootloader/ipodnano2g.c4
-rw-r--r--bootloader/iriver_h1x0.c3
-rw-r--r--bootloader/iriver_h300.c4
-rw-r--r--bootloader/main-e200r-installer.c19
-rw-r--r--bootloader/main-pp.c15
-rw-r--r--bootloader/mini2440.c3
-rw-r--r--bootloader/mpio_hd200_hd300.c3
-rw-r--r--bootloader/mrobe500.c5
-rw-r--r--bootloader/ondavx747.c3
-rw-r--r--bootloader/rk27xx.c4
-rw-r--r--bootloader/sansa_as3525.c10
-rw-r--r--bootloader/sansaconnect.c3
-rw-r--r--bootloader/telechips.c3
-rw-r--r--bootloader/tpj1022.c3
-rw-r--r--firmware/SOURCES45
-rw-r--r--firmware/asm/mips/memcpy.S22
-rw-r--r--firmware/asm/sh/memcpy.S10
-rw-r--r--firmware/common/dir.c413
-rw-r--r--firmware/common/dir_uncached.c312
-rw-r--r--firmware/common/dircache.c3981
-rw-r--r--firmware/common/disk.c451
-rw-r--r--firmware/common/disk_cache.c343
-rw-r--r--firmware/common/file.c1637
-rw-r--r--firmware/common/file_internal.c776
-rw-r--r--firmware/common/filefuncs.c102
-rw-r--r--firmware/common/fileobj_mgr.c396
-rw-r--r--firmware/common/pathfuncs.c421
-rw-r--r--firmware/common/rbpaths.c432
-rw-r--r--firmware/common/unicode.c451
-rw-r--r--firmware/drivers/fat.c4206
-rw-r--r--firmware/export/config.h29
-rw-r--r--firmware/export/config/gigabeats.h3
-rw-r--r--firmware/export/disk.h13
-rw-r--r--firmware/export/fat.h225
-rw-r--r--firmware/export/hostfs.h25
-rw-r--r--firmware/export/load_code.h10
-rw-r--r--firmware/export/mv.h77
-rw-r--r--firmware/export/pathfuncs.h100
-rw-r--r--firmware/export/rbpaths.h21
-rw-r--r--firmware/export/storage.h7
-rw-r--r--firmware/export/system.h10
-rw-r--r--firmware/include/dir.h91
-rw-r--r--firmware/include/dir_uncached.h107
-rw-r--r--firmware/include/dircache.h209
-rw-r--r--firmware/include/dircache_redirect.h198
-rw-r--r--firmware/include/disk_cache.h83
-rw-r--r--firmware/include/file.h125
-rw-r--r--firmware/include/file_internal.h371
-rw-r--r--firmware/include/fileobj_mgr.h56
-rw-r--r--firmware/include/filesystem-native.h107
-rw-r--r--firmware/include/fs_attr.h39
-rw-r--r--firmware/include/rb-loader.h1
-rw-r--r--firmware/include/rbunicode.h18
-rw-r--r--firmware/kernel/include/mutex.h11
-rw-r--r--firmware/kernel/mutex.c5
-rw-r--r--firmware/libc/include/errno.h1
-rw-r--r--firmware/libc/include/fcntl.h20
-rw-r--r--firmware/libc/mktime.c2
-rw-r--r--firmware/storage.c9
-rw-r--r--firmware/target/arm/as3525/sd-as3525.c44
-rw-r--r--firmware/target/arm/as3525/sd-as3525v2.c41
-rw-r--r--firmware/target/arm/imx233/sdmmc-imx233.c45
-rw-r--r--firmware/target/arm/pp/ata-sd-pp.c30
-rw-r--r--firmware/target/arm/rk27xx/sd-rk27xx.c52
-rw-r--r--firmware/target/arm/s3c2440/sd-s3c2440.c34
-rw-r--r--firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c27
-rw-r--r--firmware/target/arm/tcc780x/sd-tcc780x.c32
-rw-r--r--firmware/target/arm/tms320dm320/sdmmc-dm320.c35
-rw-r--r--firmware/target/hosted/filesystem-app.c562
-rw-r--r--firmware/target/hosted/filesystem-app.h117
-rw-r--r--firmware/target/hosted/filesystem-hosted.h74
-rw-r--r--firmware/target/hosted/filesystem-unix.c202
-rw-r--r--firmware/target/hosted/filesystem-unix.h82
-rw-r--r--firmware/target/hosted/filesystem-win32.c479
-rw-r--r--firmware/target/hosted/filesystem-win32.h111
-rw-r--r--firmware/target/hosted/lc-unix.c11
-rw-r--r--firmware/target/hosted/sdl/app/load_code-sdl-app.c (renamed from firmware/export/filefuncs.h)31
-rw-r--r--firmware/target/hosted/sdl/filesystem-sdl.c55
-rw-r--r--firmware/target/hosted/sdl/filesystem-sdl.h37
-rw-r--r--firmware/target/hosted/sdl/load_code-sdl.c52
-rw-r--r--firmware/target/hosted/sdl/system-sdl.c4
-rw-r--r--firmware/target/hosted/sdl/system-sim.h32
-rw-r--r--firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c29
-rw-r--r--firmware/usbstack/usb_storage.c5
-rw-r--r--tools/checkwps/SOURCES14
-rw-r--r--tools/checkwps/checkwps.c7
-rw-r--r--tools/checkwps/file.h16
-rw-r--r--tools/database/SOURCES10
-rw-r--r--tools/root.make8
-rw-r--r--uisimulator/common/SOURCES17
-rw-r--r--uisimulator/common/dummylib.c1
-rw-r--r--uisimulator/common/filesystem-sim.c833
-rw-r--r--uisimulator/common/filesystem-sim.h108
-rw-r--r--uisimulator/common/io.c729
-rw-r--r--uisimulator/common/load_code-sim.c72
-rw-r--r--uisimulator/common/sim_tasks.c34
-rw-r--r--uisimulator/common/stubs.c5
-rw-r--r--uisimulator/common/time-win32.c61
130 files changed, 14689 insertions, 7316 deletions
diff --git a/apps/bookmark.c b/apps/bookmark.c
index 3234e77d9b..567f98ac29 100644
--- a/apps/bookmark.c
+++ b/apps/bookmark.c
@@ -41,7 +41,7 @@
41#include "list.h" 41#include "list.h"
42#include "plugin.h" 42#include "plugin.h"
43#include "file.h" 43#include "file.h"
44#include "filefuncs.h" 44#include "pathfuncs.h"
45 45
46#define MAX_BOOKMARKS 10 46#define MAX_BOOKMARKS 10
47#define MAX_BOOKMARK_SIZE 350 47#define MAX_BOOKMARK_SIZE 350
@@ -1090,10 +1090,10 @@ static bool generate_bookmark_file_name(const char *in)
1090 { 1090 {
1091#ifdef HAVE_MULTIVOLUME 1091#ifdef HAVE_MULTIVOLUME
1092 /* The "root" of an extra volume need special handling too. */ 1092 /* The "root" of an extra volume need special handling too. */
1093 bool volume_root = (strip_volume(in, global_bookmark_file_name) && 1093 const char *filename;
1094 !strcmp("/", global_bookmark_file_name)); 1094 path_strip_volume(in, &filename, true);
1095 bool volume_root = *filename == '\0';
1095#endif 1096#endif
1096
1097 strcpy(global_bookmark_file_name, in); 1097 strcpy(global_bookmark_file_name, in);
1098 if(global_bookmark_file_name[len-1] == '/') 1098 if(global_bookmark_file_name[len-1] == '/')
1099 len--; 1099 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 = {
128 logf, 128 logf,
129#endif 129#endif
130 130
131 (qsort_func)qsort, 131 (void *)qsort,
132 132
133#ifdef RB_PROFILE 133#ifdef RB_PROFILE
134 profile_thread, 134 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,
528{ 528{
529 (void)data; 529 (void)data;
530 int partition = selected_item/2; 530 int partition = selected_item/2;
531 struct partinfo* p = disk_partinfo(partition); 531
532 struct partinfo p;
533 if (!disk_partinfo(partition, &p))
534 return buffer;
535
532 if (selected_item%2) 536 if (selected_item%2)
533 { 537 {
534 snprintf(buffer, buffer_len, " T:%x %ld MB", p->type, p->size / ( 2048 / ( SECTOR_SIZE / 512 ))); 538 snprintf(buffer, buffer_len, " T:%x %ld MB", p.type,
539 p.size / ( 2048 / ( SECTOR_SIZE / 512 )));
535 } 540 }
536 else 541 else
537 { 542 {
538 snprintf(buffer, buffer_len, "P%d: S:%lx", partition, p->start); 543 snprintf(buffer, buffer_len, "P%d: S:%lx", partition, p.start);
539 } 544 }
540 return buffer; 545 return buffer;
541} 546}
@@ -1377,7 +1382,7 @@ static int disk_callback(int btn, struct gui_synclist *lists)
1377 simplelist_addline( 1382 simplelist_addline(
1378 "Size: %s", buf); 1383 "Size: %s", buf);
1379 unsigned long free; 1384 unsigned long free;
1380 fat_size( IF_MV(0,) NULL, &free ); 1385 volume_size( IF_MV(0,) NULL, &free );
1381 simplelist_addline( 1386 simplelist_addline(
1382 "Free: %ld MB", free / 1024); 1387 "Free: %ld MB", free / 1024);
1383 simplelist_addline( 1388 simplelist_addline(
@@ -1469,7 +1474,7 @@ static int disk_callback(int btn, struct gui_synclist *lists)
1469 "No timing info"); 1474 "No timing info");
1470 } 1475 }
1471 simplelist_addline( 1476 simplelist_addline(
1472 "Cluster size: %d bytes", fat_get_cluster_size(IF_MV(0))); 1477 "Cluster size: %d bytes", volume_get_cluster_size(IF_MV(0)));
1473#ifdef HAVE_ATA_DMA 1478#ifdef HAVE_ATA_DMA
1474 i = ata_get_dma_mode(); 1479 i = ata_get_dma_mode();
1475 if (i == 0) { 1480 if (i == 0) {
@@ -1496,11 +1501,11 @@ static int disk_callback(int btn, struct gui_synclist *lists)
1496 simplelist_addline( 1501 simplelist_addline(
1497 "Size: %ld MB", info.num_sectors*(info.sector_size/512)/2024); 1502 "Size: %ld MB", info.num_sectors*(info.sector_size/512)/2024);
1498 unsigned long free; 1503 unsigned long free;
1499 fat_size( IF_MV(0,) NULL, &free ); 1504 volume_size( IF_MV(0,) NULL, &free );
1500 simplelist_addline( 1505 simplelist_addline(
1501 "Free: %ld MB", free / 1024); 1506 "Free: %ld MB", free / 1024);
1502 simplelist_addline( 1507 simplelist_addline(
1503 "Cluster size: %d bytes", fat_get_cluster_size(IF_MV(0))); 1508 "Cluster size: %d bytes", volume_get_cluster_size(IF_MV(0)));
1504 return btn; 1509 return btn;
1505} 1510}
1506#endif 1511#endif
@@ -1542,29 +1547,64 @@ static bool dbg_disk_info(void)
1542#ifdef HAVE_DIRCACHE 1547#ifdef HAVE_DIRCACHE
1543static int dircache_callback(int btn, struct gui_synclist *lists) 1548static int dircache_callback(int btn, struct gui_synclist *lists)
1544{ 1549{
1545 (void)lists; 1550 struct dircache_info info;
1551 dircache_get_info(&info);
1552
1553 if (global_settings.dircache)
1554 {
1555 switch (btn)
1556 {
1557 case ACTION_STD_CONTEXT:
1558 splash(HZ/2, "Rebuilding cache");
1559 dircache_suspend();
1560 *(int *)lists->data = dircache_resume();
1561 case ACTION_UNKNOWN:
1562 btn = ACTION_NONE;
1563 break;
1564 #ifdef DIRCACHE_DUMPSTER
1565 case ACTION_STD_OK:
1566 splash(0, "Dumping cache");
1567 dircache_dump();
1568 btn = ACTION_NONE;
1569 break;
1570 #endif /* DIRCACHE_DUMPSTER */
1571 case ACTION_STD_CANCEL:
1572 if (*(int *)lists->data > 0 && info.status == DIRCACHE_SCANNING)
1573 {
1574 splash(HZ, str(LANG_SCANNING_DISK));
1575 btn = ACTION_NONE;
1576 }
1577 break;
1578 }
1579 }
1580
1546 simplelist_set_line_count(0); 1581 simplelist_set_line_count(0);
1547 simplelist_addline("Cache initialized: %s", 1582
1548 dircache_is_enabled() ? "Yes" : "No"); 1583 simplelist_addline("Cache status: %s", info.statusdesc);
1549 simplelist_addline("Cache size: %d B", 1584 simplelist_addline("Last size: %lu B", info.last_size);
1550 dircache_get_cache_size()); 1585 simplelist_addline("Size: %lu B", info.size);
1551 simplelist_addline("Last size: %d B", 1586 unsigned int utilized = info.size ? 1000ull*info.sizeused / info.size : 0;
1552 global_status.dircache_size); 1587 simplelist_addline("Used: %lu B (%u.%u%%)", info.sizeused,
1553 simplelist_addline("Limit: %d B", 1588 utilized / 10, utilized % 10);
1554 DIRCACHE_LIMIT); 1589 simplelist_addline("Limit: %lu B", info.size_limit);
1555 simplelist_addline("Reserve: %d/%d B", 1590 simplelist_addline("Reserve: %lu/%lu B", info.reserve_used, info.reserve);
1556 dircache_get_reserve_used(), DIRCACHE_RESERVE); 1591 long ticks = ALIGN_UP(info.build_ticks, HZ / 10);
1557 simplelist_addline("Scanning took: %d s", 1592 simplelist_addline("Scanning took: %ld.%ld s",
1558 dircache_get_build_ticks() / HZ); 1593 ticks / HZ, (ticks*10 / HZ) % 10);
1559 simplelist_addline("Entry count: %d", 1594 simplelist_addline("Entry count: %u", info.entry_count);
1560 dircache_get_entry_count()); 1595
1596 if (btn == ACTION_NONE)
1597 btn = ACTION_REDRAW;
1598
1561 return btn; 1599 return btn;
1600 (void)lists;
1562} 1601}
1563 1602
1564static bool dbg_dircache_info(void) 1603static bool dbg_dircache_info(void)
1565{ 1604{
1566 struct simplelist_info info; 1605 struct simplelist_info info;
1567 simplelist_info_init(&info, "Dircache Info", 7, NULL); 1606 int syncbuild = 0;
1607 simplelist_info_init(&info, "Dircache Info", 8, &syncbuild);
1568 info.action_callback = dircache_callback; 1608 info.action_callback = dircache_callback;
1569 info.hide_selection = true; 1609 info.hide_selection = true;
1570 info.scroll_all = true; 1610 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)
376 ++files_in_dir; 376 ++files_in_dir;
377 377
378 dptr->name = core_get_data(c->cache.name_buffer_handle)+name_buffer_used; 378 dptr->name = core_get_data(c->cache.name_buffer_handle)+name_buffer_used;
379 dptr->time_write = 379 dptr->time_write = info.mtime;
380 (long)info.wrtdate<<16 |
381 (long)info.wrttime; /* in one # */
382 strcpy(dptr->name, (char *)entry->d_name); 380 strcpy(dptr->name, (char *)entry->d_name);
383 name_buffer_used += len + 1; 381 name_buffer_used += len + 1;
384 382
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 @@
19 * 19 *
20 ****************************************************************************/ 20 ****************************************************************************/
21#include "config.h" 21#include "config.h"
22#include "system.h"
22 23
23#include "gcc_extensions.h" 24#include "gcc_extensions.h"
24#include "storage.h" 25#include "storage.h"
25#include "disk.h" 26#include "disk.h"
26#include "fat.h" 27#include "file_internal.h"
27#include "lcd.h" 28#include "lcd.h"
28#include "rtc.h" 29#include "rtc.h"
29#include "debug.h" 30#include "debug.h"
@@ -34,7 +35,6 @@
34#include "filetypes.h" 35#include "filetypes.h"
35#include "panic.h" 36#include "panic.h"
36#include "menu.h" 37#include "menu.h"
37#include "system.h"
38#include "usb.h" 38#include "usb.h"
39#include "powermgmt.h" 39#include "powermgmt.h"
40#include "adc.h" 40#include "adc.h"
@@ -203,80 +203,53 @@ int main(void)
203 root_menu(); 203 root_menu();
204} 204}
205 205
206static int init_dircache(bool preinit) INIT_ATTR;
207static int init_dircache(bool preinit)
208{
209#ifdef HAVE_DIRCACHE 206#ifdef HAVE_DIRCACHE
210 int result = 0; 207static int INIT_ATTR init_dircache(bool preinit)
211 bool clear = false; 208{
212
213 if (preinit) 209 if (preinit)
214 dircache_init(); 210 dircache_init(MAX(global_status.dircache_size, 0));
215 211
216 if (!global_settings.dircache) 212 if (!global_settings.dircache)
217 return 0; 213 return -1;
214
215 int result = -1;
218 216
219# ifdef HAVE_EEPROM_SETTINGS 217#ifdef HAVE_EEPROM_SETTINGS
220 if (firmware_settings.initialized && firmware_settings.disk_clean 218 if (firmware_settings.initialized &&
221 && preinit) 219 firmware_settings.disk_clean &&
220 preinit)
222 { 221 {
223 result = dircache_load(); 222 result = dircache_load();
224
225 if (result < 0) 223 if (result < 0)
226 {
227 firmware_settings.disk_clean = false; 224 firmware_settings.disk_clean = false;
228 if (global_status.dircache_size <= 0)
229 {
230 /* This will be in default language, settings are not
231 applied yet. Not really any easy way to fix that. */
232 splash(0, str(LANG_SCANNING_DISK));
233 clear = true;
234 }
235
236 dircache_build(global_status.dircache_size);
237 }
238 } 225 }
239 else 226 else
240# endif 227#endif /* HAVE_EEPROM_SETTINGS */
228 if (!preinit)
241 { 229 {
242 if (preinit) 230 result = dircache_enable();
243 return -1; 231 if (result != 0)
244
245 if (!dircache_is_enabled()
246 && !dircache_is_initializing())
247 { 232 {
248 if (global_status.dircache_size <= 0) 233 if (result > 0)
249 { 234 {
235 /* Print "Scanning disk..." to the display. */
250 splash(0, str(LANG_SCANNING_DISK)); 236 splash(0, str(LANG_SCANNING_DISK));
251 clear = true; 237 dircache_wait();
238 backlight_on();
239 show_logo();
252 } 240 }
253 result = dircache_build(global_status.dircache_size);
254 }
255 241
256 if (result < 0) 242 struct dircache_info info;
257 { 243 dircache_get_info(&info);
258 /* Initialization of dircache failed. Manual action is 244 global_status.dircache_size = info.size;
259 * necessary to enable dircache again. 245 status_save();
260 */
261 splashf(0, "Dircache failed, disabled. Result: %d", result);
262 global_settings.dircache = false;
263 } 246 }
264 } 247 /* else don't wait or already enabled by load */
265
266 if (clear)
267 {
268 backlight_on();
269 show_logo();
270 global_status.dircache_size = dircache_get_cache_size();
271 status_save();
272 } 248 }
273 249
274 return result; 250 return result;
275#else
276 (void)preinit;
277 return 0;
278#endif
279} 251}
252#endif /* HAVE_DIRCACHE */
280 253
281#ifdef HAVE_TAGCACHE 254#ifdef HAVE_TAGCACHE
282static void init_tagcache(void) INIT_ATTR; 255static void init_tagcache(void) INIT_ATTR;
@@ -363,6 +336,7 @@ static void init(void)
363 button_init(); 336 button_init();
364 powermgmt_init(); 337 powermgmt_init();
365 backlight_init(); 338 backlight_init();
339 unicode_init();
366#ifdef SIMULATOR 340#ifdef SIMULATOR
367 sim_tasks_init(); 341 sim_tasks_init();
368#endif 342#endif
@@ -392,8 +366,10 @@ static void init(void)
392 settings_reset(); 366 settings_reset();
393 settings_load(SETTINGS_ALL); 367 settings_load(SETTINGS_ALL);
394 settings_apply(true); 368 settings_apply(true);
369#ifdef HAVE_DIRCACHE
395 init_dircache(true); 370 init_dircache(true);
396 init_dircache(false); 371 init_dircache(false);
372#endif
397#ifdef HAVE_TAGCACHE 373#ifdef HAVE_TAGCACHE
398 init_tagcache(); 374 init_tagcache();
399#endif 375#endif
@@ -429,6 +405,8 @@ static void init(void)
429 405
430#else 406#else
431 407
408#include "errno.h"
409
432static void init(void) INIT_ATTR; 410static void init(void) INIT_ATTR;
433static void init(void) 411static void init(void)
434{ 412{
@@ -443,6 +421,9 @@ static void init(void)
443 core_allocator_init(); 421 core_allocator_init();
444 kernel_init(); 422 kernel_init();
445 423
424 /* early early early! */
425 filesystem_init();
426
446#ifdef HAVE_ADJUSTABLE_CPU_FREQ 427#ifdef HAVE_ADJUSTABLE_CPU_FREQ
447 set_cpu_frequency(CPUFREQ_NORMAL); 428 set_cpu_frequency(CPUFREQ_NORMAL);
448#ifdef CPU_COLDFIRE 429#ifdef CPU_COLDFIRE
@@ -462,6 +443,7 @@ static void init(void)
462 /* current_tick should be ticking by now */ 443 /* current_tick should be ticking by now */
463 CHART("ticking"); 444 CHART("ticking");
464 445
446 unicode_init();
465 lcd_init(); 447 lcd_init();
466#ifdef HAVE_REMOTE_LCD 448#ifdef HAVE_REMOTE_LCD
467 lcd_remote_init(); 449 lcd_remote_init();
@@ -558,8 +540,6 @@ static void init(void)
558 } 540 }
559#endif 541#endif
560 542
561
562 disk_init_subsystem();
563 CHART(">storage_init"); 543 CHART(">storage_init");
564 rc = storage_init(); 544 rc = storage_init();
565 CHART("<storage_init"); 545 CHART("<storage_init");
@@ -661,22 +641,24 @@ static void init(void)
661 CHART("<settings_load(ALL)"); 641 CHART("<settings_load(ALL)");
662 } 642 }
663 643
644#ifdef HAVE_DIRCACHE
664 CHART(">init_dircache(true)"); 645 CHART(">init_dircache(true)");
665 rc = init_dircache(true); 646 rc = init_dircache(true);
666 CHART("<init_dircache(true)"); 647 CHART("<init_dircache(true)");
667 if (rc < 0)
668 {
669#ifdef HAVE_TAGCACHE 648#ifdef HAVE_TAGCACHE
649 if (rc < 0)
670 remove(TAGCACHE_STATEFILE); 650 remove(TAGCACHE_STATEFILE);
671#endif 651#endif /* HAVE_TAGCACHE */
672 } 652#endif /* HAVE_DIRCACHE */
673 653
674 CHART(">settings_apply(true)"); 654 CHART(">settings_apply(true)");
675 settings_apply(true); 655 settings_apply(true);
676 CHART("<settings_apply(true)"); 656 CHART("<settings_apply(true)");
657#ifdef HAVE_DIRCACHE
677 CHART(">init_dircache(false)"); 658 CHART(">init_dircache(false)");
678 init_dircache(false); 659 init_dircache(false);
679 CHART("<init_dircache(false)"); 660 CHART("<init_dircache(false)");
661#endif
680#ifdef HAVE_TAGCACHE 662#ifdef HAVE_TAGCACHE
681 CHART(">init_tagcache"); 663 CHART(">init_tagcache");
682 init_tagcache(); 664 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 @@
43#endif 43#endif
44#include "viewport.h" 44#include "viewport.h"
45#include "statusbar.h" /* statusbar_vals enum*/ 45#include "statusbar.h" /* statusbar_vals enum*/
46#include "rbunicode.h"
46 47
47#ifdef HAVE_BACKLIGHT 48#ifdef HAVE_BACKLIGHT
48static int filterfirstkeypress_callback(int action,const struct menu_item_ex *this_item) 49static 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,
524 &touchscreen_menu_calibrate, &touchscreen_menu_reset_calibration); 525 &touchscreen_menu_calibrate, &touchscreen_menu_reset_calibration);
525#endif 526#endif
526 527
528static int codepage_callback(int action, const struct menu_item_ex *this_item)
529{
530 static int old_codepage;
531 int new_codepage = global_settings.default_codepage;
532 (void)this_item;
533 switch (action)
534 {
535 case ACTION_ENTER_MENUITEM:
536 old_codepage = new_codepage;
537 break;
538 case ACTION_EXIT_MENUITEM:
539 if (new_codepage != old_codepage)
540 set_codepage(new_codepage);
541 break;
542 }
543 return action;
544}
527 545
528MENUITEM_SETTING(codepage_setting, &global_settings.default_codepage, NULL); 546MENUITEM_SETTING(codepage_setting, &global_settings.default_codepage, codepage_callback);
529 547
530 548
531MAKE_MENU(display_menu, ID2P(LANG_DISPLAY), 549MAKE_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 @@
48#include "time.h" 48#include "time.h"
49#include "wps.h" 49#include "wps.h"
50#include "skin_buffer.h" 50#include "skin_buffer.h"
51#include "disk.h"
51 52
52static const struct browse_folder_info config = {ROCKBOX_DIR, SHOW_CFG}; 53static const struct browse_folder_info config = {ROCKBOX_DIR, SHOW_CFG};
53 54
@@ -160,14 +161,14 @@ static const char* info_getname(int selected_item, void *data,
160#endif 161#endif
161 if (info->new_data) 162 if (info->new_data)
162 { 163 {
163 fat_size(IF_MV(0,) &info->size, &info->free); 164 volume_size(IF_MV(0,) &info->size, &info->free);
164#ifdef HAVE_MULTIVOLUME 165#ifdef HAVE_MULTIVOLUME
165#ifndef APPLICATION 166#ifndef APPLICATION
166 if (fat_ismounted(1)) 167 volume_size(1, &info->size2, &info->free2);
167 fat_size(1, &info->size2, &info->free2); 168#else
168 else 169 info->size2 = 0;
169#endif 170#endif
170 info->size2 = 0; 171
171#endif 172#endif
172 info->new_data = false; 173 info->new_data = false;
173 } 174 }
@@ -347,12 +348,7 @@ static int info_action_callback(int action, struct gui_synclist *lists)
347 info->new_data = true; 348 info->new_data = true;
348 splash(0, ID2P(LANG_SCANNING_DISK)); 349 splash(0, ID2P(LANG_SCANNING_DISK));
349 for (i = 0; i < NUM_VOLUMES; i++) 350 for (i = 0; i < NUM_VOLUMES; i++)
350 { 351 volume_recalc_free(IF_MV(i));
351#ifdef HAVE_HOTSWAP
352 if (fat_ismounted(i))
353#endif
354 fat_recalc_free(IF_MV(i));
355 }
356#else 352#else
357 (void) lists; 353 (void) lists;
358#endif 354#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)
209 switch (action) 209 switch (action)
210 { 210 {
211 case ACTION_EXIT_MENUITEM: /* on exit */ 211 case ACTION_EXIT_MENUITEM: /* on exit */
212 if (global_settings.dircache && !dircache_is_enabled()) 212 if (global_settings.dircache)
213 { 213 {
214 if (dircache_build(0) < 0) 214 if (dircache_enable() < 0)
215 splash(HZ*2, ID2P(LANG_PLEASE_REBOOT)); 215 splash(HZ*2, ID2P(LANG_PLEASE_REBOOT));
216 } 216 }
217 else if (!global_settings.dircache && dircache_is_enabled()) 217 else
218 { 218 {
219 dircache_disable(); 219 dircache_disable();
220 } 220 }
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 @@
28#include "misc.h" 28#include "misc.h"
29#include "system.h" 29#include "system.h"
30#include "lcd.h" 30#include "lcd.h"
31#ifdef HAVE_DIRCACHE
32#include "dircache.h"
33#endif
31#include "file.h" 34#include "file.h"
32#ifndef __PCTOOL__ 35#ifndef __PCTOOL__
33#include "filefuncs.h" 36#include "pathfuncs.h"
34#include "lang.h" 37#include "lang.h"
35#include "dir.h" 38#include "dir.h"
36#ifdef HAVE_REMOTE_LCD 39#ifdef HAVE_REMOTE_LCD
@@ -744,8 +747,7 @@ int show_logo( void )
744*/ 747*/
745void check_bootfile(bool do_rolo) 748void check_bootfile(bool do_rolo)
746{ 749{
747 static unsigned short wrtdate = 0; 750 static time_t mtime = 0;
748 static unsigned short wrttime = 0;
749 DIR* dir = NULL; 751 DIR* dir = NULL;
750 struct dirent* entry = NULL; 752 struct dirent* entry = NULL;
751 753
@@ -761,10 +763,9 @@ void check_bootfile(bool do_rolo)
761 { 763 {
762 struct dirinfo info = dir_get_info(dir, entry); 764 struct dirinfo info = dir_get_info(dir, entry);
763 /* found the bootfile */ 765 /* found the bootfile */
764 if(wrtdate && do_rolo) 766 if(mtime && do_rolo)
765 { 767 {
766 if((info.wrtdate != wrtdate) || 768 if(info.mtime != mtime)
767 (info.wrttime != wrttime))
768 { 769 {
769 static const char *lines[] = { ID2P(LANG_BOOT_CHANGED), 770 static const char *lines[] = { ID2P(LANG_BOOT_CHANGED),
770 ID2P(LANG_REBOOT_NOW) }; 771 ID2P(LANG_REBOOT_NOW) };
@@ -777,8 +778,7 @@ void check_bootfile(bool do_rolo)
777 } 778 }
778 } 779 }
779 } 780 }
780 wrtdate = info.wrtdate; 781 mtime = info.mtime;
781 wrttime = info.wrttime;
782 } 782 }
783 } 783 }
784 closedir(dir); 784 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 @@
62#include "statusbar-skinned.h" 62#include "statusbar-skinned.h"
63#include "pitchscreen.h" 63#include "pitchscreen.h"
64#include "viewport.h" 64#include "viewport.h"
65#include "filefuncs.h" 65#include "pathfuncs.h"
66#include "shortcuts.h" 66#include "shortcuts.h"
67 67
68static int context; 68static int context;
69static const char* selected_file = NULL; 69static const char *selected_file = NULL;
70static int selected_file_attr = 0; 70static int selected_file_attr = 0;
71static int onplay_result = ONPLAY_OK; 71static int onplay_result = ONPLAY_OK;
72static char clipboard_selection[MAX_PATH];
73static int clipboard_selection_attr = 0;
74static bool clipboard_is_copy = false;
75 72
76/* redefine MAKE_MENU so the MENU_EXITAFTERTHISMENU flag can be added easily */ 73/* redefine MAKE_MENU so the MENU_EXITAFTERTHISMENU flag can be added easily */
77#define MAKE_ONPLAYMENU( name, str, callback, icon, ... ) \ 74#define MAKE_ONPLAYMENU( name, str, callback, icon, ... ) \
@@ -82,6 +79,63 @@ static bool clipboard_is_copy = false;
82 MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \ 79 MENU_ITEM_COUNT(sizeof( name##_)/sizeof(*name##_)), \
83 { (void*)name##_},{.callback_and_desc = & name##__}}; 80 { (void*)name##_},{.callback_and_desc = & name##__}};
84 81
82/* Used for directory move, copy and delete */
83struct dirrecurse_params
84{
85 char path[MAX_PATH]; /* Buffer for full path */
86 size_t append; /* Append position in 'path' for stack push */
87};
88
89enum clipboard_op_flags
90{
91 PASTE_CUT = 0x00, /* Is a move (cut) operation (default) */
92 PASTE_COPY = 0x01, /* Is a copy operation */
93 PASTE_OVERWRITE = 0x02, /* Overwrite destination */
94 PASTE_EXDEV = 0x04, /* Actually copy/move across volumes */
95};
96
97/* result codec of various onplay operations */
98enum onplay_result_code
99{
100 /* Anything < 0 is failure */
101 OPRC_SUCCESS = 0, /* All operations completed successfully */
102 OPRC_NOOP = 1, /* Operation didn't need to do anything */
103 OPRC_CANCELLED = 2, /* Operation was cancelled by user */
104 OPRC_NOOVERWRT = 3,
105};
106
107static struct clipboard
108{
109 char path[MAX_PATH]; /* Clipped file's path */
110 unsigned int attr; /* Clipped file's attributes */
111 unsigned int flags; /* Operation type flags */
112} clipboard;
113
114/* Empty the clipboard */
115static void clipboard_clear_selection(struct clipboard *clip)
116{
117 clip->path[0] = '\0';
118 clip->attr = 0;
119 clip->flags = 0;
120}
121
122/* Store the selection in the clipboard */
123static bool clipboard_clip(struct clipboard *clip, const char *path,
124 unsigned int attr, unsigned int flags)
125{
126 /* if it fits it clips */
127 if (strlcpy(clip->path, path, sizeof (clip->path))
128 < sizeof (clip->path)) {
129 clip->attr = attr;
130 clip->flags = flags;
131 return true;
132 }
133 else {
134 clipboard_clear_selection(clip);
135 return false;
136 }
137}
138
85/* ----------------------------------------------------------------------- */ 139/* ----------------------------------------------------------------------- */
86/* Displays the bookmark menu options for the user to decide. This is an */ 140/* Displays the bookmark menu options for the user to decide. This is an */
87/* interface function. */ 141/* interface function. */
@@ -492,438 +546,578 @@ static void draw_slider(void)
492#define draw_slider() 546#define draw_slider()
493#endif 547#endif
494 548
495/* helper function to remove a non-empty directory */ 549static void clear_display(bool update)
496static int remove_dir(char* dirname, int len)
497{ 550{
498 int result = 0; 551 struct viewport vp;
499 DIR* dir;
500 int dirlen = strlen(dirname);
501 552
502 dir = opendir(dirname); 553 FOR_NB_SCREENS(i)
503 if (!dir) 554 {
555 struct screen * screen = &screens[i];
556 viewport_set_defaults(&vp, screen->screen_type);
557 screen->set_viewport(&vp);
558 screen->clear_viewport();
559 if (update) {
560 screen->update_viewport();
561 }
562 screen->set_viewport(NULL);
563 }
564}
565
566static void splash_path(const char *path)
567{
568 clear_display(false);
569 path_basename(path, &path);
570 splash(0, path);
571 draw_slider();
572}
573
574/* Splashes the path and checks the keys */
575static bool poll_cancel_action(const char *path)
576{
577 splash_path(path);
578 return ACTION_STD_CANCEL == get_action(CONTEXT_STD, TIMEOUT_NOBLOCK);
579}
580
581static int confirm_overwrite(void)
582{
583 static const char *lines[] = { ID2P(LANG_REALLY_OVERWRITE) };
584 static const struct text_message message = { lines, 1 };
585 return gui_syncyesno_run(&message, NULL, NULL);
586}
587
588static int confirm_delete(const char *file)
589{
590 const char *lines[] = { ID2P(LANG_REALLY_DELETE), file };
591 const char *yes_lines[] = { ID2P(LANG_DELETING), file };
592 const struct text_message message = { lines, 2 };
593 const struct text_message yes_message = { yes_lines, 2 };
594 return gui_syncyesno_run(&message, &yes_message, NULL);
595}
596
597static bool check_new_name(const char *basename)
598{
599 /* at least prevent escapes out of the base directory from keyboard-
600 entered filenames; the file code should reject other invalidities */
601 return *basename != '\0' && !strchr(basename, PATH_SEPCH) &&
602 !is_dotdir_name(basename);
603}
604
605static void splash_cancelled(void)
606{
607 clear_display(true);
608 splash(HZ, ID2P(LANG_CANCEL));
609}
610
611static void splash_failed(int lang_what)
612{
613 cond_talk_ids_fq(lang_what, LANG_FAILED);
614 clear_display(true);
615 splashf(HZ*2, "%s %s", str(lang_what), str(LANG_FAILED));
616}
617
618/* helper function to remove a non-empty directory */
619static int remove_dir(struct dirrecurse_params *parm)
620{
621 DIR *dir = opendir(parm->path);
622 if (!dir) {
504 return -1; /* open error */ 623 return -1; /* open error */
624 }
505 625
506 while(true) 626 size_t append = parm->append;
507 { 627 int rc = OPRC_SUCCESS;
508 struct dirent* entry; 628
509 /* walk through the directory content */ 629 /* walk through the directory content */
510 entry = readdir(dir); 630 while (rc == OPRC_SUCCESS) {
511 if (!entry) 631 errno = 0; /* distinguish failure from eod */
632 struct dirent *entry = readdir(dir);
633 if (!entry) {
634 if (errno) {
635 rc = -1;
636 }
512 break; 637 break;
638 }
639
513 struct dirinfo info = dir_get_info(dir, entry); 640 struct dirinfo info = dir_get_info(dir, entry);
514 dirname[dirlen] ='\0'; 641 if ((info.attribute & ATTR_DIRECTORY) &&
515 /* inform the user which dir we're deleting */ 642 is_dotdir_name(entry->d_name)) {
516 splash(0, dirname); 643 continue; /* skip these */
644 }
517 645
518 /* append name to current directory */ 646 /* append name to current directory */
519 snprintf(dirname+dirlen, len-dirlen, "/%s", entry->d_name); 647 parm->append = append + path_append(&parm->path[append],
520 if (info.attribute & ATTR_DIRECTORY) 648 PA_SEP_HARD, entry->d_name,
521 { /* remove a subdirectory */ 649 sizeof (parm->path) - append);
522 if (!strcmp((char *)entry->d_name, ".") || 650 if (parm->append >= sizeof (parm->path)) {
523 !strcmp((char *)entry->d_name, "..")) 651 rc = -1;
524 continue; /* skip these */ 652 break; /* no space left in buffer */
525
526 result = remove_dir(dirname, len); /* recursion */
527 if (result)
528 break; /* or better continue, delete what we can? */
529 }
530 else
531 { /* remove a file */
532 draw_slider();
533 result = remove(dirname);
534 } 653 }
535 if(ACTION_STD_CANCEL == get_action(CONTEXT_STD,TIMEOUT_NOBLOCK)) 654
536 { 655 if (info.attribute & ATTR_DIRECTORY) {
537 splash(HZ, ID2P(LANG_CANCEL)); 656 /* remove a subdirectory */
538 result = -1; 657 rc = remove_dir(parm);
539 break; 658 } else {
659 /* remove a file */
660 if (poll_cancel_action(parm->path)) {
661 rc = OPRC_CANCELLED;
662 break;
663 }
664
665 rc = remove(parm->path);
540 } 666 }
667
668 /* Remove basename we added above */
669 parm->path[append] = '\0';
541 } 670 }
542 closedir(dir);
543 671
544 if (!result) 672 closedir(dir);
545 { /* remove the now empty directory */
546 dirname[dirlen] = '\0'; /* terminate to original length */
547 673
548 result = rmdir(dirname); 674 if (rc == 0) {
675 /* remove the now empty directory */
676 if (poll_cancel_action(parm->path)) {
677 rc = OPRC_CANCELLED;
678 } else {
679 rc = rmdir(parm->path);
680 }
549 } 681 }
550 682
551 return result; 683 return rc;
552} 684}
553 685
554
555/* share code for file and directory deletion, saves space */ 686/* share code for file and directory deletion, saves space */
556static bool delete_file_dir(void) 687static int delete_file_dir(void)
557{ 688{
558 char file_to_delete[MAX_PATH]; 689 if (confirm_delete(selected_file) != YESNO_YES) {
559 strcpy(file_to_delete, selected_file); 690 return 1;
691 }
560 692
561 const char *lines[]={ 693 clear_display(true);
562 ID2P(LANG_REALLY_DELETE), 694 splash(HZ/2, str(LANG_DELETING));
563 file_to_delete
564 };
565 const char *yes_lines[]={
566 ID2P(LANG_DELETING),
567 file_to_delete
568 };
569 695
570 const struct text_message message={lines, 2}; 696 int rc = -1;
571 const struct text_message yes_message={yes_lines, 2};
572 697
573 if(gui_syncyesno_run(&message, &yes_message, NULL)!=YESNO_YES) 698 if (selected_file_attr & ATTR_DIRECTORY) { /* true if directory */
574 return false; 699 struct dirrecurse_params parm;
700 parm.append = strlcpy(parm.path, selected_file, sizeof (parm.path));
575 701
576 splash(0, str(LANG_DELETING)); 702 if (parm.append < sizeof (parm.path)) {
703 cpu_boost(true);
704 rc = remove_dir(&parm);
705 cpu_boost(false);
706 }
707 } else {
708 rc = remove(selected_file);
709 }
577 710
578 int res; 711 if (rc < OPRC_SUCCESS) {
579 if (selected_file_attr & ATTR_DIRECTORY) /* true if directory */ 712 splash_failed(LANG_DELETE);
580 { 713 } else if (rc == OPRC_CANCELLED) {
581 char pathname[MAX_PATH]; /* space to go deep */ 714 splash_cancelled();
582 cpu_boost(true);
583 strlcpy(pathname, file_to_delete, sizeof(pathname));
584 res = remove_dir(pathname, sizeof(pathname));
585 cpu_boost(false);
586 } 715 }
587 else
588 res = remove(file_to_delete);
589 716
590 if (!res) 717 if (rc != OPRC_NOOP) {
718 /* Could have failed after some but not all needed changes; reload */
591 onplay_result = ONPLAY_RELOAD_DIR; 719 onplay_result = ONPLAY_RELOAD_DIR;
720 }
592 721
593 return (res == 0); 722 return 1;
594} 723}
595 724
596static bool rename_file(void) 725static int rename_file(void)
597{ 726{
727 int rc = -1;
598 char newname[MAX_PATH]; 728 char newname[MAX_PATH];
599 char* ptr = strrchr(selected_file, '/') + 1; 729 const char *oldbase, *selection = selected_file;
600 int pathlen = (ptr - selected_file); 730
601 strlcpy(newname, selected_file, sizeof(newname)); 731 path_basename(selection, &oldbase);
602 if (!kbd_input(newname + pathlen, (sizeof newname)-pathlen)) { 732 size_t pathlen = oldbase - selection;
603 if (!strlen(newname + pathlen) || 733 char *newbase = newname + pathlen;
604 (rename(selected_file, newname) < 0)) { 734
605 cond_talk_ids_fq(LANG_RENAME, LANG_FAILED); 735 if (strlcpy(newname, selection, sizeof (newname)) >= sizeof (newname)) {
606 splashf(HZ*2, "%s %s", str(LANG_RENAME), str(LANG_FAILED)); 736 /* Too long */
737 } else if (kbd_input(newbase, sizeof (newname) - pathlen) < 0) {
738 rc = OPRC_CANCELLED;
739 } else if (!strcmp(oldbase, newbase)) {
740 rc = OPRC_NOOP; /* No change at all */
741 } else if (check_new_name(newbase)) {
742 switch (relate(selection, newname))
743 {
744 case RELATE_DIFFERENT:
745 if (file_exists(newname)) {
746 break; /* don't overwrite */
747 }
748 /* Fall-through */
749 case RELATE_SAME:
750 rc = rename(selection, newname);
751 break;
752 case RELATE_PREFIX:
753 default:
754 break;
607 } 755 }
608 else
609 onplay_result = ONPLAY_RELOAD_DIR;
610 } 756 }
611 757
612 return false; 758 if (rc < OPRC_SUCCESS) {
759 splash_failed(LANG_RENAME);
760 } else if (rc == OPRC_CANCELLED) {
761 /* splash_cancelled(); kbd_input() splashes it */
762 } else if (rc == OPRC_SUCCESS) {
763 onplay_result = ONPLAY_RELOAD_DIR;
764 }
765
766 return 1;
613} 767}
614 768
615static bool create_dir(void) 769static int create_dir(void)
616{ 770{
771 int rc = -1;
617 char dirname[MAX_PATH]; 772 char dirname[MAX_PATH];
618 char *cwd; 773 size_t pathlen = path_append(dirname, getcwd(NULL, 0), PA_SEP_HARD,
619 int rc; 774 sizeof (dirname));
620 int pathlen; 775 char *basename = dirname + pathlen;
621 776
622 cwd = getcwd(NULL, 0); 777 if (pathlen >= sizeof (dirname)) {
623 memset(dirname, 0, sizeof dirname); 778 /* Too long */
624 779 } else if (kbd_input(basename, sizeof (dirname) - pathlen) < 0) {
625 snprintf(dirname, sizeof dirname, "%s/", cwd[1] ? cwd : ""); 780 rc = OPRC_CANCELLED;
626 781 } else if (check_new_name(basename)) {
627 pathlen = strlen(dirname); 782 rc = mkdir(dirname);
628 rc = kbd_input(dirname + pathlen, (sizeof dirname)-pathlen); 783 }
629 if (rc < 0)
630 return false;
631 784
632 rc = mkdir(dirname); 785 if (rc < OPRC_SUCCESS) {
633 if (rc < 0) { 786 splash_failed(LANG_CREATE_DIR);
634 cond_talk_ids_fq(LANG_CREATE_DIR, LANG_FAILED); 787 } else if (rc == OPRC_CANCELLED) {
635 splashf(HZ, (unsigned char *)"%s %s", str(LANG_CREATE_DIR), 788 /* splash_cancelled(); kbd_input() splashes it */
636 str(LANG_FAILED)); 789 } else if (rc == OPRC_SUCCESS) {
637 } else {
638 onplay_result = ONPLAY_RELOAD_DIR; 790 onplay_result = ONPLAY_RELOAD_DIR;
639 } 791 }
640 792
641 return true; 793 return 1;
642} 794}
643 795
644/* Store the current selection in the clipboard */ 796/* Paste a file */
645static bool clipboard_clip(bool copy) 797static int clipboard_pastefile(const char *src, const char *target,
798 unsigned int flags)
646{ 799{
647 clipboard_selection[0] = 0; 800 int rc = -1;
648 strlcpy(clipboard_selection, selected_file, sizeof(clipboard_selection)); 801
649 clipboard_selection_attr = selected_file_attr; 802 while (!(flags & (PASTE_COPY | PASTE_EXDEV))) {
650 clipboard_is_copy = copy; 803 if ((flags & PASTE_OVERWRITE) || !file_exists(target)) {
651 804 /* Rename and possibly overwrite the file */
652 return true; 805 if (poll_cancel_action(src)) {
653} 806 rc = OPRC_CANCELLED;
807 } else {
808 rc = rename(src, target);
809 }
654 810
655static bool clipboard_cut(void) 811 #ifdef HAVE_MULTIVOLUME
656{ 812 if (rc < 0 && errno == EXDEV) {
657 return clipboard_clip(false); 813 /* Failed because cross volume rename doesn't work; force
658} 814 a move instead */
815 flags |= PASTE_EXDEV;
816 break;
817 }
818 #endif /* HAVE_MULTIVOLUME */
819 }
659 820
660static bool clipboard_copy(void) 821 return rc;
661{ 822 }
662 return clipboard_clip(true);
663}
664 823
665/* Paste a file to a new directory. Will overwrite always. */ 824 /* See if we can get the plugin buffer for the file copy buffer */
666static bool clipboard_pastefile(const char *src, const char *target, bool copy)
667{
668 int src_fd, target_fd;
669 size_t buffersize; 825 size_t buffersize;
670 ssize_t size, bytesread, byteswritten; 826 char *buffer = (char *) plugin_get_buffer(&buffersize);
671 char *buffer; 827 if (buffer == NULL || buffersize < 512) {
672 bool result = false; 828 /* Not large enough, try for a disk sector worth of stack
673 829 instead */
674 if (copy) { 830 buffersize = 512;
675 /* See if we can get the plugin buffer for the file copy buffer */ 831 buffer = (char *)alloca(buffersize);
676 buffer = (char *) plugin_get_buffer(&buffersize); 832 }
677 if (buffer == NULL || buffersize < 512) {
678 /* Not large enough, try for a disk sector worth of stack
679 instead */
680 buffersize = 512;
681 buffer = (char *) __builtin_alloca(buffersize);
682 }
683 833
684 if (buffer == NULL) { 834 if (buffer == NULL) {
685 return false; 835 return -1;
686 } 836 }
687 837
688 buffersize &= ~0x1ff; /* Round buffer size to multiple of sector 838 buffersize &= ~0x1ff; /* Round buffer size to multiple of sector
689 size */ 839 size */
690 840
691 src_fd = open(src, O_RDONLY); 841 int src_fd = open(src, O_RDONLY);
842 if (src_fd >= 0) {
843 int oflag = O_WRONLY|O_CREAT;
692 844
693 if (src_fd >= 0) { 845 if (!(flags & PASTE_OVERWRITE)) {
694 target_fd = creat(target, 0666); 846 oflag |= O_EXCL;
847 }
695 848
696 if (target_fd >= 0) { 849 int target_fd = open(target, oflag, 0666);
697 result = true; 850 if (target_fd >= 0) {
851 off_t total_size = 0;
852 off_t next_cancel_test = 0; /* No excessive button polling */
698 853
699 size = filesize(src_fd); 854 rc = OPRC_SUCCESS;
700 855
701 if (size == -1) { 856 while (rc == OPRC_SUCCESS) {
702 result = false; 857 if (total_size >= next_cancel_test) {
858 next_cancel_test = total_size + 0x10000;
859 if (poll_cancel_action(src)) {
860 rc = OPRC_CANCELLED;
861 break;
862 }
703 } 863 }
704 864
705 while(size > 0) { 865 ssize_t bytesread = read(src_fd, buffer, buffersize);
706 bytesread = read(src_fd, buffer, buffersize); 866 if (bytesread <= 0) {
707 867 if (bytesread < 0) {
708 if (bytesread == -1) { 868 rc = -1;
709 result = false;
710 break;
711 } 869 }
870 /* else eof on buffer boundary; nothing to write */
871 break;
872 }
712 873
713 size -= bytesread; 874 ssize_t byteswritten = write(target_fd, buffer, bytesread);
714 875 if (byteswritten < bytesread) {
715 while(bytesread > 0) { 876 /* Some I/O error */
716 byteswritten = write(target_fd, buffer, bytesread); 877 rc = -1;
717 878 break;
718 if (byteswritten < 0) {
719 result = false;
720 size = 0;
721 break;
722 }
723
724 bytesread -= byteswritten;
725 draw_slider();
726 }
727 } 879 }
728 880
729 close(target_fd); 881 total_size += byteswritten;
730 882
731 /* Copy failed. Cleanup. */ 883 if (bytesread < (ssize_t)buffersize) {
732 if (!result) { 884 /* EOF with trailing bytes */
733 remove(target); 885 break;
734 } 886 }
735 } 887 }
736 888
737 close(src_fd); 889 if (rc == OPRC_SUCCESS) {
738 } 890 /* If overwriting, set the correct length if original was
739 } else { 891 longer */
740 result = rename(src, target) == 0; 892 rc = ftruncate(target_fd, total_size);
741#ifdef HAVE_MULTIVOLUME 893 }
742 if (!result) { 894
743 if (errno == EXDEV) { 895 close(target_fd);
744 /* Failed because cross volume rename doesn't work. Copy 896
745 instead */ 897 if (rc != OPRC_SUCCESS) {
746 result = clipboard_pastefile(src, target, true); 898 /* Copy failed. Cleanup. */
747 899 remove(target);
748 if (result) {
749 result = remove(src) == 0;
750 }
751 } 900 }
752 } 901 }
753#endif 902
903 close(src_fd);
754 } 904 }
755 905
756 return result; 906 if (rc == OPRC_SUCCESS && !(flags & PASTE_COPY)) {
907 /* Remove the source file */
908 rc = remove(src);
909 }
910
911 return rc;
757} 912}
758 913
759/* Paste a directory to a new location. Designed to be called by 914/* Paste a directory */
760 clipboard_paste */ 915static int clipboard_pastedirectory(struct dirrecurse_params *src,
761static bool clipboard_pastedirectory(char *src, int srclen, char *target, 916 struct dirrecurse_params *target,
762 int targetlen, bool copy) 917 unsigned int flags)
763{ 918{
764 DIR *srcdir; 919 int rc = -1;
765 int srcdirlen = strlen(src); 920
766 int targetdirlen = strlen(target); 921 while (!(flags & (PASTE_COPY | PASTE_EXDEV))) {
767 bool result = true; 922 if ((flags & PASTE_OVERWRITE) || !file_exists(target->path)) {
768 923 /* Just try to move the directory */
769 if (!file_exists(target)) { 924 if (poll_cancel_action(src->path)) {
770 if (!copy) { 925 rc = OPRC_CANCELLED;
771 /* Just move the directory */ 926 } else {
772 result = rename(src, target) == 0; 927 rc = rename(src->path, target->path);
928 }
773 929
774#ifdef HAVE_MULTIVOLUME 930 if (rc < 0) {
775 if (!result && errno == EXDEV) { 931 int errnum = errno;
776 /* Try a copy as we're going across devices */ 932 if (errnum == ENOTEMPTY && (flags & PASTE_OVERWRITE)) {
777 result = clipboard_pastedirectory(src, srclen, target, 933 /* Directory is not empty thus rename() will not do a quick
778 targetlen, true); 934 overwrite */
779 935 break;
780 /* If it worked, remove the source directory */
781 if (result) {
782 remove_dir(src, srclen);
783 } 936 }
937 #ifdef HAVE_MULTIVOLUME
938 else if (errnum == EXDEV) {
939 /* Failed because cross volume rename doesn't work; force
940 a move instead */
941 flags |= PASTE_EXDEV;
942 break;
943 }
944 #endif /* HAVE_MULTIVOLUME */
784 } 945 }
785#endif
786 return result;
787 } else {
788 /* Make a directory to copy things to */
789 result = mkdir(target) == 0;
790 } 946 }
791 }
792 947
793 /* Check if something went wrong already */ 948 return rc;
794 if (!result) {
795 return result;
796 } 949 }
797 950
798 srcdir = opendir(src); 951 DIR *srcdir = opendir(src->path);
799 if (!srcdir) { 952
800 return false; 953 if (srcdir) {
954 /* Make a directory to copy things to */
955 rc = mkdir(target->path);
956 if (rc < 0 && errno == EEXIST && (flags & PASTE_OVERWRITE)) {
957 /* Exists and overwrite was approved */
958 rc = OPRC_SUCCESS;
959 }
801 } 960 }
802 961
803 /* This loop will exit as soon as there's a problem */ 962 size_t srcap = src->append, targetap = target->append;
804 while(result) 963
805 { 964 /* Walk through the directory content; this loop will exit as soon as
806 struct dirent* entry; 965 there's a problem */
807 /* walk through the directory content */ 966 while (rc == OPRC_SUCCESS) {
808 entry = readdir(srcdir); 967 errno = 0; /* Distinguish failure from eod */
809 if (!entry) 968 struct dirent *entry = readdir(srcdir);
969 if (!entry) {
970 if (errno) {
971 rc = -1;
972 }
810 break; 973 break;
974 }
811 975
812 struct dirinfo info = dir_get_info(srcdir, entry); 976 struct dirinfo info = dir_get_info(srcdir, entry);
813 /* append name to current directory */ 977 if ((info.attribute & ATTR_DIRECTORY) &&
814 snprintf(src+srcdirlen, srclen-srcdirlen, "/%s", entry->d_name); 978 is_dotdir_name(entry->d_name)) {
815 snprintf(target+targetdirlen, targetlen-targetdirlen, "/%s", 979 continue; /* Skip these */
816 entry->d_name); 980 }
817 981
818 DEBUGF("Copy %s to %s\n", src, target); 982 /* Append names to current directories */
983 src->append = srcap +
984 path_append(&src->path[srcap], PA_SEP_HARD, entry->d_name,
985 sizeof(src->path) - srcap);
819 986
820 if (info.attribute & ATTR_DIRECTORY) 987 target->append = targetap +
821 { /* copy/move a subdirectory */ 988 path_append(&target->path[targetap], PA_SEP_HARD, entry->d_name,
822 if (!strcmp((char *)entry->d_name, ".") || 989 sizeof (target->path) - targetap);
823 !strcmp((char *)entry->d_name, ".."))
824 continue; /* skip these */
825 990
826 result = clipboard_pastedirectory(src, srclen, target, targetlen, 991 if (src->append >= sizeof (src->path) ||
827 copy); /* recursion */ 992 target->append >= sizeof (target->path)) {
993 rc = -1; /* No space left in buffer */
994 break;
828 } 995 }
829 else 996
830 { /* copy/move a file */ 997 if (poll_cancel_action(src->path)) {
831 draw_slider(); 998 rc = OPRC_CANCELLED;
832 result = clipboard_pastefile(src, target, copy); 999 break;
1000 }
1001
1002 DEBUGF("Copy %s to %s\n", src->path, target->path);
1003
1004 if (info.attribute & ATTR_DIRECTORY) {
1005 /* Copy/move a subdirectory */
1006 rc = clipboard_pastedirectory(src, target, flags); /* recursion */
1007 } else {
1008 /* Copy/move a file */
1009 rc = clipboard_pastefile(src->path, target->path, flags);
833 } 1010 }
1011
1012 /* Remove basenames we added above */
1013 src->path[srcap] = target->path[targetap] = '\0';
1014 }
1015
1016 if (rc == OPRC_SUCCESS && !(flags & PASTE_COPY)) {
1017 /* Remove the now empty directory */
1018 rc = rmdir(src->path);
834 } 1019 }
835 1020
836 closedir(srcdir); 1021 closedir(srcdir);
1022 return rc;
1023}
837 1024
838 if (result) { 1025static bool clipboard_cut(void)
839 src[srcdirlen] = '\0'; /* terminate to original length */ 1026{
840 target[targetdirlen] = '\0'; /* terminate to original length */ 1027 return clipboard_clip(&clipboard, selected_file, selected_file_attr,
841 } 1028 PASTE_CUT);
1029}
842 1030
843 return result; 1031static bool clipboard_copy(void)
1032{
1033 return clipboard_clip(&clipboard, selected_file, selected_file_attr,
1034 PASTE_COPY);
844} 1035}
845 1036
846/* Paste the clipboard to the current directory */ 1037/* Paste the clipboard to the current directory */
847static bool clipboard_paste(void) 1038static int clipboard_paste(void)
848{ 1039{
849 char target[MAX_PATH]; 1040 if (!clipboard.path[0])
850 char *cwd, *nameptr; 1041 return 1;
851 bool success;
852 1042
853 static const char *lines[]={ID2P(LANG_REALLY_OVERWRITE)}; 1043 int rc = -1;
854 static const struct text_message message={lines, 1};
855 1044
856 /* Get the name of the current directory */ 1045 struct dirrecurse_params src, target;
857 cwd = getcwd(NULL, 0); 1046 unsigned int flags = clipboard.flags;
858 1047
859 /* Figure out the name of the selection */ 1048 /* Figure out the name of the selection */
860 nameptr = strrchr(clipboard_selection, '/'); 1049 const char *nameptr;
1050 path_basename(clipboard.path, &nameptr);
861 1051
862 /* Final target is current directory plus name of selection */ 1052 /* Final target is current directory plus name of selection */
863 snprintf(target, sizeof(target), "%s%s", cwd[1] ? cwd : "", nameptr); 1053 target.append = path_append(target.path, getcwd(NULL, 0),
864 1054 nameptr, sizeof (target.path));
865 /* If the target existed but they choose not to overwite, exit */
866 if (file_exists(target) &&
867 (gui_syncyesno_run(&message, NULL, NULL) == YESNO_NO)) {
868 return false;
869 }
870 1055
871 if (clipboard_is_copy) { 1056 switch (target.append < sizeof (target.path) ?
872 splash(0, ID2P(LANG_COPYING)); 1057 relate(clipboard.path, target.path) : -1)
873 }
874 else
875 { 1058 {
876 splash(0, ID2P(LANG_MOVING)); 1059 case RELATE_SAME:
877 } 1060 rc = OPRC_NOOP;
1061 break;
1062
1063 case RELATE_DIFFERENT:
1064 if (file_exists(target.path)) {
1065 /* If user chooses not to overwrite, cancel */
1066 if (confirm_overwrite() == YESNO_NO) {
1067 rc = OPRC_NOOVERWRT;
1068 break;
1069 }
878 1070
879 /* Now figure out what we're doing */ 1071 flags |= PASTE_OVERWRITE;
880 cpu_boost(true);
881 if (clipboard_selection_attr & ATTR_DIRECTORY) {
882 /* Recursion. Set up external stack */
883 char srcpath[MAX_PATH];
884 char targetpath[MAX_PATH];
885 if (!strncmp(clipboard_selection, target, strlen(clipboard_selection)))
886 {
887 /* Do not allow the user to paste a directory into a dir they are
888 copying */
889 success = 0;
890 } 1072 }
891 else
892 {
893 strlcpy(srcpath, clipboard_selection, sizeof(srcpath));
894 strlcpy(targetpath, target, sizeof(targetpath));
895 1073
896 success = clipboard_pastedirectory(srcpath, sizeof(srcpath), 1074 clear_display(true);
897 target, sizeof(targetpath), clipboard_is_copy); 1075 splash(HZ/2, (flags & PASTE_COPY) ? ID2P(LANG_COPYING) :
1076 ID2P(LANG_MOVING));
898 1077
899 if (success && !clipboard_is_copy) 1078 /* Now figure out what we're doing */
900 { 1079 cpu_boost(true);
901 strlcpy(srcpath, clipboard_selection, sizeof(srcpath)); 1080
902 remove_dir(srcpath, sizeof(srcpath)); 1081 if (clipboard.attr & ATTR_DIRECTORY) {
1082 /* Copy or move a subdirectory */
1083 src.append = strlcpy(src.path, clipboard.path,
1084 sizeof (src.path));
1085 if (src.append < sizeof (src.path)) {
1086 rc = clipboard_pastedirectory(&src, &target, flags);
903 } 1087 }
1088 } else {
1089 /* Copy or move a file */
1090 rc = clipboard_pastefile(clipboard.path, target.path, flags);
904 } 1091 }
905 } else { 1092
906 success = clipboard_pastefile(clipboard_selection, target, 1093 cpu_boost(false);
907 clipboard_is_copy); 1094 break;
1095
1096 case RELATE_PREFIX:
1097 default: /* Some other relation / failure */
1098 break;
908 } 1099 }
909 cpu_boost(false);
910 1100
911 /* Did it work? */ 1101 clear_display(true);
912 if (success) {
913 /* Reset everything */
914 clipboard_selection[0] = 0;
915 clipboard_selection_attr = 0;
916 clipboard_is_copy = false;
917 1102
918 /* Force reload of the current directory */ 1103 switch (rc)
1104 {
1105 case OPRC_CANCELLED:
1106 splash_cancelled();
1107 case OPRC_SUCCESS:
919 onplay_result = ONPLAY_RELOAD_DIR; 1108 onplay_result = ONPLAY_RELOAD_DIR;
920 } else { 1109 case OPRC_NOOP:
921 cond_talk_ids_fq(LANG_PASTE, LANG_FAILED); 1110 clipboard_clear_selection(&clipboard);
922 splashf(HZ, (unsigned char *)"%s %s", str(LANG_PASTE), 1111 case OPRC_NOOVERWRT:
923 str(LANG_FAILED)); 1112 break;
1113 default:
1114 if (rc < OPRC_SUCCESS) {
1115 splash_failed(LANG_PASTE);
1116 onplay_result = ONPLAY_RELOAD_DIR;
1117 }
924 } 1118 }
925 1119
926 return true; 1120 return 1;
927} 1121}
928 1122
929#ifdef HAVE_TAGCACHE 1123#ifdef HAVE_TAGCACHE
@@ -1094,15 +1288,12 @@ static int clipboard_callback(int action,const struct menu_item_ex *this_item)
1094 { 1288 {
1095 case ACTION_REQUEST_MENUITEM: 1289 case ACTION_REQUEST_MENUITEM:
1096#ifdef HAVE_MULTIVOLUME 1290#ifdef HAVE_MULTIVOLUME
1097 if ((selected_file_attr & FAT_ATTR_VOLUME) &&
1098 (this_item == &rename_file_item ||
1099 this_item == &delete_dir_item ||
1100 this_item == &clipboard_cut_item) )
1101 return ACTION_EXIT_MENUITEM;
1102 /* no rename+delete for volumes */ 1291 /* no rename+delete for volumes */
1103 if ((selected_file_attr & ATTR_VOLUME) && 1292 if ((selected_file_attr & ATTR_VOLUME) &&
1104 (this_item == &delete_file_item || 1293 (this_item == &rename_file_item ||
1105 this_item == &list_viewers_item)) 1294 this_item == &delete_dir_item ||
1295 this_item == &clipboard_cut_item ||
1296 this_item == &list_viewers_item))
1106 return ACTION_EXIT_MENUITEM; 1297 return ACTION_EXIT_MENUITEM;
1107#endif 1298#endif
1108#ifdef HAVE_TAGCACHE 1299#ifdef HAVE_TAGCACHE
@@ -1117,7 +1308,7 @@ static int clipboard_callback(int action,const struct menu_item_ex *this_item)
1117#endif 1308#endif
1118 if (this_item == &clipboard_paste_item) 1309 if (this_item == &clipboard_paste_item)
1119 { /* visible if there is something to paste */ 1310 { /* visible if there is something to paste */
1120 return (clipboard_selection[0] != 0) ? 1311 return (clipboard.path[0] != 0) ?
1121 action : ACTION_EXIT_MENUITEM; 1312 action : ACTION_EXIT_MENUITEM;
1122 } 1313 }
1123 else if (this_item == &create_dir_item) 1314 else if (this_item == &create_dir_item)
@@ -1232,8 +1423,7 @@ static bool delete_item(void)
1232{ 1423{
1233#ifdef HAVE_MULTIVOLUME 1424#ifdef HAVE_MULTIVOLUME
1234 /* no delete for volumes */ 1425 /* no delete for volumes */
1235 if ((selected_file_attr & FAT_ATTR_VOLUME) || 1426 if (selected_file_attr & ATTR_VOLUME)
1236 (selected_file_attr & ATTR_VOLUME))
1237 return false; 1427 return false;
1238#endif 1428#endif
1239 return delete_file_dir(); 1429 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 @@
86#include "screens.h" 86#include "screens.h"
87#include "core_alloc.h" 87#include "core_alloc.h"
88#include "misc.h" 88#include "misc.h"
89#include "filefuncs.h" 89#include "pathfuncs.h"
90#include "button.h" 90#include "button.h"
91#include "filetree.h" 91#include "filetree.h"
92#include "abrepeat.h" 92#include "abrepeat.h"
@@ -107,6 +107,8 @@
107#include "panic.h" 107#include "panic.h"
108#include "logdiskf.h" 108#include "logdiskf.h"
109 109
110#undef HAVE_DIRCACHE
111
110#define PLAYLIST_CONTROL_FILE_VERSION 2 112#define PLAYLIST_CONTROL_FILE_VERSION 2
111 113
112/* 114/*
@@ -180,8 +182,8 @@ static int get_next_directory(char *dir);
180static int get_next_dir(char *dir, bool is_forward); 182static int get_next_dir(char *dir, bool is_forward);
181static int get_previous_directory(char *dir); 183static int get_previous_directory(char *dir);
182static int check_subdir_for_music(char *dir, const char *subdir, bool recurse); 184static int check_subdir_for_music(char *dir, const char *subdir, bool recurse);
183static int format_track_path(char *dest, char *src, int buf_length, int max, 185static ssize_t format_track_path(char *dest, char *src, int buf_length,
184 const char *dir); 186 const char *dir);
185static void display_playlist_count(int count, const unsigned char *fmt, 187static void display_playlist_count(int count, const unsigned char *fmt,
186 bool final); 188 bool final);
187static void display_buffer_full(void); 189static void display_buffer_full(void);
@@ -526,7 +528,7 @@ static int add_indices_to_playlist(struct playlist_info* playlist,
526 int result = 0; 528 int result = 0;
527 /* get emergency buffer so we don't fail horribly */ 529 /* get emergency buffer so we don't fail horribly */
528 if (!buflen) 530 if (!buflen)
529 buffer = __builtin_alloca((buflen = 64)); 531 buffer = alloca((buflen = 64));
530 532
531 if(-1 == playlist->fd) 533 if(-1 == playlist->fd)
532 playlist->fd = open_utf8(playlist->filename, O_RDONLY); 534 playlist->fd = open_utf8(playlist->filename, O_RDONLY);
@@ -1429,7 +1431,7 @@ static int get_filename(struct playlist_info* playlist, int index, int seek,
1429 1431
1430 strlcpy(dir_buf, playlist->filename, playlist->dirlen); 1432 strlcpy(dir_buf, playlist->filename, playlist->dirlen);
1431 1433
1432 return (format_track_path(buf, tmp_buf, buf_length, max, dir_buf)); 1434 return format_track_path(buf, tmp_buf, buf_length, dir_buf);
1433} 1435}
1434 1436
1435static int get_next_directory(char *dir){ 1437static int get_next_directory(char *dir){
@@ -1629,7 +1631,7 @@ static int get_next_dir(char *dir, bool is_forward)
1629static int check_subdir_for_music(char *dir, const char *subdir, bool recurse) 1631static int check_subdir_for_music(char *dir, const char *subdir, bool recurse)
1630{ 1632{
1631 int result = -1; 1633 int result = -1;
1632 int dirlen = strlen(dir); 1634 size_t dirlen = strlen(dir);
1633 int num_files = 0; 1635 int num_files = 0;
1634 int i; 1636 int i;
1635 struct entry *files; 1637 struct entry *files;
@@ -1637,12 +1639,11 @@ static int check_subdir_for_music(char *dir, const char *subdir, bool recurse)
1637 bool has_subdir = false; 1639 bool has_subdir = false;
1638 struct tree_context* tc = tree_get_context(); 1640 struct tree_context* tc = tree_get_context();
1639 1641
1640 snprintf( 1642 if (path_append(dir + dirlen, PA_SEP_HARD, subdir, MAX_PATH - dirlen) >=
1641 dir + dirlen, MAX_PATH - dirlen, 1643 MAX_PATH - dirlen)
1642 /* only add a trailing slash if we need one */ 1644 {
1643 dirlen && dir[dirlen - 1] == '/' ? "%s" : "/%s", 1645 return 0;
1644 subdir 1646 }
1645 );
1646 1647
1647 if (ft_load(tc, dir) < 0) 1648 if (ft_load(tc, dir) < 0)
1648 { 1649 {
@@ -1695,7 +1696,7 @@ static int check_subdir_for_music(char *dir, const char *subdir, bool recurse)
1695 } 1696 }
1696 else 1697 else
1697 { 1698 {
1698 strcpy(dir, "/"); 1699 strcpy(dir, PATH_ROOTSTR);
1699 } 1700 }
1700 1701
1701 /* we now need to reload our current directory */ 1702 /* 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)
1708/* 1709/*
1709 * Returns absolute path of track 1710 * Returns absolute path of track
1710 */ 1711 */
1711static int format_track_path(char *dest, char *src, int buf_length, int max, 1712static ssize_t format_track_path(char *dest, char *src, int buf_length,
1712 const char *dir) 1713 const char *dir)
1713{ 1714{
1714 int i = 0; 1715 size_t len;
1715 int j;
1716 char *temp_ptr;
1717
1718 /* Look for the end of the string */
1719 while((i < max) &&
1720 (src[i] != '\n') &&
1721 (src[i] != '\r') &&
1722 (src[i] != '\0'))
1723 i++;
1724
1725 /* Now work back killing white space */
1726 while((i > 0) &&
1727 ((src[i-1] == ' ') ||
1728 (src[i-1] == '\t')))
1729 i--;
1730 1716
1731 /* Zero-terminate the file name */ 1717 /* strip whitespace at beginning and end */
1732 src[i]=0; 1718 len = path_trim_whitespace(src, (const char **)&src);
1719 src[len] = '\0';
1733 1720
1734 /* replace backslashes with forward slashes */ 1721 /* replace backslashes with forward slashes */
1735 for ( j=0; j<i; j++ ) 1722 path_correct_separators(src, src);
1736 if ( src[j] == '\\' )
1737 src[j] = '/';
1738 1723
1739 if('/' == src[0]) 1724 /* handle DOS style drive letter and parse non-greedily so that:
1740 { 1725 * 1) "c:/foo" becomes "/foo" and the result is absolute
1741 strlcpy(dest, src, buf_length); 1726 * 2) "c:foo becomes "foo" and the result is relative
1742 } 1727 * This is how Windows seems to handle it except drive letters are of no
1743 else 1728 * meaning here. */
1744 { 1729 path_strip_drive(src, (const char **)&src, false);
1745 /* handle dos style drive letter */
1746 if (':' == src[1])
1747 strlcpy(dest, &src[2], buf_length);
1748 else if (!strncmp(src, "../", 3))
1749 {
1750 /* handle relative paths */
1751 i=3;
1752 while(!strncmp(&src[i], "../", 3))
1753 i += 3;
1754 for (j=0; j<i/3; j++) {
1755 temp_ptr = strrchr(dir, '/');
1756 if (temp_ptr)
1757 *temp_ptr = '\0';
1758 else
1759 break;
1760 }
1761 snprintf(dest, buf_length, "%s/%s", dir, &src[i]);
1762 }
1763 else if ( '.' == src[0] && '/' == src[1] ) {
1764 snprintf(dest, buf_length, "%s/%s", dir, &src[2]);
1765 }
1766 else {
1767 snprintf(dest, buf_length, "%s/%s", dir, src);
1768 }
1769 }
1770#ifdef HAVE_MULTIVOLUME
1771 1730
1772 char vol_string[VOL_ENUM_POS + 8]; 1731 /* prepends directory only if src is relative */
1773 snprintf(vol_string, sizeof(vol_string), "/"VOL_NAMES, 1); 1732 len = path_append(dest, *dir ? dir : PATH_ROOTSTR, src, buf_length);
1733 if (len >= (size_t)buf_length)
1734 return -1; /* buffer too small */
1774 1735
1775 /*check if the playlist is on a external card, and correct path if needed */ 1736 return len;
1776 if(strstr(dir, vol_string) && (strstr(dest, vol_string) == NULL)){
1777 char temp[buf_length];
1778 strlcpy(temp, dest, buf_length);
1779 snprintf(dest, buf_length, "%s%s", vol_string, temp);
1780 }
1781#endif
1782
1783 return 0;
1784} 1737}
1785 1738
1786/* 1739/*
@@ -3113,8 +3066,7 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam
3113{ 3066{
3114 int fd; 3067 int fd;
3115 int max; 3068 int max;
3116 char *temp_ptr; 3069 char *dir;
3117 const char *dir;
3118 unsigned char *count_str; 3070 unsigned char *count_str;
3119 char temp_buf[MAX_PATH+1]; 3071 char temp_buf[MAX_PATH+1];
3120 char trackname[MAX_PATH+1]; 3072 char trackname[MAX_PATH+1];
@@ -3139,13 +3091,8 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam
3139 } 3091 }
3140 3092
3141 /* we need the directory name for formatting purposes */ 3093 /* we need the directory name for formatting purposes */
3142 dir = filename; 3094 size_t dirlen = path_dirname(filename, (const char **)&dir);
3143 3095 dir = strmemdupa(dir, dirlen);
3144 temp_ptr = strrchr(filename+1,'/');
3145 if (temp_ptr)
3146 *temp_ptr = 0;
3147 else
3148 dir = "/";
3149 3096
3150 if (queue) 3097 if (queue)
3151 count_str = ID2P(LANG_PLAYLIST_QUEUE_COUNT); 3098 count_str = ID2P(LANG_PLAYLIST_QUEUE_COUNT);
@@ -3183,8 +3130,8 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam
3183 3130
3184 /* we need to format so that relative paths are correctly 3131 /* we need to format so that relative paths are correctly
3185 handled */ 3132 handled */
3186 if (format_track_path(trackname, temp_buf, sizeof(trackname), max, 3133 if (format_track_path(trackname, temp_buf, sizeof(trackname),
3187 dir) < 0) 3134 dir) < 0)
3188 { 3135 {
3189 result = -1; 3136 result = -1;
3190 break; 3137 break;
@@ -3223,9 +3170,6 @@ int playlist_insert_playlist(struct playlist_info* playlist, const char *filenam
3223 3170
3224 close(fd); 3171 close(fd);
3225 3172
3226 if (temp_ptr)
3227 *temp_ptr = '/';
3228
3229 sync_control(playlist, false); 3173 sync_control(playlist, false);
3230 3174
3231 cpu_boost(false); 3175 cpu_boost(false);
@@ -3537,9 +3481,9 @@ int playlist_save(struct playlist_info* playlist, char *filename,
3537 char path[MAX_PATH+1]; 3481 char path[MAX_PATH+1];
3538 char tmp_buf[MAX_PATH+1]; 3482 char tmp_buf[MAX_PATH+1];
3539 int result = 0; 3483 int result = 0;
3540 bool overwrite_current = false;
3541 int *seek_buf; 3484 int *seek_buf;
3542 bool reparse; 3485 bool reparse;
3486 ssize_t pathlen;
3543 3487
3544 ALIGN_BUFFER(temp_buffer, temp_buffer_size, sizeof(int)); 3488 ALIGN_BUFFER(temp_buffer, temp_buffer_size, sizeof(int));
3545 seek_buf = temp_buffer; 3489 seek_buf = temp_buffer;
@@ -3557,22 +3501,18 @@ int playlist_save(struct playlist_info* playlist, char *filename,
3557 return -1; 3501 return -1;
3558 3502
3559 /* use current working directory as base for pathname */ 3503 /* use current working directory as base for pathname */
3560 if (format_track_path(path, filename, sizeof(tmp_buf), 3504 pathlen = format_track_path(path, filename, sizeof(path), PATH_ROOTSTR);
3561 strlen(filename)+1, "/") < 0) 3505 if (pathlen < 0)
3506 return -1;
3507
3508 /* Use temporary pathname and overwrite/rename later */
3509 if (strlcat(path, "_temp", sizeof(path)) >= sizeof (path))
3562 return -1; 3510 return -1;
3563 3511
3564 /* can ignore volatile here, because core_get_data() is called later */ 3512 /* can ignore volatile here, because core_get_data() is called later */
3565 char* old_buffer = (char*)playlist->buffer; 3513 char* old_buffer = (char*)playlist->buffer;
3566 size_t old_buffer_size = playlist->buffer_size; 3514 size_t old_buffer_size = playlist->buffer_size;
3567 3515
3568 if (!strncmp(playlist->filename, path, strlen(path)))
3569 {
3570 /* Attempting to overwrite current playlist file.
3571 * use temporary pathname and overwrite later */
3572 strlcat(path, "_temp", sizeof(path));
3573 overwrite_current = true;
3574 }
3575
3576 if (is_m3u8(path)) 3516 if (is_m3u8(path))
3577 { 3517 {
3578 fd = open_utf8(path, O_CREAT|O_WRONLY|O_TRUNC); 3518 fd = open_utf8(path, O_CREAT|O_WRONLY|O_TRUNC);
@@ -3621,8 +3561,8 @@ int playlist_save(struct playlist_info* playlist, char *filename,
3621 break; 3561 break;
3622 } 3562 }
3623 3563
3624 if (overwrite_current && !reparse) 3564 if (!reparse)
3625 seek_buf[count] = lseek(fd, 0, SEEK_CUR); 3565 seek_buf[count] = filesize(fd);
3626 3566
3627 if (fdprintf(fd, "%s\n", tmp_buf) < 0) 3567 if (fdprintf(fd, "%s\n", tmp_buf) < 0)
3628 { 3568 {
@@ -3647,57 +3587,61 @@ int playlist_save(struct playlist_info* playlist, char *filename,
3647 index = (index+1)%playlist->amount; 3587 index = (index+1)%playlist->amount;
3648 } 3588 }
3649 3589
3650 display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT), true);
3651
3652 close(fd); 3590 close(fd);
3591 fd = -1;
3653 3592
3654 if (overwrite_current && result >= 0) 3593 display_playlist_count(count, ID2P(LANG_PLAYLIST_SAVE_COUNT), true);
3594
3595 if (result >= 0)
3655 { 3596 {
3656 result = -1; 3597 strmemcpy(tmp_buf, path, pathlen); /* remove "_temp" */
3657 3598
3658 mutex_lock(playlist->control_mutex); 3599 mutex_lock(playlist->control_mutex);
3659 3600
3660 /* Replace the current playlist with the new one and update indices */ 3601 if (!rename(path, tmp_buf))
3661 close(playlist->fd);
3662 playlist->fd = -1;
3663 if (remove(playlist->filename) >= 0)
3664 { 3602 {
3665 if (rename(path, playlist->filename) >= 0) 3603 fd = open_utf8(tmp_buf, O_RDONLY);
3604 if (fsamefile(fd, playlist->fd) > 0)
3666 { 3605 {
3667 playlist->fd = open_utf8(playlist->filename, O_RDONLY); 3606 /* Replace the current playlist with the new one and update
3668 if (playlist->fd >= 0) 3607 indices */
3608 close(playlist->fd);
3609 playlist->fd = fd;
3610 fd = -1;
3611
3612 if (!reparse)
3669 { 3613 {
3670 if (!reparse) 3614 index = playlist->first_index;
3615 for (i=0, count=0; i<playlist->amount; i++)
3671 { 3616 {
3672 index = playlist->first_index; 3617 if (!(playlist->indices[index] & PLAYLIST_QUEUE_MASK))
3673 for (i=0, count=0; i<playlist->amount; i++)
3674 { 3618 {
3675 if (!(playlist->indices[index] & PLAYLIST_QUEUE_MASK)) 3619 playlist->indices[index] = seek_buf[count];
3676 { 3620 count++;
3677 playlist->indices[index] = seek_buf[count];
3678 count++;
3679 }
3680 index = (index+1)%playlist->amount;
3681 } 3621 }
3622 index = (index+1)%playlist->amount;
3682 } 3623 }
3683 else
3684 {
3685 NOTEF("reparsing current playlist (slow)");
3686 playlist->amount = 0;
3687 add_indices_to_playlist(playlist, temp_buffer, temp_buffer_size);
3688 }
3689 /* we need to recreate control because inserted tracks are
3690 now part of the playlist and shuffle has been
3691 invalidated */
3692 result = recreate_control(playlist);
3693 } 3624 }
3694 } 3625 else
3695 } 3626 {
3627 NOTEF("reparsing current playlist (slow)");
3628 playlist->amount = 0;
3629 add_indices_to_playlist(playlist, temp_buffer,
3630 temp_buffer_size);
3631 }
3696 3632
3697 mutex_unlock(playlist->control_mutex); 3633 /* we need to recreate control because inserted tracks are
3634 now part of the playlist and shuffle has been invalidated */
3635 result = recreate_control(playlist);
3636 }
3637 }
3698 3638
3639 mutex_unlock(playlist->control_mutex);
3699 } 3640 }
3700 3641
3642 if (fd >= 0)
3643 close(fd);
3644
3701 cpu_boost(false); 3645 cpu_boost(false);
3702 3646
3703reset_old_buffer: 3647reset_old_buffer:
@@ -3759,8 +3703,12 @@ int playlist_directory_tracksearch(const char* dirname, bool recurse,
3759 if (recurse) 3703 if (recurse)
3760 { 3704 {
3761 /* recursively add directories */ 3705 /* recursively add directories */
3762 snprintf(buf, sizeof(buf), "%s/%s", 3706 if (path_append(buf, dirname, files[i].name, sizeof(buf))
3763 dirname[1]? dirname: "", files[i].name); 3707 >= sizeof(buf))
3708 {
3709 continue;
3710 }
3711
3764 result = playlist_directory_tracksearch(buf, recurse, 3712 result = playlist_directory_tracksearch(buf, recurse,
3765 callback, context); 3713 callback, context);
3766 if (result < 0) 3714 if (result < 0)
@@ -3785,8 +3733,11 @@ int playlist_directory_tracksearch(const char* dirname, bool recurse,
3785 } 3733 }
3786 else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO) 3734 else if ((files[i].attr & FILE_ATTR_MASK) == FILE_ATTR_AUDIO)
3787 { 3735 {
3788 snprintf(buf, sizeof(buf), "%s/%s", 3736 if (path_append(buf, dirname, files[i].name, sizeof(buf))
3789 dirname[1]? dirname: "", files[i].name); 3737 >= sizeof(buf))
3738 {
3739 continue;
3740 }
3790 3741
3791 if (callback(buf, context) != 0) 3742 if (callback(buf, context) != 0)
3792 { 3743 {
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 @@
32#include "lang.h" 32#include "lang.h"
33#include "list.h" 33#include "list.h"
34#include "misc.h" 34#include "misc.h"
35#include "filefuncs.h" 35#include "pathfuncs.h"
36#include "onplay.h" 36#include "onplay.h"
37#include "playlist.h" 37#include "playlist.h"
38#include "settings.h" 38#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 @@
18 * KIND, either express or implied. 18 * KIND, either express or implied.
19 * 19 *
20 ****************************************************************************/ 20 ****************************************************************************/
21#define DIRFUNCTIONS_DEFINED
22#define FILEFUNCTIONS_DEFINED
21#include "plugin.h" 23#include "plugin.h"
22#include <ctype.h> 24#include <ctype.h>
23#include <string.h> 25#include <string.h>
@@ -40,8 +42,9 @@
40#include "pcmbuf.h" 42#include "pcmbuf.h"
41#include "errno.h" 43#include "errno.h"
42#include "diacritic.h" 44#include "diacritic.h"
43#include "filefuncs.h" 45#include "pathfuncs.h"
44#include "load_code.h" 46#include "load_code.h"
47#include "file.h"
45 48
46#if CONFIG_CHARGING 49#if CONFIG_CHARGING
47#include "power.h" 50#include "power.h"
@@ -58,80 +61,119 @@
58#include "usbstack/usb_hid.h" 61#include "usbstack/usb_hid.h"
59#endif 62#endif
60 63
61#if defined (SIMULATOR) 64#define WRAPPER(_x_) _x_ ## _wrapper
62#define PREFIX(_x_) sim_ ## _x_ 65
63#elif defined (APPLICATION) 66#if (CONFIG_PLATFORM & PLATFORM_HOSTED)
64#define PREFIX(_x_) app_ ## _x_ 67static unsigned char pluginbuf[PLUGIN_BUFFER_SIZE];
68void sim_lcd_ex_init(unsigned long (*getpixel)(int, int));
69void sim_lcd_ex_update_rect(int x, int y, int width, int height);
65#else 70#else
66#define PREFIX(_x_) _x_ 71extern unsigned char pluginbuf[];
72#include "bitswap.h"
67#endif 73#endif
68 74
69#if defined (APPLICATION) 75/* for actual plugins only, not for codecs */
70/* For symmetry reasons (we want app_ and sim_ to behave similarly), some 76static int plugin_size = 0;
71 * wrappers are needed */ 77static bool (*pfn_tsr_exit)(bool reenter) = NULL; /* TSR exit callback */
72static int app_close(int fd) 78static char current_plugin[MAX_PATH];
79/* NULL if no plugin is loaded, otherwise the handle that lc_open() returned */
80static void *current_plugin_handle;
81
82char *plugin_get_current_filename(void);
83
84static void* plugin_get_audio_buffer(size_t *buffer_size);
85static void plugin_release_audio_buffer(void);
86static void plugin_tsr(bool (*exit_callback)(bool));
87
88
89#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
90/* File handle leak prophylaxis */
91#include "bitarray.h"
92#include "file_internal.h" /* for MAX_OPEN_FILES */
93
94#define PCOC_WRAPPER(_x_) WRAPPER(_x_)
95
96BITARRAY_TYPE_DECLARE(plugin_check_open_close_bitmap_t, open_files_bitmap,
97 MAX_OPEN_FILES)
98
99static plugin_check_open_close_bitmap_t open_files_bitmap;
100
101static void plugin_check_open_close__enter(void)
73{ 102{
74 return close(fd); 103 if (!current_plugin_handle)
104 open_files_bitmap_clear(&open_files_bitmap);
75} 105}
76 106
77static ssize_t app_read(int fd, void *buf, size_t count) 107static void plugin_check_open_close__open(int fildes)
78{ 108{
79 return read(fd,buf,count); 109 if (fildes >= 0)
110 open_files_bitmap_set_bit(&open_files_bitmap, fildes);
80} 111}
81 112
82static off_t app_lseek(int fd, off_t offset, int whence) 113static void plugin_check_open_close__close(int fildes)
83{ 114{
84 return lseek(fd,offset,whence); 115 if (fildes < 0)
116 return;
117
118 if (!open_files_bitmap_test_bit(&open_files_bitmap, fildes))
119 {
120 logf("double close from plugin");
121 }
122
123 open_files_bitmap_clear_bit(&open_files_bitmap, fildes);
85} 124}
86 125
87static ssize_t app_write(int fd, const void *buf, size_t count) 126static int WRAPPER(open)(const char *path, int oflag, ...)
88{ 127{
89 return write(fd,buf,count); 128 int fildes = FS_PREFIX(open)(path, oflag __OPEN_MODE_ARG);
129 plugin_check_open_close__open(fildes);
130 return fildes;
90} 131}
91 132
92static int app_ftruncate(int fd, off_t length) 133static int WRAPPER(creat)(const char *path, mode_t mode)
93{ 134{
94 return ftruncate(fd,length); 135 int fildes = FS_PREFIX(creat)(path __CREAT_MODE_ARG);
136 plugin_check_open_close__open(fildes);
137 return fildes;
138 (void)mode;
95} 139}
96#endif
97 140
98#if defined(HAVE_PLUGIN_CHECK_OPEN_CLOSE) && (MAX_OPEN_FILES>32) 141static int WRAPPER(close)(int fildes)
99#warning "MAX_OPEN_FILES>32, disabling plugin file open/close checking" 142{
100#undef HAVE_PLUGIN_CHECK_OPEN_CLOSE 143 int rc = FS_PREFIX(close)(fildes);
101#endif 144 if (rc >= 0)
145 plugin_check_open_close__close(fildes);
102 146
103#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE 147 return rc;
104static unsigned int open_files; 148}
105#endif
106 149
107#if (CONFIG_PLATFORM & PLATFORM_HOSTED) 150static void plugin_check_open_close__exit(void)
108static unsigned char pluginbuf[PLUGIN_BUFFER_SIZE]; 151{
109void sim_lcd_ex_init(unsigned long (*getpixel)(int, int)); 152 if (current_plugin_handle)
110void sim_lcd_ex_update_rect(int x, int y, int width, int height); 153 return;
111#else
112extern unsigned char pluginbuf[];
113#include "bitswap.h"
114#endif
115 154
116/* for actual plugins only, not for codecs */ 155 if (open_files_bitmap_is_clear(&open_files_bitmap))
117static int plugin_size = 0; 156 return;
118static bool (*pfn_tsr_exit)(bool reenter) = NULL; /* TSR exit callback */
119static char current_plugin[MAX_PATH];
120/* NULL if no plugin is loaded, otherwise the handle that lc_open() returned */
121static void *current_plugin_handle;
122 157
123char *plugin_get_current_filename(void); 158 logf("Plugin '%s' leaks file handles", plugin);
124 159
125/* Some wrappers used to monitor open and close and detect leaks*/ 160 static const char *lines[] =
126static int open_wrapper(const char* pathname, int flags, ...); 161 { ID2P(LANG_PLUGIN_ERROR), "#leak-file-handles" };
127#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE 162 static const struct text_message message = { lines, 2 };
128static int close_wrapper(int fd); 163 button_clear_queue(); /* Empty the keyboard buffer */
129static int creat_wrapper(const char *pathname, mode_t mode); 164 gui_syncyesno_run(&message, NULL, NULL);
130#endif
131 165
132static void* plugin_get_audio_buffer(size_t *buffer_size); 166 FOR_EACH_BITARRAY_SET_BIT(&open_files_bitmap, fildes)
133static void plugin_release_audio_buffer(void); 167 WRAPPER(close)(fildes);
134static void plugin_tsr(bool (*exit_callback)(bool)); 168}
169
170#else /* !HAVE_PLUGIN_CHECK_OPEN_CLOSE */
171
172#define PCOC_WRAPPER(_x_) FS_PREFIX(_x_)
173#define plugin_check_open_close__enter()
174#define plugin_check_open_close__exit()
175
176#endif /* HAVE_PLUGIN_CHECK_OPEN_CLOSE */
135 177
136static const struct plugin_api rockbox_api = { 178static const struct plugin_api rockbox_api = {
137 179
@@ -339,24 +381,16 @@ static const struct plugin_api rockbox_api = {
339 381
340 /* file */ 382 /* file */
341 open_utf8, 383 open_utf8,
342 (open_func)open_wrapper, 384 PCOC_WRAPPER(open),
343#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE 385 PCOC_WRAPPER(creat),
344 close_wrapper, 386 PCOC_WRAPPER(close),
345#else 387 FS_PREFIX(read),
346 PREFIX(close), 388 FS_PREFIX(lseek),
347#endif 389 FS_PREFIX(write),
348 (read_func)PREFIX(read), 390 FS_PREFIX(remove),
349 PREFIX(lseek), 391 FS_PREFIX(rename),
350#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE 392 FS_PREFIX(ftruncate),
351 (creat_func)creat_wrapper, 393 FS_PREFIX(filesize),
352#else
353 PREFIX(creat),
354#endif
355 (write_func)PREFIX(write),
356 PREFIX(remove),
357 PREFIX(rename),
358 PREFIX(ftruncate),
359 filesize,
360 fdprintf, 394 fdprintf,
361 read_line, 395 read_line,
362 settings_parseline, 396 settings_parseline,
@@ -369,18 +403,18 @@ static const struct plugin_api rockbox_api = {
369#endif /* USING_STORAGE_CALLBACK */ 403#endif /* USING_STORAGE_CALLBACK */
370 reload_directory, 404 reload_directory,
371 create_numbered_filename, 405 create_numbered_filename,
372 file_exists, 406 FS_PREFIX(file_exists),
373 strip_extension, 407 strip_extension,
374 crc_32, 408 crc_32,
375 filetype_get_attr, 409 filetype_get_attr,
376 410
377 /* dir */ 411 /* dir */
378 (opendir_func)opendir, 412 FS_PREFIX(opendir),
379 (closedir_func)closedir, 413 FS_PREFIX(closedir),
380 (readdir_func)readdir, 414 FS_PREFIX(readdir),
381 mkdir, 415 FS_PREFIX(mkdir),
382 rmdir, 416 FS_PREFIX(rmdir),
383 dir_exists, 417 FS_PREFIX(dir_exists),
384 dir_get_info, 418 dir_get_info,
385 419
386 /* browsing */ 420 /* browsing */
@@ -688,10 +722,11 @@ static const struct plugin_api rockbox_api = {
688#endif 722#endif
689 srand, 723 srand,
690 rand, 724 rand,
691 (qsort_func)qsort, 725 (void *)qsort,
692 kbd_input, 726 kbd_input,
693 get_time, 727 get_time,
694 set_time, 728 set_time,
729 gmtime_r,
695#if CONFIG_RTC 730#if CONFIG_RTC
696 mktime, 731 mktime,
697#endif 732#endif
@@ -891,9 +926,7 @@ int plugin_load(const char* plugin, const void* parameter)
891 /* allow voice to back off if the plugin needs lots of memory */ 926 /* allow voice to back off if the plugin needs lots of memory */
892 talk_buffer_set_policy(TALK_BUFFER_LOOSE); 927 talk_buffer_set_policy(TALK_BUFFER_LOOSE);
893 928
894#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE 929 plugin_check_open_close__enter();
895 open_files = 0;
896#endif
897 930
898 int rc = p_hdr->entry_point(parameter); 931 int rc = p_hdr->entry_point(parameter);
899 932
@@ -947,24 +980,7 @@ int plugin_load(const char* plugin, const void* parameter)
947 FOR_NB_SCREENS(i) 980 FOR_NB_SCREENS(i)
948 viewportmanager_theme_undo(i, true); 981 viewportmanager_theme_undo(i, true);
949 982
950#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE 983 plugin_check_open_close__exit();
951 if(open_files != 0 && !current_plugin_handle)
952 {
953 int fd;
954 logf("Plugin '%s' leaks file handles", plugin);
955
956 static const char *lines[] =
957 { ID2P(LANG_PLUGIN_ERROR),
958 "#leak-file-handles" };
959 static const struct text_message message={ lines, 2 };
960 button_clear_queue(); /* Empty the keyboard buffer */
961 gui_syncyesno_run(&message, NULL, NULL);
962
963 for(fd=0; fd < MAX_OPEN_FILES; fd++)
964 if(open_files & (1<<fd))
965 close_wrapper(fd);
966 }
967#endif
968 984
969 if (rc == PLUGIN_ERROR) 985 if (rc == PLUGIN_ERROR)
970 splash(HZ*2, str(LANG_PLUGIN_ERROR)); 986 splash(HZ*2, str(LANG_PLUGIN_ERROR));
@@ -1027,55 +1043,3 @@ char *plugin_get_current_filename(void)
1027{ 1043{
1028 return current_plugin; 1044 return current_plugin;
1029} 1045}
1030
1031static int open_wrapper(const char* pathname, int flags, ...)
1032{
1033/* we don't have an 'open' function. it's a define. and we need
1034 * the real file_open, hence PREFIX() doesn't work here */
1035 int fd;
1036#if (CONFIG_PLATFORM & PLATFORM_HOSTED)
1037 if (flags & O_CREAT)
1038 {
1039 va_list ap;
1040 va_start(ap, flags);
1041 fd = open(pathname, flags, va_arg(ap, unsigned int));
1042 va_end(ap);
1043 }
1044 else
1045 fd = open(pathname, flags);
1046#else
1047 fd = file_open(pathname,flags);
1048#endif
1049
1050#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
1051 if(fd >= 0)
1052 open_files |= 1<<fd;
1053#endif
1054 return fd;
1055}
1056
1057#ifdef HAVE_PLUGIN_CHECK_OPEN_CLOSE
1058static int close_wrapper(int fd)
1059{
1060 if((~open_files) & (1<<fd))
1061 {
1062 logf("double close from plugin");
1063 }
1064 if(fd >= 0)
1065 open_files &= (~(1<<fd));
1066
1067 return PREFIX(close)(fd);
1068}
1069
1070static int creat_wrapper(const char *pathname, mode_t mode)
1071{
1072 (void)mode;
1073
1074 int fd = PREFIX(creat)(pathname, mode);
1075
1076 if(fd >= 0)
1077 open_files |= (1<<fd);
1078
1079 return fd;
1080}
1081#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);
75#include "profile.h" 75#include "profile.h"
76#endif 76#endif
77#include "misc.h" 77#include "misc.h"
78#include "filefuncs.h" 78#include "pathfuncs.h"
79#if (CONFIG_CODEC == SWCODEC) 79#if (CONFIG_CODEC == SWCODEC)
80#include "pcm_mixer.h" 80#include "pcm_mixer.h"
81#include "dsp-util.h" 81#include "dsp-util.h"
@@ -160,12 +160,12 @@ void* plugin_get_buffer(size_t *buffer_size);
160#define PLUGIN_MAGIC 0x526F634B /* RocK */ 160#define PLUGIN_MAGIC 0x526F634B /* RocK */
161 161
162/* increase this every time the api struct changes */ 162/* increase this every time the api struct changes */
163#define PLUGIN_API_VERSION 231 163#define PLUGIN_API_VERSION 232
164 164
165/* update this to latest version if a change to the api struct breaks 165/* update this to latest version if a change to the api struct breaks
166 backwards compatibility (and please take the opportunity to sort in any 166 backwards compatibility (and please take the opportunity to sort in any
167 new function which are "waiting" at the end of the function table) */ 167 new function which are "waiting" at the end of the function table) */
168#define PLUGIN_MIN_API_VERSION 231 168#define PLUGIN_MIN_API_VERSION 232
169 169
170/* plugin return codes */ 170/* plugin return codes */
171/* internal returns start at 0x100 to make exit(1..255) work */ 171/* internal returns start at 0x100 to make exit(1..255) work */
@@ -433,17 +433,17 @@ struct plugin_api {
433 433
434 /* file */ 434 /* file */
435 int (*open_utf8)(const char* pathname, int flags); 435 int (*open_utf8)(const char* pathname, int flags);
436 int (*open)(const char* pathname, int flags, ...); 436 int (*open)(const char *path, int oflag, ...);
437 int (*close)(int fd); 437 int (*creat)(const char *path, mode_t mode);
438 ssize_t (*read)(int fd, void* buf, size_t count); 438 int (*close)(int fildes);
439 off_t (*lseek)(int fd, off_t offset, int whence); 439 ssize_t (*read)(int fildes, void *buf, size_t nbyte);
440 int (*creat)(const char *pathname, mode_t mode); 440 off_t (*lseek)(int fildes, off_t offset, int whence);
441 ssize_t (*write)(int fd, const void* buf, size_t count); 441 ssize_t (*write)(int fildes, const void *buf, size_t nbyte);
442 int (*remove)(const char* pathname); 442 int (*remove)(const char *path);
443 int (*rename)(const char* path, const char* newname); 443 int (*rename)(const char *old, const char *new);
444 int (*ftruncate)(int fd, off_t length); 444 int (*ftruncate)(int fildes, off_t length);
445 off_t (*filesize)(int fd); 445 off_t (*filesize)(int fildes);
446 int (*fdprintf)(int fd, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3); 446 int (*fdprintf)(int fildes, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
447 int (*read_line)(int fd, char* buffer, int buffer_size); 447 int (*read_line)(int fd, char* buffer, int buffer_size);
448 bool (*settings_parseline)(char* line, char** name, char** value); 448 bool (*settings_parseline)(char* line, char** name, char** value);
449 void (*storage_sleep)(void); 449 void (*storage_sleep)(void);
@@ -457,7 +457,7 @@ struct plugin_api {
457 char *(*create_numbered_filename)(char *buffer, const char *path, 457 char *(*create_numbered_filename)(char *buffer, const char *path,
458 const char *prefix, const char *suffix, 458 const char *prefix, const char *suffix,
459 int numberlen IF_CNFN_NUM_(, int *num)); 459 int numberlen IF_CNFN_NUM_(, int *num));
460 bool (*file_exists)(const char *file); 460 bool (*file_exists)(const char *path);
461 char* (*strip_extension)(char* buffer, int buffer_size, const char *filename); 461 char* (*strip_extension)(char* buffer, int buffer_size, const char *filename);
462 uint32_t (*crc_32)(const void *src, uint32_t len, uint32_t crc32); 462 uint32_t (*crc_32)(const void *src, uint32_t len, uint32_t crc32);
463 463
@@ -466,13 +466,13 @@ struct plugin_api {
466 466
467 467
468 /* dir */ 468 /* dir */
469 DIR* (*opendir)(const char* name); 469 DIR * (*opendir)(const char *dirname);
470 int (*closedir)(DIR* dir); 470 int (*closedir)(DIR *dirp);
471 struct dirent* (*readdir)(DIR* dir); 471 struct dirent * (*readdir)(DIR *dirp);
472 int (*mkdir)(const char *name); 472 int (*mkdir)(const char *path);
473 int (*rmdir)(const char *name); 473 int (*rmdir)(const char *path);
474 bool (*dir_exists)(const char *path); 474 bool (*dir_exists)(const char *dirname);
475 struct dirinfo (*dir_get_info)(DIR* parent, struct dirent *entry); 475 struct dirinfo (*dir_get_info)(DIR *dirp, struct dirent *entry);
476 476
477 /* browsing */ 477 /* browsing */
478 void (*browse_context_init)(struct browse_context *browse, 478 void (*browse_context_init)(struct browse_context *browse,
@@ -838,6 +838,7 @@ struct plugin_api {
838 int (*kbd_input)(char* buffer, int buflen); 838 int (*kbd_input)(char* buffer, int buflen);
839 struct tm* (*get_time)(void); 839 struct tm* (*get_time)(void);
840 int (*set_time)(const struct tm *tm); 840 int (*set_time)(const struct tm *tm);
841 struct tm * (*gmtime_r)(const time_t *timep, struct tm *tm);
841#if CONFIG_RTC 842#if CONFIG_RTC
842 time_t (*mktime)(struct tm *t); 843 time_t (*mktime)(struct tm *t);
843#endif 844#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)
99 log = human_size_log((unsigned long)info.size); 99 log = human_size_log((unsigned long)info.size);
100 rb->snprintf(str_size, sizeof str_size, "%lu %cB", 100 rb->snprintf(str_size, sizeof str_size, "%lu %cB",
101 ((unsigned long)info.size) >> (log*10), human_size_prefix[log]); 101 ((unsigned long)info.size) >> (log*10), human_size_prefix[log]);
102 struct tm tm;
103 rb->gmtime_r(&info.mtime, &tm);
102 rb->snprintf(str_date, sizeof str_date, "%04d/%02d/%02d", 104 rb->snprintf(str_date, sizeof str_date, "%04d/%02d/%02d",
103 ((info.wrtdate >> 9 ) & 0x7F) + 1980, /* year */ 105 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
104 ((info.wrtdate >> 5 ) & 0x0F), /* month */ 106 rb->snprintf(str_time, sizeof str_time, "%02d:%02d:%02d",
105 ((info.wrtdate ) & 0x1F)); /* day */ 107 tm.tm_hour, tm.tm_min, tm.tm_sec);
106 rb->snprintf(str_time, sizeof str_time, "%02d:%02d",
107 ((info.wrttime >> 11) & 0x1F), /* hour */
108 ((info.wrttime >> 5 ) & 0x3F)); /* minutes */
109 108
110 num_properties = 5; 109 num_properties = 5;
111 110
@@ -175,7 +174,10 @@ static bool _dir_properties(DPS* dps)
175 dirlen = rb->strlen(dps->dirname); 174 dirlen = rb->strlen(dps->dirname);
176 dir = rb->opendir(dps->dirname); 175 dir = rb->opendir(dps->dirname);
177 if (!dir) 176 if (!dir)
177 {
178 rb->splashf(HZ*2, "%s", dps->dirname);
178 return false; /* open error */ 179 return false; /* open error */
180 }
179 181
180 /* walk through the directory content */ 182 /* walk through the directory content */
181 while(result && (0 != (entry = rb->readdir(dir)))) 183 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 @@
30#include "file.h" 30#include "file.h"
31#include "string-extra.h" 31#include "string-extra.h"
32#include "misc.h" 32#include "misc.h"
33#include "filefuncs.h" 33#include "pathfuncs.h"
34#include "lang.h" 34#include "lang.h"
35#include "action.h" 35#include "action.h"
36#include "list.h" 36#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 @@
31#include "file.h" 31#include "file.h"
32#include "kernel.h" 32#include "kernel.h"
33#include "string-extra.h" 33#include "string-extra.h"
34#include "filefuncs.h" 34#include "pathfuncs.h"
35#include "core_alloc.h" 35#include "core_alloc.h"
36 36
37#define MAX_RADIOART_IMAGES 10 37#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 @@
27#include "buffering.h" 27#include "buffering.h"
28#include "dircache.h" 28#include "dircache.h"
29#include "misc.h" 29#include "misc.h"
30#include "filefuncs.h" 30#include "pathfuncs.h"
31#include "settings.h" 31#include "settings.h"
32#include "wps.h" 32#include "wps.h"
33 33
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 @@
56#include "timefuncs.h" 56#include "timefuncs.h"
57#include "debug.h" 57#include "debug.h"
58#include "misc.h" 58#include "misc.h"
59#include "filefuncs.h" 59#include "pathfuncs.h"
60#include "tree.h" 60#include "tree.h"
61#include "string.h" 61#include "string.h"
62#include "dir.h" 62#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)
146 int i; 146 int i;
147 for (i = 0; i < NUM_VOLUMES; i++) 147 for (i = 0; i < NUM_VOLUMES; i++)
148 { 148 {
149 char vol_string[VOL_ENUM_POS + 8]; 149 char vol_string[VOL_MAX_LEN + 1];
150 if (!volume_removable(i)) 150 if (!volume_removable(i))
151 continue; 151 continue;
152 /* VOL_NAMES contains a %d */ 152 get_volume_name(i, vol_string);
153 snprintf(vol_string, sizeof(vol_string), "/"VOL_NAMES, i);
154 /* test whether we would browse the external card */ 153 /* test whether we would browse the external card */
155 if (!volume_present(i) && 154 if (!volume_present(i) &&
156 (strstr(last_folder, vol_string) 155 (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
33#include "core_alloc.h" 33#include "core_alloc.h"
34#include "settings.h" 34#include "settings.h"
35#include "ata_idle_notify.h" 35#include "ata_idle_notify.h"
36#include "filefuncs.h" 36#include "pathfuncs.h"
37#include "appevents.h" 37#include "appevents.h"
38 38
39#if CONFIG_RTC 39#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)
822#ifdef HAVE_LCD_BITMAP 822#ifdef HAVE_LCD_BITMAP
823 int rc; 823 int rc;
824#endif 824#endif
825 CHART(">set_codepage");
826 set_codepage(global_settings.default_codepage);
827 CHART("<set_codepage");
828
825 sound_settings_apply(); 829 sound_settings_apply();
826 830
827#ifdef HAVE_DISK_STORAGE 831#ifdef HAVE_DISK_STORAGE
@@ -1008,10 +1012,6 @@ void settings_apply(bool read_disk)
1008 lcd_scroll_delay(global_settings.scroll_delay); 1012 lcd_scroll_delay(global_settings.scroll_delay);
1009 1013
1010 1014
1011 CHART(">set_codepage");
1012 set_codepage(global_settings.default_codepage);
1013 CHART("<set_codepage");
1014
1015#ifdef HAVE_PLAY_FREQ 1015#ifdef HAVE_PLAY_FREQ
1016 settings_apply_play_freq(global_settings.play_frequency, false); 1016 settings_apply_play_freq(global_settings.play_frequency, false);
1017#endif 1017#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[] = {
1724 OFFON_SETTING(F_BANFROMQS, tagcache_autoupdate, LANG_TAGCACHE_AUTOUPDATE, false, 1724 OFFON_SETTING(F_BANFROMQS, tagcache_autoupdate, LANG_TAGCACHE_AUTOUPDATE, false,
1725 "tagcache_autoupdate", NULL), 1725 "tagcache_autoupdate", NULL),
1726#endif 1726#endif
1727 CHOICE_SETTING(0, default_codepage, LANG_DEFAULT_CODEPAGE, 0, 1727 CHOICE_SETTING(F_TEMPVAR, default_codepage, LANG_DEFAULT_CODEPAGE, 0,
1728 "default codepage", 1728 "default codepage",
1729#ifdef HAVE_LCD_BITMAP 1729#ifdef HAVE_LCD_BITMAP
1730 /* The order must match with that in unicode.c */ 1730 /* The order must match with that in unicode.c */
1731 "iso8859-1,iso8859-7,iso8859-8,cp1251,iso8859-11,cp1256," 1731 "iso8859-1,iso8859-7,iso8859-8,cp1251,iso8859-11,cp1256,"
1732 "iso8859-9,iso8859-2,cp1250,cp1252,sjis,gb2312,ksx1001,big5,utf-8", 1732 "iso8859-9,iso8859-2,cp1250,cp1252,sjis,gb2312,ksx1001,big5,utf-8",
1733 set_codepage, 15, 1733 NULL, 15,
1734 ID2P(LANG_CODEPAGE_LATIN1), 1734 ID2P(LANG_CODEPAGE_LATIN1),
1735 ID2P(LANG_CODEPAGE_GREEK), 1735 ID2P(LANG_CODEPAGE_GREEK),
1736 ID2P(LANG_CODEPAGE_HEBREW), ID2P(LANG_CODEPAGE_CYRILLIC), 1736 ID2P(LANG_CODEPAGE_HEBREW), ID2P(LANG_CODEPAGE_CYRILLIC),
@@ -1745,7 +1745,7 @@ const struct settings_list settings[] = {
1745#else /* !HAVE_LCD_BITMAP */ 1745#else /* !HAVE_LCD_BITMAP */
1746 /* The order must match with that in unicode.c */ 1746 /* The order must match with that in unicode.c */
1747 "iso8859-1,iso8859-7,cp1251,iso8859-9,iso8859-2,cp1250,cp1252,utf-8", 1747 "iso8859-1,iso8859-7,cp1251,iso8859-9,iso8859-2,cp1250,cp1252,utf-8",
1748 set_codepage, 8, 1748 NULL, 8,
1749 ID2P(LANG_CODEPAGE_LATIN1), ID2P(LANG_CODEPAGE_GREEK), 1749 ID2P(LANG_CODEPAGE_LATIN1), ID2P(LANG_CODEPAGE_GREEK),
1750 ID2P(LANG_CODEPAGE_CYRILLIC), ID2P(LANG_CODEPAGE_TURKISH), 1750 ID2P(LANG_CODEPAGE_CYRILLIC), ID2P(LANG_CODEPAGE_TURKISH),
1751 ID2P(LANG_CODEPAGE_LATIN_EXTENDED), 1751 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 @@
37#include "misc.h" 37#include "misc.h"
38#include "tree.h" 38#include "tree.h"
39#include "splash.h" 39#include "splash.h"
40#include "filefuncs.h" 40#include "pathfuncs.h"
41#include "filetypes.h" 41#include "filetypes.h"
42#include "shortcuts.h" 42#include "shortcuts.h"
43#include "onplay.h" 43#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 @@
79#include "misc.h" 79#include "misc.h"
80#include "settings.h" 80#include "settings.h"
81#include "dir.h" 81#include "dir.h"
82#include "filefuncs.h" 82#include "pathfuncs.h"
83#include "structec.h" 83#include "structec.h"
84#include "debug.h" 84#include "debug.h"
85 85
@@ -88,6 +88,8 @@
88#include "eeprom_settings.h" 88#include "eeprom_settings.h"
89#endif 89#endif
90 90
91#undef HAVE_DIRCACHE
92
91#ifdef __PCTOOL__ 93#ifdef __PCTOOL__
92#define yield() do { } while(0) 94#define yield() do { } while(0)
93#define sim_sleep(timeout) do { } while(0) 95#define sim_sleep(timeout) do { } while(0)
@@ -2986,20 +2988,21 @@ static bool commit(void)
2986 /* Try to steal every buffer we can :) */ 2988 /* Try to steal every buffer we can :) */
2987 if (tempbuf_size == 0) 2989 if (tempbuf_size == 0)
2988 local_allocation = true; 2990 local_allocation = true;
2989 2991
2992#if 0 /* FIXME: How much big? dircache buffer can no longer be taken but
2993 may be freed to make room and the cache resumed. --jethead71 */
2990#ifdef HAVE_DIRCACHE 2994#ifdef HAVE_DIRCACHE
2991 if (tempbuf_size == 0) 2995 if (tempbuf_size == 0)
2992 { 2996 {
2993 /* Try to steal the dircache buffer. */ 2997 /* Shut down dircache to free its allocation. */
2994 tempbuf = dircache_steal_buffer(&tempbuf_size); 2998 dircache_free_buffer();
2995 tempbuf_size &= ~0x03;
2996
2997 if (tempbuf_size > 0) 2999 if (tempbuf_size > 0)
2998 { 3000 {
2999 dircache_buffer_stolen = true; 3001 dircache_buffer_stolen = true;
3000 } 3002 }
3001 } 3003 }
3002#endif 3004#endif
3005#endif
3003 3006
3004#ifdef HAVE_TC_RAMCACHE 3007#ifdef HAVE_TC_RAMCACHE
3005 if (tempbuf_size == 0 && tc_stat.ramcache_allocated > 0) 3008 if (tempbuf_size == 0 && tc_stat.ramcache_allocated > 0)
@@ -4462,7 +4465,7 @@ static bool check_dir(const char *dirname, int add_files)
4462 tc_stat.curentry = curpath; 4465 tc_stat.curentry = curpath;
4463 4466
4464 /* Add a new entry to the temporary db file. */ 4467 /* Add a new entry to the temporary db file. */
4465 add_tagcache(curpath, (info.wrtdate << 16) | info.wrttime 4468 add_tagcache(curpath, info.mtime
4466#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE) 4469#if defined(HAVE_TC_RAMCACHE) && defined(HAVE_DIRCACHE)
4467 , dir->internal_entry 4470 , dir->internal_entry
4468#endif 4471#endif
@@ -4780,7 +4783,7 @@ void tagcache_shutdown(void)
4780 /* Flush the command queue. */ 4783 /* Flush the command queue. */
4781 run_command_queue(true); 4784 run_command_queue(true);
4782 4785
4783#ifdef HAVE_EEPROM_SETTINGS 4786#if defined(HAVE_EEPROM_SETTINGS) && defined(HAVE_TC_RAMCACHE)
4784 if (tc_stat.ramcache) 4787 if (tc_stat.ramcache)
4785 tagcache_dumpsave(); 4788 tagcache_dumpsave();
4786#endif 4789#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 @@
52#include "talk.h" 52#include "talk.h"
53#include "filetypes.h" 53#include "filetypes.h"
54#include "misc.h" 54#include "misc.h"
55#include "filefuncs.h" 55#include "pathfuncs.h"
56#include "filetree.h" 56#include "filetree.h"
57#include "tagtree.h" 57#include "tagtree.h"
58#ifdef HAVE_RECORDING 58#ifdef HAVE_RECORDING
@@ -1205,26 +1205,36 @@ void tree_flush(void)
1205#endif 1205#endif
1206 1206
1207#ifdef HAVE_DIRCACHE 1207#ifdef HAVE_DIRCACHE
1208 int old_val = global_status.dircache_size;
1209#ifdef HAVE_EEPROM_SETTINGS
1210 bool savecache = false;
1211#endif
1212
1213 if (global_settings.dircache)
1208 { 1214 {
1209 int old_val = global_status.dircache_size; 1215 dircache_suspend();
1210 if (global_settings.dircache) 1216
1211 { 1217 struct dircache_info info;
1212 if (!dircache_is_initializing()) 1218 dircache_get_info(&info);
1213 global_status.dircache_size = dircache_get_cache_size(); 1219
1214# ifdef HAVE_EEPROM_SETTINGS 1220 global_status.dircache_size = info.last_size;
1215 if (firmware_settings.initialized) 1221 #ifdef HAVE_EEPROM_SETTINGS
1216 dircache_save(); 1222 savecache = firmware_settings.initialized;
1217# endif 1223 #endif
1218 dircache_suspend();
1219 }
1220 else
1221 {
1222 global_status.dircache_size = 0;
1223 }
1224 if (old_val != global_status.dircache_size)
1225 status_save();
1226 } 1224 }
1227#endif 1225 else
1226 {
1227 global_status.dircache_size = 0;
1228 }
1229
1230 if (old_val != global_status.dircache_size)
1231 status_save();
1232
1233 #ifdef HAVE_EEPROM_SETTINGS
1234 if (savecache)
1235 dircache_save();
1236 #endif
1237#endif /* HAVE_DIRCACHE */
1228} 1238}
1229 1239
1230void tree_restore(void) 1240void tree_restore(void)
@@ -1238,15 +1248,14 @@ void tree_restore(void)
1238#endif 1248#endif
1239 1249
1240#ifdef HAVE_DIRCACHE 1250#ifdef HAVE_DIRCACHE
1241 remove(DIRCACHE_FILE); 1251 if (global_settings.dircache && dircache_resume() > 0)
1242 if (global_settings.dircache)
1243 { 1252 {
1244 /* Print "Scanning disk..." to the display. */ 1253 /* Print "Scanning disk..." to the display. */
1245 splash(0, str(LANG_SCANNING_DISK)); 1254 splash(0, str(LANG_SCANNING_DISK));
1246 1255 dircache_wait();
1247 dircache_build(global_status.dircache_size);
1248 } 1256 }
1249#endif 1257#endif
1258
1250#ifdef HAVE_TAGCACHE 1259#ifdef HAVE_TAGCACHE
1251 tagcache_start_scan(); 1260 tagcache_start_scan();
1252#endif 1261#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 @@
22#include "../kernel-internal.h" 22#include "../kernel-internal.h"
23#include "storage.h" 23#include "storage.h"
24#include "ata-target.h" 24#include "ata-target.h"
25#include "file_internal.h"
25#include "disk.h" 26#include "disk.h"
26#include "font.h" 27#include "font.h"
27#include "backlight.h" 28#include "backlight.h"
@@ -73,6 +74,8 @@ void main(void)
73 ret = storage_init(); 74 ret = storage_init();
74 if(ret) 75 if(ret)
75 printf("ATA error: %d", ret); 76 printf("ATA error: %d", ret);
77
78 filesystem_init();
76 79
77 /* If no button is held, start the OF */ 80 /* If no button is held, start the OF */
78 if(button_read_device() == 0) 81 if(button_read_device() == 0)
@@ -93,8 +96,6 @@ void main(void)
93 } 96 }
94 else 97 else
95 { 98 {
96 disk_init();
97
98 ret = disk_mount_all(); 99 ret = disk_mount_all();
99 if (ret <= 0) 100 if (ret <= 0)
100 error(EDISK, ret, true); 101 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 @@
21#include "config.h" 21#include "config.h"
22#include "system.h" 22#include "system.h"
23#include <stdio.h> 23#include <stdio.h>
24#include <errno.h>
24#include "../kernel-internal.h" 25#include "../kernel-internal.h"
25#include "gcc_extensions.h" 26#include "gcc_extensions.h"
26#include "string.h" 27#include "string.h"
27#include "adc.h" 28#include "adc.h"
28#include "powermgmt.h" 29#include "powermgmt.h"
29#include "storage.h" 30#include "storage.h"
31#include "file_internal.h"
30#include "dir.h" 32#include "dir.h"
33#include "file.h"
31#include "disk.h" 34#include "disk.h"
32#include "common.h" 35#include "common.h"
33#include "rb-loader.h" 36#include "rb-loader.h"
@@ -219,7 +222,7 @@ static void untar(int tar_fd)
219 222
220 /* Create the dir */ 223 /* Create the dir */
221 ret = mkdir(path); 224 ret = mkdir(path);
222 if (ret < 0 && ret != -4) 225 if (ret < 0 && errno != EEXIST)
223 { 226 {
224 printf("failed to create dir (%d)", ret); 227 printf("failed to create dir (%d)", ret);
225 } 228 }
@@ -233,14 +236,14 @@ static void handle_untar(void)
233 char buf[MAX_PATH]; 236 char buf[MAX_PATH];
234 char tarstring[6]; 237 char tarstring[6];
235 char model[5]; 238 char model[5];
236 struct dirent_uncached* entry; 239 struct dirent* entry;
237 DIR_UNCACHED* dir; 240 DIR* dir;
238 int fd; 241 int fd;
239 int rc; 242 int rc;
240 243
241 dir = opendir_uncached(basedir); 244 dir = opendir(basedir);
242 245
243 while ((entry = readdir_uncached(dir))) 246 while ((entry = readdir(dir)))
244 { 247 {
245 if (*entry->d_name == '.') 248 if (*entry->d_name == '.')
246 continue; 249 continue;
@@ -262,7 +265,6 @@ static void handle_untar(void)
262 verbose = true; 265 verbose = true;
263 printf("Found rockbox binary. Moving..."); 266 printf("Found rockbox binary. Moving...");
264 close(fd); 267 close(fd);
265 remove( BOOTDIR "/" BOOTFILE);
266 int ret = rename(buf, BOOTDIR "/" BOOTFILE); 268 int ret = rename(buf, BOOTDIR "/" BOOTFILE);
267 printf("returned %d", ret); 269 printf("returned %d", ret);
268 sleep(HZ); 270 sleep(HZ);
@@ -365,7 +367,7 @@ void main(void)
365 if(rc) 367 if(rc)
366 error(EATA, rc, true); 368 error(EATA, rc, true);
367 369
368 disk_init(); 370 filesystem_init();
369 371
370 rc = disk_mount_all(); 372 rc = disk_mount_all();
371 if (rc <= 0) 373 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 @@
30#include "../kernel-internal.h" 30#include "../kernel-internal.h"
31#include "storage.h" 31#include "storage.h"
32#include "fat.h" 32#include "fat.h"
33#include "file_internal.h"
33#include "disk.h" 34#include "disk.h"
34#include "font.h" 35#include "font.h"
35#include "adc.h" 36#include "adc.h"
@@ -185,7 +186,7 @@ void main(void)
185 error(EATA, rc, true); 186 error(EATA, rc, true);
186 } 187 }
187 188
188 disk_init(); 189 filesystem_init();
189 190
190 rc = disk_mount_all(); 191 rc = disk_mount_all();
191 if (rc<=0) 192 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)
196 if(rc) 196 if(rc)
197 error(EATA, rc, true); 197 error(EATA, rc, true);
198 198
199 disk_init(); 199 filesystem_init();
200 200
201 rc = disk_mount_all(); 201 rc = disk_mount_all();
202 if (rc<=0) 202 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 @@
35#include "rb-loader.h" 35#include "rb-loader.h"
36#include "loader_strerror.h" 36#include "loader_strerror.h"
37#include "storage.h" 37#include "storage.h"
38#include "file_internal.h"
38#include "disk.h" 39#include "disk.h"
39#include "panic.h" 40#include "panic.h"
40#include "power.h" 41#include "power.h"
@@ -171,7 +172,7 @@ void main(uint32_t arg, uint32_t addr)
171 if(ret < 0) 172 if(ret < 0)
172 error(EATA, ret, true); 173 error(EATA, ret, true);
173 174
174 disk_init_subsystem(); 175 filesystem_init();
175 176
176 /* NOTE: disk_mount_all to fail since we can do USB after. 177 /* NOTE: disk_mount_all to fail since we can do USB after.
177 * We need this order to determine the correct logical sector size */ 178 * 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 @@
33#include "../kernel-internal.h" 33#include "../kernel-internal.h"
34#include "ata.h" 34#include "ata.h"
35#include "fat.h" 35#include "fat.h"
36#include "file_internal.h"
36#include "disk.h" 37#include "disk.h"
37#include "font.h" 38#include "font.h"
38#include "adc.h" 39#include "adc.h"
@@ -295,7 +296,7 @@ void* main(void)
295 int rc; 296 int rc;
296 bool haveramos; 297 bool haveramos;
297 bool button_was_held; 298 bool button_was_held;
298 struct partinfo* pinfo; 299 struct partinfo pinfo;
299 unsigned short* identify_info; 300 unsigned short* identify_info;
300 301
301 /* Check the button hold status as soon as possible - to 302 /* Check the button hold status as soon as possible - to
@@ -353,7 +354,8 @@ void* main(void)
353 printf("ATA: %d", i); 354 printf("ATA: %d", i);
354 } 355 }
355 356
356 disk_init(); 357 filesystem_init();
358
357 rc = disk_mount_all(); 359 rc = disk_mount_all();
358 if (rc<=0) 360 if (rc<=0)
359 { 361 {
@@ -361,9 +363,9 @@ void* main(void)
361 fatal_error(); 363 fatal_error();
362 } 364 }
363 365
364 pinfo = disk_partinfo(1); 366 disk_partinfo(1, &pinfo);
365 printf("Partition 1: 0x%02x %ld sectors", 367 printf("Partition 1: 0x%02x %ld sectors",
366 pinfo->type, pinfo->size); 368 pinfo.type, pinfo.size);
367 369
368 if (button_was_held || (btn==BUTTON_MENU)) { 370 if (button_was_held || (btn==BUTTON_MENU)) {
369 /* If either the hold switch was on, or the Menu button was held, then 371 /* 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 @@
32#include "lcd.h" 32#include "lcd.h"
33#include "i2c-s5l8700.h" 33#include "i2c-s5l8700.h"
34#include "../kernel-internal.h" 34#include "../kernel-internal.h"
35#include "file_internal.h"
35#include "storage.h" 36#include "storage.h"
36#include "fat.h" 37#include "fat.h"
37#include "disk.h" 38#include "disk.h"
@@ -213,7 +214,8 @@ void main(void)
213 fatal_error(); 214 fatal_error();
214 } 215 }
215 216
216 disk_init(); 217 filesystem_init();
218
217 rc = disk_mount_all(); 219 rc = disk_mount_all();
218 if (rc<=0) 220 if (rc<=0)
219 { 221 {
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 @@
31#include "scroll_engine.h" 31#include "scroll_engine.h"
32#include "../kernel-internal.h" 32#include "../kernel-internal.h"
33#include "storage.h" 33#include "storage.h"
34#include "file_internal.h"
34#include "usb.h" 35#include "usb.h"
35#include "disk.h" 36#include "disk.h"
36#include "font.h" 37#include "font.h"
@@ -596,7 +597,7 @@ void main(void)
596 } 597 }
597 598
598 599
599 disk_init(); 600 filesystem_init();
600 601
601 rc = disk_mount_all(); 602 rc = disk_mount_all();
602 if (rc<=0) 603 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 @@
31#include "scroll_engine.h" 31#include "scroll_engine.h"
32#include "../kernel-internal.h" 32#include "../kernel-internal.h"
33#include "storage.h" 33#include "storage.h"
34#include "file_internal.h"
34#include "usb.h" 35#include "usb.h"
35#include "disk.h" 36#include "disk.h"
36#include "font.h" 37#include "font.h"
@@ -349,8 +350,7 @@ void main(void)
349 while(!(button_get(true) & BUTTON_REL)); 350 while(!(button_get(true) & BUTTON_REL));
350 } 351 }
351 352
352 353 filesystem_init();
353 disk_init();
354 354
355 rc = disk_mount_all(); 355 rc = disk_mount_all();
356 if (rc<=0) 356 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 @@
26#include <stdlib.h> 26#include <stdlib.h>
27#include "common.h" 27#include "common.h"
28#include "cpu.h" 28#include "cpu.h"
29#include "file.h"
30#include "system.h" 29#include "system.h"
31#include "../kernel-internal.h" 30#include "../kernel-internal.h"
32#include "lcd.h" 31#include "lcd.h"
33#include "font.h" 32#include "font.h"
34#include "storage.h" 33#include "storage.h"
34#include "file_internal.h"
35#include "file.h"
35#include "button.h" 36#include "button.h"
36#include "disk.h" 37#include "disk.h"
37#include "crc32-mi4.h" 38#include "crc32-mi4.h"
@@ -92,7 +93,7 @@ void* main(void)
92 int num_partitions; 93 int num_partitions;
93 int crc32; 94 int crc32;
94 char sector[512]; 95 char sector[512];
95 struct partinfo* pinfo; 96 struct partinfo pinfo;
96 97
97 system_init(); 98 system_init();
98 kernel_init(); 99 kernel_init();
@@ -117,7 +118,7 @@ void* main(void)
117 printf(""); 118 printf("");
118 119
119 i=storage_init(); 120 i=storage_init();
120 disk_init(IF_MV(0)); 121 filesystem_init();
121 num_partitions = disk_mount_all(); 122 num_partitions = disk_mount_all();
122 123
123 if (num_partitions<=0) 124 if (num_partitions<=0)
@@ -125,17 +126,17 @@ void* main(void)
125 error(EDISK, num_partitions, true); 126 error(EDISK, num_partitions, true);
126 } 127 }
127 128
128 pinfo = disk_partinfo(1); 129 disk_partinfo(1, &pinfo);
129 130
130#if 0 /* not needed in release builds */ 131#if 0 /* not needed in release builds */
131 printf("--- Partition info ---"); 132 printf("--- Partition info ---");
132 printf("start: %x", pinfo->start); 133 printf("start: %x", pinfo.start);
133 printf("size: %x", pinfo->size); 134 printf("size: %x", pinfo.size);
134 printf("type: %x", pinfo->type); 135 printf("type: %x", pinfo.type);
135 printf("reading: %x", (START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK)*512); 136 printf("reading: %x", (START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK)*512);
136#endif 137#endif
137 138
138 storage_read_sectors(pinfo->start + START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK, 139 storage_read_sectors(pinfo.start + START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK,
139 1 , sector); 140 1 , sector);
140 crc32 = chksum_crc32 (sector, 512); 141 crc32 = chksum_crc32 (sector, 512);
141 142
@@ -157,7 +158,7 @@ void* main(void)
157 memcpy(&sector[HACK_OFFSET], changedBytes, 158 memcpy(&sector[HACK_OFFSET], changedBytes,
158 sizeof(changedBytes)/sizeof(*changedBytes)); 159 sizeof(changedBytes)/sizeof(*changedBytes));
159 storage_write_sectors( 160 storage_write_sectors(
160 pinfo->start + START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK, 161 pinfo.start + START_SECTOR_OF_ROM + ROMSECTOR_TO_HACK,
161 1 , sector); 162 1 , sector);
162 printf("Firmware unlocked"); 163 printf("Firmware unlocked");
163 printf("Proceed to Step 2"); 164 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 @@
33#include "lcd.h" 33#include "lcd.h"
34#include "font.h" 34#include "font.h"
35#include "storage.h" 35#include "storage.h"
36#include "file_internal.h"
36#include "adc.h" 37#include "adc.h"
37#include "button.h" 38#include "button.h"
38#include "disk.h" 39#include "disk.h"
@@ -291,7 +292,7 @@ void* main(void)
291 int btn; 292 int btn;
292 int rc; 293 int rc;
293 int num_partitions; 294 int num_partitions;
294 struct partinfo* pinfo; 295 struct partinfo pinfo;
295#if !(CONFIG_STORAGE & STORAGE_SD) 296#if !(CONFIG_STORAGE & STORAGE_SD)
296 char buf[256]; 297 char buf[256];
297 unsigned short* identify_info; 298 unsigned short* identify_info;
@@ -370,7 +371,7 @@ void* main(void)
370 } 371 }
371#endif 372#endif
372 373
373 disk_init(IF_MV(0)); 374 filesystem_init();
374 num_partitions = disk_mount_all(); 375 num_partitions = disk_mount_all();
375 if (num_partitions<=0) 376 if (num_partitions<=0)
376 { 377 {
@@ -381,9 +382,9 @@ void* main(void)
381 that have more than that */ 382 that have more than that */
382 for(i=0; i<NUM_PARTITIONS; i++) 383 for(i=0; i<NUM_PARTITIONS; i++)
383 { 384 {
384 pinfo = disk_partinfo(i); 385 disk_partinfo(i, &pinfo);
385 printf("Partition %d: 0x%02x %ld MB", 386 printf("Partition %d: 0x%02x %ld MB",
386 i, pinfo->type, pinfo->size / 2048); 387 i, pinfo.type, pinfo.size / 2048);
387 } 388 }
388 389
389 /* Now that storage is initialized, check for USB connection */ 390 /* Now that storage is initialized, check for USB connection */
@@ -430,10 +431,10 @@ void* main(void)
430#if (CONFIG_STORAGE & STORAGE_SD) 431#if (CONFIG_STORAGE & STORAGE_SD)
431 /* First try a (hidden) firmware partition */ 432 /* First try a (hidden) firmware partition */
432 printf("Trying firmware partition"); 433 printf("Trying firmware partition");
433 pinfo = disk_partinfo(1); 434 disk_partinfo(1, &pinfo);
434 if(pinfo->type == PARTITION_TYPE_OS2_HIDDEN_C_DRIVE) 435 if(pinfo.type == PARTITION_TYPE_OS2_HIDDEN_C_DRIVE)
435 { 436 {
436 rc = load_mi4_part(loadbuffer, pinfo, MAX_LOADSIZE, 437 rc = load_mi4_part(loadbuffer, &pinfo, MAX_LOADSIZE,
437 usb == USB_INSERTED); 438 usb == USB_INSERTED);
438 if (rc <= EFILE_EMPTY) { 439 if (rc <= EFILE_EMPTY) {
439 printf("Can't load from partition"); 440 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 @@
29#include "lcd.h" 29#include "lcd.h"
30#include "../kernel-internal.h" 30#include "../kernel-internal.h"
31#include "storage.h" 31#include "storage.h"
32#include "file_internal.h"
32#include "fat.h" 33#include "fat.h"
33#include "disk.h" 34#include "disk.h"
34#include "font.h" 35#include "font.h"
@@ -88,7 +89,7 @@ int main(void)
88 error(EATA, rc, true); 89 error(EATA, rc, true);
89 } 90 }
90 91
91 disk_init(IF_MD(0)); 92 filesystem_init();
92 rc = disk_mount_all(); 93 rc = disk_mount_all();
93 if (rc<=0) 94 if (rc<=0)
94 { 95 {
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 @@
29#include "lcd.h" 29#include "lcd.h"
30#include "../kernel-internal.h" 30#include "../kernel-internal.h"
31#include "storage.h" 31#include "storage.h"
32#include "file_internal.h"
32#include "usb.h" 33#include "usb.h"
33#include "disk.h" 34#include "disk.h"
34#include "font.h" 35#include "font.h"
@@ -202,7 +203,7 @@ static void rb_boot(void)
202 if(rc) 203 if(rc)
203 error(EATA, rc, true); 204 error(EATA, rc, true);
204 205
205 disk_init(); 206 filesystem_init();
206 207
207 rc = disk_mount_all(); 208 rc = disk_mount_all();
208 if (rc <= 0) 209 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 @@
26#include "lcd.h" 26#include "lcd.h"
27#include "../kernel-internal.h" 27#include "../kernel-internal.h"
28#include "storage.h" 28#include "storage.h"
29#include "file_internal.h"
29#include "fat.h" 30#include "fat.h"
30#include "disk.h" 31#include "disk.h"
31#include "font.h" 32#include "font.h"
@@ -123,8 +124,8 @@ void main(void)
123 error(EATA, rc, true); 124 error(EATA, rc, true);
124 } 125 }
125 126
126 printf("disk"); 127 printf("filesystem");
127 disk_init(); 128 filesystem_init();
128 129
129 printf("mount"); 130 printf("mount");
130 rc = disk_mount_all(); 131 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 @@
33#include "rb-loader.h" 33#include "rb-loader.h"
34#include "loader_strerror.h" 34#include "loader_strerror.h"
35#include "storage.h" 35#include "storage.h"
36#include "file_internal.h"
36#include "disk.h" 37#include "disk.h"
37#include "string.h" 38#include "string.h"
38#include "adc.h" 39#include "adc.h"
@@ -269,6 +270,8 @@ int main(void)
269 270
270 show_logo(); 271 show_logo();
271 272
273 filesystem_init();
274
272 rc = storage_init(); 275 rc = storage_init();
273 if(rc) 276 if(rc)
274 error(EATA, rc, true); 277 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 @@
12#include "button.h" 12#include "button.h"
13#include "common.h" 13#include "common.h"
14#include "storage.h" 14#include "storage.h"
15#include "file_internal.h"
15#include "disk.h" 16#include "disk.h"
16#include "panic.h" 17#include "panic.h"
17#include "power.h" 18#include "power.h"
@@ -146,8 +147,7 @@ void main(void)
146 if(ret < 0) 147 if(ret < 0)
147 error(EATA, ret, true); 148 error(EATA, ret, true);
148 149
149 while(!disk_init(IF_MV(0))) 150 filesystem_init();
150 panicf("disk_init failed!");
151 151
152 while((ret = disk_mount_all()) <= 0) 152 while((ret = disk_mount_all()) <= 0)
153 error(EDISK, ret, true); 153 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 @@
39#include "rb-loader.h" 39#include "rb-loader.h"
40#include "loader_strerror.h" 40#include "loader_strerror.h"
41#include "storage.h" 41#include "storage.h"
42#include "file_internal.h"
42#include "disk.h" 43#include "disk.h"
43#include "panic.h" 44#include "panic.h"
44#include "power.h" 45#include "power.h"
@@ -119,6 +120,8 @@ void main(void)
119 if(ret < 0) 120 if(ret < 0)
120 error(EATA, ret, true); 121 error(EATA, ret, true);
121 122
123 filesystem_init();
124
122#ifdef USE_ROCKBOX_USB 125#ifdef USE_ROCKBOX_USB
123 usb_init(); 126 usb_init();
124 usb_start_monitoring(); 127 usb_start_monitoring();
@@ -128,13 +131,6 @@ void main(void)
128 usb_mode(); 131 usb_mode();
129#endif /* USE_ROCKBOX_USB */ 132#endif /* USE_ROCKBOX_USB */
130 133
131 while(!disk_init(IF_MV(0)))
132#ifdef USE_ROCKBOX_USB
133 usb_mode();
134#else
135 panicf("disk_init failed!");
136#endif
137
138 while((ret = disk_mount_all()) <= 0) 134 while((ret = disk_mount_all()) <= 0)
139 { 135 {
140#ifdef USE_ROCKBOX_USB 136#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 @@
21#include "lcd.h" 21#include "lcd.h"
22#include "../kernel-internal.h" 22#include "../kernel-internal.h"
23#include "storage.h" 23#include "storage.h"
24#include "file_internal.h"
24#include "disk.h" 25#include "disk.h"
25#include "font.h" 26#include "font.h"
26#include "backlight.h" 27#include "backlight.h"
@@ -72,7 +73,7 @@ void main(void)
72 if(ret) 73 if(ret)
73 printf("SD error: %d", ret); 74 printf("SD error: %d", ret);
74 75
75 disk_init(IF_MD(0)); 76 filesystem_init();
76 77
77 ret = disk_mount_all(); 78 ret = disk_mount_all();
78 if (ret <= 0) 79 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 @@
30#include "lcd.h" 30#include "lcd.h"
31#include "../kernel-internal.h" 31#include "../kernel-internal.h"
32#include "storage.h" 32#include "storage.h"
33#include "file_internal.h"
33#include "fat.h" 34#include "fat.h"
34#include "disk.h" 35#include "disk.h"
35#include "font.h" 36#include "font.h"
@@ -162,6 +163,8 @@ void* main(void)
162 error(EATA, rc, true); 163 error(EATA, rc, true);
163 } 164 }
164 165
166 filesystem_init();
167
165 printf("mount"); 168 printf("mount");
166 rc = disk_mount_all(); 169 rc = disk_mount_all();
167 if (rc<=0) 170 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 @@
31#include "kernel.h" 31#include "kernel.h"
32#include "thread.h" 32#include "thread.h"
33#include "storage.h" 33#include "storage.h"
34#include "file_internal.h"
34#include "fat.h" 35#include "fat.h"
35#include "disk.h" 36#include "disk.h"
36#include "font.h" 37#include "font.h"
@@ -56,7 +57,7 @@ void* main(void)
56 57
57 i=storage_init(); 58 i=storage_init();
58 59
59 disk_init(); 60 filesystem_init();
60 rc = disk_mount_all(); 61 rc = disk_mount_all();
61 62
62#if 0 63#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
59target/hosted/sdl/lcd-sdl.c 59target/hosted/sdl/lcd-sdl.c
60target/hosted/sdl/system-sdl.c 60target/hosted/sdl/system-sdl.c
61#ifdef HAVE_SDL_THREADS 61#ifdef HAVE_SDL_THREADS
62target/hosted/sdl/thread-sdl.c 62target/hosted/sdl/filesystem-sdl.c
63#else
64#endif 63#endif
64target/hosted/sdl/load_code-sdl.c
65target/hosted/sdl/timer-sdl.c 65target/hosted/sdl/timer-sdl.c
66#ifdef HAVE_TOUCHSCREEN 66#ifdef HAVE_TOUCHSCREEN
67target/hosted/sdl/key_to_touch-sdl.c 67target/hosted/sdl/key_to_touch-sdl.c
68#endif 68#endif
69#ifdef APPLICATION 69#ifdef APPLICATION
70target/hosted/sdl/app/load_code-sdl-app.c
70target/hosted/sdl/app/button-application.c 71target/hosted/sdl/app/button-application.c
71#endif 72#ifdef WIN32
72#endif 73target/hosted/filesystem-win32.c
74#else /* !WIN32 */
75target/hosted/filesystem-unix.c
76#endif /* WIN32 */
77#endif /* APPLICATION */
78#endif /* HAVE_SDL */
79
80#ifdef APPLICATION
81target/hosted/filesystem-app.c
82#endif /* APPLICATION */
73 83
74#if defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1) 84#if defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1)
75target/hosted/kernel-unix.c 85target/hosted/kernel-unix.c
@@ -163,19 +173,19 @@ common/crc32-mi4.c
163common/crc32-rkw.c 173common/crc32-rkw.c
164#endif 174#endif
165#if (CONFIG_PLATFORM & PLATFORM_NATIVE) 175#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
166common/dir_uncached.c 176common/dir.c
177common/disk_cache.c
167common/file.c 178common/file.c
179common/file_internal.c
168common/disk.c 180common/disk.c
181common/fileobj_mgr.c
169#endif /* PLATFORM_NATIVE */ 182#endif /* PLATFORM_NATIVE */
170#ifdef HAVE_DIRCACHE 183#ifdef HAVE_DIRCACHE
171common/dircache.c 184common/dircache.c
172#endif /* HAVE_DIRCACHE */ 185#endif /* HAVE_DIRCACHE */
173common/filefuncs.c 186common/pathfuncs.c
174common/format.c 187common/format.c
175common/linked_list.c 188common/linked_list.c
176#ifdef APPLICATION
177common/rbpaths.c
178#endif
179common/strcasecmp.c 189common/strcasecmp.c
180common/strcasestr.c 190common/strcasestr.c
181common/strnatcmp.c 191common/strnatcmp.c
@@ -1816,7 +1826,20 @@ target/hosted/android/app/button-application.c
1816drivers/audio/android.c 1826drivers/audio/android.c
1817#endif 1827#endif
1818 1828
1819#endif /* defined(SIMULATOR) */ 1829#else /* defined(SIMULATOR) */
1830
1831#ifdef WIN32
1832asm/mempcpy.c
1833target/hosted/filesystem-win32.c
1834#else /* !WIN32 */
1835target/hosted/filesystem-unix.c
1836#endif /* WIN32 */
1837target/hosted/sdl/load_code-sdl.c
1838#ifdef HAVE_SDL_THREADS
1839target/hosted/sdl/filesystem-sdl.c
1840#endif
1841
1842#endif /* !defined(SIMULATOR) */
1820 1843
1821#if defined(HAVE_TOUCHPAD) && !defined(HAS_BUTTON_HOLD) 1844#if defined(HAVE_TOUCHPAD) && !defined(HAS_BUTTON_HOLD)
1822drivers/touchpad.c 1845drivers/touchpad.c
@@ -1826,9 +1849,7 @@ drivers/touchpad.c
1826#ifdef HAVE_CORELOCK_OBJECT 1849#ifdef HAVE_CORELOCK_OBJECT
1827kernel/corelock.c 1850kernel/corelock.c
1828#endif 1851#endif
1829#if 0 /* pending dependent code */
1830kernel/mrsw_lock.c 1852kernel/mrsw_lock.c
1831#endif
1832kernel/mutex.c 1853kernel/mutex.c
1833kernel/queue.c 1854kernel/queue.c
1834#ifdef HAVE_SEMAPHORE_OBJECTS 1855#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:
63 SWHI t0, 0(a0) 63 SWHI t0, 0(a0)
64 addu a0, t1 64 addu a0, t1
65 65
66chk8w: 66chk8w:
67 andi t0, a2, 0x1f # 32 or more bytes left? 67 andi t0, a2, 0x1f # 32 or more bytes left?
68 beq t0, a2, chk1w 68 beq t0, a2, chk1w
69 subu a3, a2, t0 # Yes 69 subu a3, a2, t0 # Yes
70 addu a3, a1 # a3 = end address of loop 70 addu a3, a1 # a3 = end address of loop
71 move a2, t0 # a2 = what will be left after loop 71 move a2, t0 # a2 = what will be left after loop
72lop8w: 72lop8w:
73 lw t0, 0(a1) # Loop taking 8 words at a time 73 lw t0, 0(a1) # Loop taking 8 words at a time
74 lw t1, 4(a1) 74 lw t1, 4(a1)
75 lw t2, 8(a1) 75 lw t2, 8(a1)
@@ -90,34 +90,34 @@ lop8w:
90 bne a1, a3, lop8w 90 bne a1, a3, lop8w
91 sw t7, -4(a0) 91 sw t7, -4(a0)
92 92
93chk1w: 93chk1w:
94 andi t0, a2, 0x3 # 4 or more bytes left? 94 andi t0, a2, 0x3 # 4 or more bytes left?
95 beq t0, a2, last8 95 beq t0, a2, last8
96 subu a3, a2, t0 # Yes, handle them one word at a time 96 subu a3, a2, t0 # Yes, handle them one word at a time
97 addu a3, a1 # a3 again end address 97 addu a3, a1 # a3 again end address
98 move a2, t0 98 move a2, t0
99lop1w: 99lop1w:
100 lw t0, 0(a1) 100 lw t0, 0(a1)
101 addiu a0, 4 101 addiu a0, 4
102 addiu a1, 4 102 addiu a1, 4
103 bne a1, a3, lop1w 103 bne a1, a3, lop1w
104 sw t0, -4(a0) 104 sw t0, -4(a0)
105 105
106last8: 106last8:
107 blez a2, lst8e # Handle last 8 bytes, one at a time 107 blez a2, lst8e # Handle last 8 bytes, one at a time
108 addu a3, a2, a1 108 addu a3, a2, a1
109lst8l: 109lst8l:
110 lb t0, 0(a1) 110 lb t0, 0(a1)
111 addiu a0, 1 111 addiu a0, 1
112 addiu a1, 1 112 addiu a1, 1
113 bne a1, a3, lst8l 113 bne a1, a3, lst8l
114 sb t0, -1(a0) 114 sb t0, -1(a0)
115lst8e: 115lst8e:
116 jr ra # Bye, bye 116 jr ra # Bye, bye
117 nop 117 nop
118 118
119shift: 119shift:
120 subu a3, zero, a0 # Src and Dest unaligned 120 subu a3, zero, a0 # Src and Dest unaligned
121 andi a3, 0x3 # (unoptimized case...) 121 andi a3, 0x3 # (unoptimized case...)
122 beq a3, zero, shft1 122 beq a3, zero, shft1
123 subu a2, a3 # a2 = bytes left 123 subu a2, a3 # a2 = bytes left
@@ -126,11 +126,11 @@ shift:
126 addu a1, a3 126 addu a1, a3
127 SWHI t0, 0(a0) 127 SWHI t0, 0(a0)
128 addu a0, a3 128 addu a0, a3
129shft1: 129shft1:
130 andi t0, a2, 0x3 130 andi t0, a2, 0x3
131 subu a3, a2, t0 131 subu a3, a2, t0
132 addu a3, a1 132 addu a3, a1
133shfth: 133shfth:
134 LWHI t1, 0(a1) # Limp through, word by word 134 LWHI t1, 0(a1) # Limp through, word by word
135 LWLO t1, 3(a1) 135 LWLO t1, 3(a1)
136 addiu a0, 4 136 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:
60 cmp/hs r0,r6 /* at least 11 bytes to copy? (ensures 2 aligned longs) */ 60 cmp/hs r0,r6 /* at least 11 bytes to copy? (ensures 2 aligned longs) */
61 add r5,r6 /* r6 = source_end */ 61 add r5,r6 /* r6 = source_end */
62 bf .start_b2 /* no: jump directly to byte loop */ 62 bf .start_b2 /* no: jump directly to byte loop */
63 63
64 mov #3,r0 64 mov #3,r0
65 neg r5,r3 65 neg r5,r3
66 and r0,r3 /* r3 = (4 - align_offset) % 4 */ 66 and r0,r3 /* r3 = (4 - align_offset) % 4 */
67 tst r3,r3 /* already aligned? */ 67 tst r3,r3 /* already aligned? */
68 bt .end_b1 /* yes: skip leading byte loop */ 68 bt .end_b1 /* yes: skip leading byte loop */
69 69
70 add r5,r3 /* r3 = first source long bound */ 70 add r5,r3 /* r3 = first source long bound */
71 71
72 /* leading byte loop: copies 0..3 bytes */ 72 /* leading byte loop: copies 0..3 bytes */
@@ -89,7 +89,7 @@ ___memcpy_fwd_entry:
89 mov r6,r3 /* move end address to r3 */ 89 mov r6,r3 /* move end address to r3 */
90 jmp @r1 /* and jump to it */ 90 jmp @r1 /* and jump to it */
91 add #-7,r3 /* adjust end addr for main loops doing 2 longs/pass */ 91 add #-7,r3 /* adjust end addr for main loops doing 2 longs/pass */
92 92
93 /** main loops, copying 2 longs per pass to profit from fast page mode **/ 93 /** main loops, copying 2 longs per pass to profit from fast page mode **/
94 94
95 /* long aligned destination (fastest) */ 95 /* long aligned destination (fastest) */
@@ -102,11 +102,11 @@ ___memcpy_fwd_entry:
102 mov.l r0,@-r4 /* store second long */ 102 mov.l r0,@-r4 /* store second long */
103 mov.l r1,@-r4 /* store first long; NOT ALIGNED - no speed loss here! */ 103 mov.l r1,@-r4 /* store first long; NOT ALIGNED - no speed loss here! */
104 bt .loop_do0 104 bt .loop_do0
105 105
106 add #4,r3 /* readjust end address */ 106 add #4,r3 /* readjust end address */
107 cmp/hi r5,r3 /* one long left? */ 107 cmp/hi r5,r3 /* one long left? */
108 bf .start_b2 /* no, jump to trailing byte loop */ 108 bf .start_b2 /* no, jump to trailing byte loop */
109 109
110 mov.l @r5+,r0 /* load last long & increment source addr */ 110 mov.l @r5+,r0 /* load last long & increment source addr */
111 add #4,r4 /* increment dest addr */ 111 add #4,r4 /* increment dest addr */
112 bra .start_b2 /* jump to trailing byte loop */ 112 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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 by Björn Stenberg
11 * Copyright (C) 2014 by Michael Sevakis
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22#define DIRFUNCTIONS_DEFINED
23#include "config.h"
24#include <errno.h>
25#include <string.h>
26#include "debug.h"
27#include "dir.h"
28#include "pathfuncs.h"
29#include "fileobj_mgr.h"
30#include "dircache_redirect.h"
31
32/* structure used for open directory streams */
33static struct dirstr_desc
34{
35 struct filestr_base stream; /* basic stream info (first!) */
36 struct dirscan_info scan; /* directory scan cursor */
37 struct dirent entry; /* current parsed entry information */
38#ifdef HAVE_MULTIVOLUME
39 int volumecounter; /* counter for root volume entries */
40#endif
41} open_streams[MAX_OPEN_DIRS];
42
43/* check and return a struct dirstr_desc* from a DIR* */
44static struct dirstr_desc * get_dirstr(DIR *dirp)
45{
46 struct dirstr_desc *dir = (struct dirstr_desc *)dirp;
47
48 if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
49 dir = NULL;
50 else if (dir->stream.flags & FDO_BUSY)
51 return dir;
52
53 int errnum;
54
55 if (!dir)
56 {
57 errnum = EFAULT;
58 }
59 else if (dir->stream.flags == FV_NONEXIST)
60 {
61 DEBUGF("dir #%d: nonexistant device\n", (int)(dir - open_streams));
62 errnum = ENXIO;
63 }
64 else
65 {
66 DEBUGF("dir #%d: dir not open\n", (int)(dir - open_streams));
67 errnum = EBADF;
68 }
69
70 errno = errnum;
71 return NULL;
72}
73
74#define GET_DIRSTR(type, dirp) \
75 ({ \
76 file_internal_lock_##type(); \
77 struct dirstr_desc *_dir = get_dirstr(dirp); \
78 if (_dir) \
79 FILESTR_LOCK(type, &_dir->stream); \
80 else \
81 file_internal_unlock_##type(); \
82 _dir; \
83 })
84
85/* release the lock on the dirstr_desc* */
86#define RELEASE_DIRSTR(type, dir) \
87 ({ \
88 FILESTR_UNLOCK(type, &(dir)->stream); \
89 file_internal_unlock_##type(); \
90 })
91
92
93/* find a free dir stream descriptor */
94static struct dirstr_desc * alloc_dirstr(void)
95{
96 for (unsigned int dd = 0; dd < MAX_OPEN_DIRS; dd++)
97 {
98 struct dirstr_desc *dir = &open_streams[dd];
99 if (!dir->stream.flags)
100 return dir;
101 }
102
103 DEBUGF("Too many dirs open\n");
104 return NULL;
105}
106
107#ifdef HAVE_MULTIVOLUME
108static int readdir_volume_inner(struct dirstr_desc *dir, struct dirent *entry)
109{
110 /* Volumes (secondary file systems) get inserted into the system root
111 * directory. If the path specified volume 0, enumeration will not
112 * include other volumes, but just its own files and directories.
113 *
114 * Fake special directories, which don't really exist, that will get
115 * redirected upon opendir()
116 */
117 while (++dir->volumecounter < NUM_VOLUMES)
118 {
119 /* on the system root */
120 if (!fat_ismounted(dir->volumecounter))
121 continue;
122
123 get_volume_name(dir->volumecounter, entry->d_name);
124 dir->entry.info.attr = ATTR_MOUNT_POINT;
125 dir->entry.info.size = 0;
126 dir->entry.info.wrtdate = 0;
127 dir->entry.info.wrttime = 0;
128 return 1;
129 }
130
131 /* do normal directory entry fetching */
132 return 0;
133}
134#endif /* HAVE_MULTIVOLUME */
135
136static inline int readdir_volume(struct dirstr_desc *dir,
137 struct dirent *entry)
138{
139#ifdef HAVE_MULTIVOLUME
140 /* fetch virtual volume entries? */
141 if (dir->volumecounter < NUM_VOLUMES)
142 return readdir_volume_inner(dir, entry);
143#endif /* HAVE_MULTIVOLUME */
144
145 /* do normal directory entry fetching */
146 return 0;
147 (void)dir; (void)entry;
148}
149
150
151/** POSIX interface **/
152
153/* open a directory */
154DIR * opendir(const char *dirname)
155{
156 DEBUGF("opendir(dirname=\"%s\"\n", dirname);
157
158 DIR *dirp = NULL;
159
160 file_internal_lock_WRITER();
161
162 int rc;
163
164 struct dirstr_desc * const dir = alloc_dirstr();
165 if (!dir)
166 FILE_ERROR(EMFILE, RC);
167
168 rc = open_stream_internal(dirname, FF_DIR, &dir->stream, NULL);
169 if (rc < 0)
170 {
171 DEBUGF("Open failed: %d\n", rc);
172 FILE_ERROR(ERRNO, RC);
173 }
174
175#ifdef HAVE_MULTIVOLUME
176 /* volume counter is relevant only to the system root */
177 dir->volumecounter = rc > 1 ? 0 : INT_MAX;
178#endif /* HAVE_MULTIVOLUME */
179
180 fat_rewind(&dir->stream.fatstr);
181 rewinddir_dirent(&dir->scan);
182
183 dirp = (DIR *)dir;
184file_error:
185 file_internal_unlock_WRITER();
186 return dirp;
187}
188
189/* close a directory stream */
190int closedir(DIR *dirp)
191{
192 int rc;
193
194 file_internal_lock_WRITER();
195
196 /* needs to work even if marked "nonexistant" */
197 struct dirstr_desc * const dir = (struct dirstr_desc *)dirp;
198 if (!PTR_IN_ARRAY(open_streams, dir, MAX_OPEN_DIRS))
199 FILE_ERROR(EFAULT, -1);
200
201 if (!dir->stream.flags)
202 {
203 DEBUGF("dir #%d: dir not open\n", (int)(dir - open_streams));
204 FILE_ERROR(EBADF, -2);
205 }
206
207 rc = close_stream_internal(&dir->stream);
208 if (rc < 0)
209 FILE_ERROR(ERRNO, rc * 10 - 3);
210
211file_error:
212 file_internal_unlock_WRITER();
213 return rc;
214}
215
216/* read a directory */
217struct dirent * readdir(DIR *dirp)
218{
219 struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
220 if (!dir)
221 FILE_ERROR_RETURN(ERRNO, NULL);
222
223 struct dirent *res = NULL;
224
225 int rc = readdir_volume(dir, &dir->entry);
226 if (rc == 0)
227 {
228 rc = readdir_dirent(&dir->stream, &dir->scan, &dir->entry);
229 if (rc < 0)
230 FILE_ERROR(EIO, RC);
231 }
232
233 if (rc > 0)
234 res = &dir->entry;
235
236file_error:
237 RELEASE_DIRSTR(READER, dir);
238
239 if (rc > 1)
240 iso_decode_d_name(res->d_name);
241
242 return res;
243}
244
245#if 0 /* not included now but probably should be */
246/* read a directory (reentrant) */
247int readdir_r(DIR *dirp, struct dirent *entry, struct dirent **result)
248{
249 if (!result)
250 FILE_ERROR_RETURN(EFAULT, -2);
251
252 *result = NULL;
253
254 if (!entry)
255 FILE_ERROR_RETURN(EFAULT, -3);
256
257 struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
258 if (!dir)
259 FILE_ERROR_RETURN(ERRNO, -1);
260
261 int rc = readdir_volume(dir, entry);
262 if (rc == 0)
263 {
264 rc = readdir_dirent(&dir->stream, &dir->scan, entry);
265 if (rc < 0)
266 FILE_ERROR(EIO, rc * 10 - 4);
267 }
268
269file_error:
270 RELEASE_DIRSTR(READER, dir);
271
272 if (rc > 0)
273 {
274 if (rc > 1)
275 iso_decode_d_name(entry->d_name);
276
277 *result = entry;
278 rc = 0;
279 }
280
281 return rc;
282}
283
284/* reset the position of a directory stream to the beginning of a directory */
285void rewinddir(DIR *dirp)
286{
287 struct dirstr_desc * const dir = GET_DIRSTR(READER, dirp);
288 if (!dir)
289 FILE_ERROR_RETURN(ERRNO);
290
291 rewinddir_dirent(&dir->scan);
292
293#ifdef HAVE_MULTIVOLUME
294 if (dir->volumecounter != INT_MAX)
295 dir->volumecounter = 0;
296#endif /* HAVE_MULTIVOLUME */
297
298 RELEASE_DIRSTR(READER, dir);
299}
300
301#endif /* 0 */
302
303/* make a directory */
304int mkdir(const char *path)
305{
306 DEBUGF("mkdir(path=\"%s\")\n", path);
307
308 int rc;
309
310 file_internal_lock_WRITER();
311
312 struct filestr_base stream;
313 struct path_component_info compinfo;
314 rc = open_stream_internal(path, FF_DIR, &stream, &compinfo);
315 if (rc < 0)
316 {
317 DEBUGF("Can't open parent dir or path is not a directory\n");
318 FILE_ERROR(ERRNO, rc * 10 - 1);
319 }
320 else if (rc > 0)
321 {
322 DEBUGF("File exists\n");
323 FILE_ERROR(EEXIST, -2);
324 }
325
326 rc = create_stream_internal(&compinfo.parentinfo, compinfo.name,
327 compinfo.length, ATTR_NEW_DIRECTORY,
328 FO_DIRECTORY, &stream);
329 if (rc < 0)
330 FILE_ERROR(ERRNO, rc * 10 - 3);
331
332 rc = 0;
333file_error:
334 close_stream_internal(&stream);
335 file_internal_unlock_WRITER();
336 return rc;
337}
338
339/* remove a directory */
340int rmdir(const char *name)
341{
342 DEBUGF("rmdir(name=\"%s\")\n", name);
343
344 if (name)
345 {
346 /* path may not end with "." */
347 const char *basename;
348 size_t len = path_basename(name, &basename);
349 if (basename[0] == '.' && len == 1)
350 {
351 DEBUGF("Invalid path; last component is \".\"\n");
352 FILE_ERROR_RETURN(EINVAL, -9);
353 }
354 }
355
356 file_internal_lock_WRITER();
357 int rc = remove_stream_internal(name, NULL, FF_DIR);
358 file_internal_unlock_WRITER();
359 return rc;
360}
361
362
363/** Extended interface **/
364
365/* return if two directory streams refer to the same directory */
366int samedir(DIR *dirp1, DIR *dirp2)
367{
368 struct dirstr_desc * const dir1 = GET_DIRSTR(WRITER, dirp1);
369 if (!dir1)
370 FILE_ERROR_RETURN(ERRNO, -1);
371
372 int rc = -2;
373
374 struct dirstr_desc * const dir2 = get_dirstr(dirp2);
375 if (dir2)
376 rc = dir1->stream.bindp == dir2->stream.bindp ? 1 : 0;
377
378 RELEASE_DIRSTR(WRITER, dir1);
379 return rc;
380}
381
382/* test directory existence (returns 'false' if a file) */
383bool dir_exists(const char *dirname)
384{
385 file_internal_lock_WRITER();
386 bool rc = test_stream_exists_internal(dirname, FF_DIR) > 0;
387 file_internal_unlock_WRITER();
388 return rc;
389}
390
391/* get the portable info from the native entry */
392struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry)
393{
394 int rc;
395 if (!dirp || !entry)
396 FILE_ERROR(EFAULT, RC);
397
398 if (entry->d_name[0] == '\0')
399 FILE_ERROR(ENOENT, RC);
400
401 if ((file_size_t)entry->info.size > FILE_SIZE_MAX)
402 FILE_ERROR(EOVERFLOW, RC);
403
404 return (struct dirinfo)
405 {
406 .attribute = entry->info.attr,
407 .size = entry->info.size,
408 .mtime = fattime_mktime(entry->info.wrtdate, entry->info.wrttime),
409 };
410
411file_error:
412 return (struct dirinfo){ .attribute = 0 };
413}
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id: dir.c 13741 2007-06-30 02:08:27Z jethead71 $
9 *
10 * Copyright (C) 2002 by Björn Stenberg
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include <stdio.h>
22#include <errno.h>
23#include <string.h>
24#include <stdbool.h>
25#include <stdlib.h>
26#include "fat.h"
27#include "dir.h"
28#include "debug.h"
29#include "filefuncs.h"
30
31#if (MEMORYSIZE > 8)
32#define MAX_OPEN_DIRS 12
33#else
34#define MAX_OPEN_DIRS 8
35#endif
36
37static DIR_UNCACHED opendirs[MAX_OPEN_DIRS];
38
39// release all dir handles on a given volume "by force", to avoid leaks
40int release_dirs(int volume)
41{
42 DIR_UNCACHED* pdir = opendirs;
43 int dd;
44 int closed = 0;
45 for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++)
46 {
47#ifdef HAVE_MULTIVOLUME
48 if (pdir->fatdir.file.volume == volume)
49#else
50 (void)volume;
51#endif
52 {
53 pdir->busy = false; /* mark as available, no further action */
54 closed++;
55 }
56 }
57 return closed; /* return how many we did */
58}
59
60DIR_UNCACHED* opendir_uncached(const char* name)
61{
62 char namecopy[MAX_PATH];
63 char* part;
64 char* end;
65 struct fat_direntry entry;
66 int dd;
67 DIR_UNCACHED* pdir = opendirs;
68#ifdef HAVE_MULTIVOLUME
69 int volume;
70#endif
71
72 if ( name[0] != '/' ) {
73 DEBUGF("Only absolute paths supported right now\n");
74 return NULL;
75 }
76
77 /* find a free dir descriptor */
78 for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++)
79 if ( !pdir->busy )
80 break;
81
82 if ( dd == MAX_OPEN_DIRS ) {
83 DEBUGF("Too many dirs open\n");
84 errno = EMFILE;
85 return NULL;
86 }
87
88 pdir->busy = true;
89
90#ifdef HAVE_MULTIVOLUME
91 /* try to extract a heading volume name, if present */
92 volume = strip_volume(name, namecopy);
93 pdir->volumecounter = 0;
94#else
95 strlcpy(namecopy, name, sizeof(namecopy)); /* just copy */
96#endif
97
98 if ( fat_opendir(IF_MV(volume,) &pdir->fatdir, 0, NULL) < 0 ) {
99 DEBUGF("Failed opening root dir\n");
100 pdir->busy = false;
101 return NULL;
102 }
103
104 for ( part = strtok_r(namecopy, "/", &end); part;
105 part = strtok_r(NULL, "/", &end)) {
106 /* scan dir for name */
107 while (1) {
108 if ((fat_getnext(&pdir->fatdir,&entry) < 0) ||
109 (!entry.name[0])) {
110 pdir->busy = false;
111 return NULL;
112 }
113 if ( (entry.attr & FAT_ATTR_DIRECTORY) &&
114 (!strcasecmp(part, entry.name)) ) {
115 /* In reality, the parent_dir parameter of fat_opendir seems
116 * useless because it's sole purpose it to have a way to
117 * update the file metadata, but here we are only reading
118 * a directory so there's no need for that kind of stuff.
119 * However, the rmdir_uncached function uses a ugly hack to
120 * avoid opening a directory twice when deleting it and thus
121 * needs those information. That's why we pass pdir->fatdir both
122 * as the parent directory and the resulting one (this is safe,
123 * in doubt, check fat_open(dir) code) which will allow this kind of
124 * (ugly) things */
125 if ( fat_opendir(IF_MV(volume,)
126 &pdir->fatdir,
127 entry.firstcluster,
128 &pdir->fatdir) < 0 ) {
129 DEBUGF("Failed opening dir '%s' (%ld)\n",
130 part, entry.firstcluster);
131 pdir->busy = false;
132 return NULL;
133 }
134#ifdef HAVE_MULTIVOLUME
135 pdir->volumecounter = -1; /* n.a. to subdirs */
136#endif
137 break;
138 }
139 }
140 }
141
142 return pdir;
143}
144
145int closedir_uncached(DIR_UNCACHED* dir)
146{
147 dir->busy=false;
148 return 0;
149}
150
151struct dirent_uncached* readdir_uncached(DIR_UNCACHED* dir)
152{
153 struct fat_direntry entry;
154 struct dirent_uncached* theent = &(dir->theent);
155
156 if (!dir->busy)
157 return NULL;
158
159#ifdef HAVE_MULTIVOLUME
160 /* Volumes (secondary file systems) get inserted into the root directory
161 of the first volume, since we have no separate top level. */
162 if (dir->volumecounter >= 0 /* on a root dir */
163 && dir->volumecounter < NUM_VOLUMES /* in range */
164 && dir->fatdir.file.volume == 0) /* at volume 0 */
165 { /* fake special directories, which don't really exist, but
166 will get redirected upon opendir_uncached() */
167 while (++dir->volumecounter < NUM_VOLUMES)
168 {
169 if (fat_ismounted(dir->volumecounter))
170 {
171 memset(theent, 0, sizeof(*theent));
172 theent->info.attribute = FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME;
173 snprintf(theent->d_name, sizeof(theent->d_name),
174 VOL_NAMES, dir->volumecounter);
175 return theent;
176 }
177 }
178 }
179#endif
180 /* normal directory entry fetching follows here */
181 if (fat_getnext(&(dir->fatdir),&entry) < 0)
182 return NULL;
183
184 if ( !entry.name[0] )
185 return NULL;
186
187 strlcpy(theent->d_name, entry.name, sizeof(theent->d_name));
188 theent->info.attribute = entry.attr;
189 theent->info.wrtdate = entry.wrtdate;
190 theent->info.wrttime = entry.wrttime;
191 theent->info.size = entry.filesize;
192 theent->startcluster = entry.firstcluster;
193
194 return theent;
195}
196
197int mkdir_uncached(const char *name)
198{
199 DIR_UNCACHED *dir;
200 char namecopy[MAX_PATH];
201 char* end;
202 char *basename;
203 char *parent;
204 struct dirent_uncached *entry;
205 int dd;
206 DIR_UNCACHED* pdir = opendirs;
207 struct fat_dir *newdir;
208 int rc;
209
210 if ( name[0] != '/' ) {
211 DEBUGF("mkdir: Only absolute paths supported right now\n");
212 return -1;
213 }
214 /* find a free dir descriptor */
215 for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++)
216 if ( !pdir->busy )
217 break;
218
219 if ( dd == MAX_OPEN_DIRS ) {
220 DEBUGF("Too many dirs open\n");
221 errno = EMFILE;
222 return -5;
223 }
224
225 pdir->busy = true;
226 newdir = &pdir->fatdir;
227
228 strlcpy(namecopy, name, sizeof(namecopy));
229
230 /* Split the base name and the path */
231 end = strrchr(namecopy, '/');
232 *end = 0;
233 basename = end+1;
234
235 if(namecopy == end) /* Root dir? */
236 parent = "/";
237 else
238 parent = namecopy;
239
240 DEBUGF("mkdir: parent: %s, name: %s\n", parent, basename);
241
242 dir = opendir_uncached(parent);
243
244 if(!dir) {
245 DEBUGF("mkdir: can't open parent dir\n");
246 pdir->busy = false;
247 return -2;
248 }
249
250 if(basename[0] == 0) {
251 DEBUGF("mkdir: Empty dir name\n");
252 pdir->busy = false;
253 errno = EINVAL;
254 return -3;
255 }
256
257 /* Now check if the name already exists */
258 while ((entry = readdir_uncached(dir))) {
259 if ( !strcasecmp(basename, entry->d_name) ) {
260 DEBUGF("mkdir error: file exists\n");
261 errno = EEXIST;
262 closedir_uncached(dir);
263 pdir->busy = false;
264 return - 4;
265 }
266 }
267
268 memset(newdir, 0, sizeof(struct fat_dir));
269
270 rc = fat_create_dir(basename, newdir, &(dir->fatdir));
271 closedir_uncached(dir);
272 pdir->busy = false;
273
274 return rc;
275}
276
277int rmdir_uncached(const char* name)
278{
279 int rc;
280 DIR_UNCACHED* dir;
281 struct dirent_uncached* entry;
282
283 dir = opendir_uncached(name);
284 if (!dir)
285 {
286 errno = ENOENT; /* open error */
287 return -1;
288 }
289
290 /* check if the directory is empty */
291 while ((entry = readdir_uncached(dir)))
292 {
293 if (strcmp(entry->d_name, ".") &&
294 strcmp(entry->d_name, ".."))
295 {
296 DEBUGF("rmdir error: not empty\n");
297 errno = ENOTEMPTY;
298 closedir_uncached(dir);
299 return -2;
300 }
301 }
302
303 rc = fat_remove(&(dir->fatdir.file));
304 if ( rc < 0 ) {
305 DEBUGF("Failed removing dir: %d\n", rc);
306 errno = EIO;
307 rc = rc * 10 - 3;
308 }
309
310 closedir_uncached(dir);
311 return rc;
312}
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 @@
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2005 by Miika Pekkarinen 10 * Copyright (C) 2005 by Miika Pekkarinen
11 * Copyright (C) 2014 by Michael Sevakis
11 * 12 *
12 * This program is free software; you can redistribute it and/or 13 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License 14 * modify it under the terms of the GNU General Public License
@@ -18,13 +19,7 @@
18 * KIND, either express or implied. 19 * KIND, either express or implied.
19 * 20 *
20 ****************************************************************************/ 21 ****************************************************************************/
21
22/* TODO:
23 - Allow cache live updating while transparent rebuild is running.
24*/
25
26#include "config.h" 22#include "config.h"
27
28#include <stdio.h> 23#include <stdio.h>
29#include <errno.h> 24#include <errno.h>
30#include "string-extra.h" 25#include "string-extra.h"
@@ -33,6 +28,8 @@
33#include "debug.h" 28#include "debug.h"
34#include "system.h" 29#include "system.h"
35#include "logf.h" 30#include "logf.h"
31#include "fileobj_mgr.h"
32#include "pathfuncs.h"
36#include "dircache.h" 33#include "dircache.h"
37#include "thread.h" 34#include "thread.h"
38#include "kernel.h" 35#include "kernel.h"
@@ -42,393 +39,1626 @@
42#include "dir.h" 39#include "dir.h"
43#include "storage.h" 40#include "storage.h"
44#include "audio.h" 41#include "audio.h"
45#if CONFIG_RTC
46#include "time.h"
47#include "timefuncs.h"
48#endif
49#include "rbpaths.h" 42#include "rbpaths.h"
43#include "linked_list.h"
44#ifdef HAVE_EEPROM_SETTINGS
45#include "crc32.h"
46#endif
50 47
48/**
49 * Cache memory layout:
50 * x - array of struct dircache_entry
51 * r - reserved buffer
52 * d - name buffer for the name entry of the struct dircache_entry
53 * 0 - zero bytes to assist free name block sentinel scanning (not 0xfe or 0xff)
54 * |xxxxxx|rrrrrrrrr|0|dddddd|0|
55 *
56 * Subsequent x are allocated from the front, d are allocated from the back,
57 * using the reserve buffer for entries added after initial scan.
58 *
59 * After a while the cache may look like:
60 * |xxxxxxxx|rrrrr|0|dddddddd|0|
61 *
62 * After a reboot, the reserve buffer is restored in it's size, so that the
63 * total allocation size grows:
64 * |xxxxxxxx|rrrrrrrrr|0|dddddddd|0|
65 *
66 *
67 * Cache structure:
68 * Format is memory position independent and uses only indexes as links. The
69 * buffer pointers are offset back by one entry to make the array 1-based so
70 * that an index of 0 may be considered an analog of a NULL pointer.
71 *
72 * Entry elements are linked together analagously to the filesystem directory
73 * structure with minor variations that are helpful to the cache's algorithms.
74 * Each volume has a special root structure in the dircache structure, not an
75 * entry in the cache, comprising a forest of volume trees which facilitates
76 * mounting or dismounting of specified volumes on the fly.
77 *
78 * Indexes identifying a volume are computed as: index = -volume - 1
79 * Returning the volume from these indexes is thus: volume = -index - 1
80 * Such indexes are used in root binding and as the 'up' index for an entry
81 * who's parent is the root directory.
82 *
83 * Open files list:
84 * When dircache is made it is the maintainer of the main volume open files
85 * lists, even when it is off. Any files open before dircache is enabled or
86 * initialized must be bound to cache entries by the scan and build operation.
87 * It maintains these lists in a special way.
88 *
89 * Queued (unresolved) bindings are at the back and resolved at the front.
90 * A pointer to the first of each kind of binding is provided to skip to help
91 * iterating one sublist or another.
92 *
93 * r0->r1->r2->q0->q1->q2->NULL
94 * ^resolved0 ^queued0
95 */
51 96
52/* Queue commands. */ 97#ifdef DIRCACHE_NATIVE
53#define DIRCACHE_BUILD 1 98#define dircache_lock() file_internal_lock_WRITER()
54#define DIRCACHE_STOP 2 99#define dircache_unlock() file_internal_unlock_WRITER()
55 100
56#if (MEMORYSIZE > 8) 101/* scan and build parameter data */
57#define MAX_OPEN_DIRS 12 102struct sab_component;
58#else 103struct sab
59#define MAX_OPEN_DIRS 8 104{
60#endif 105 struct filestr_base stream; /* scan directory stream */
61static DIR_CACHED opendirs[MAX_OPEN_DIRS]; 106 struct file_base_info info; /* scanned entry info */
62static char opendir_dnames[MAX_OPEN_DIRS][MAX_PATH]; 107 bool volatile quit; /* halt all scanning */
108 struct sab_component *stackend; /* end of stack pointer */
109 struct sab_component *top; /* current top of stack */
110 struct sab_component
111 {
112 int volatile idx; /* cache index of directory */
113 int *downp; /* pointer to ce->down */
114 int *volatile prevp; /* previous item accessed */
115 } stack[]; /* "recursion" stack */
116};
117
118#else /* !DIRCACHE_NATIVE */
119
120#error need locking scheme
121#define FILESYS_WRITER_LOCK()
122#define FILESYS_WRITER_UNLOCK()
63 123
64#define MAX_PENDING_BINDINGS 2 124struct sab_component
65struct fdbind_queue { 125{
126};
127
128struct sab
129{
130#ifdef HAVE_MUTLIVOLUME
131 int volume;
132#endif /* HAVE_MULTIVOLUME */
66 char path[MAX_PATH]; 133 char path[MAX_PATH];
67 int fd; 134 unsigned int append;
135};
136#endif /* DIRCACHE_NATIVE */
137
138enum
139{
140 FRONTIER_SETTLED = 0x0, /* dir entry contents are complete */
141 FRONTIER_NEW = 0x1, /* dir entry contents are in-progress */
142 FRONTIER_ZONED = 0x2, /* frontier entry permanent mark (very sticky!) */
143 FRONTIER_RENEW = 0x4, /* override FRONTIER_ZONED sticky (not stored) */
68}; 144};
69 145
70/* Unions with char to make pointer arithmetic simpler and avoid casting */ 146enum
71struct dircache_entry { 147{
72 struct dirinfo info; 148 DCM_BUILD, /* build a volume */
149 DCM_PROCEED, /* merged DCM_BUILD messages */
150 DCM_FIRST = DCM_BUILD,
151 DCM_LAST = DCM_PROCEED,
152};
153
154#define MAX_TINYNAME sizeof (uint32_t)
155#define DC_MAX_NAME MIN(MAX_NAME, UINT8_MAX)
156
157/* Throw some warnings if about the limits if things may not work */
158#if MAX_NAME > UINT8_MAX
159#warning Need more than 8 bits in name length bitfield
160#endif
161
162#if DIRCACHE_LIMIT > ((1 << 24)-255)
163#warning Names may not be addressable with 24 bits
164#endif
165
166/* data structure used by cache entries */
167struct dircache_entry
168{
169 int next; /* next at same level */
73 union { 170 union {
74 struct dircache_entry *next; 171 int down; /* first at child level (if directory) */
75 char* next_char; 172 file_size_t filesize; /* size of file in bytes (if file) */
76 }; 173 };
174 int up; /* parent index (-volume-1 if root) */
77 union { 175 union {
78 struct dircache_entry *up; 176 struct {
79 char* up_char; 177 uint32_t name : 24; /* indirect storage (.tinyname == 0) */
178 uint32_t length : 8; /* length of name indexed by 'name' */
80 }; 179 };
81 union { 180 unsigned char namebuf[MAX_TINYNAME]; /* direct storage (.tinyname == 1) */
82 struct dircache_entry *down;
83 char* down_char;
84 }; 181 };
85 long startcluster; 182 uint32_t direntry : 16; /* entry # in parent - max 0xffff */
86 char *d_name; 183 uint32_t direntries : 5; /* # of entries used - max 21 */
87}; 184 uint32_t tinyname : 1; /* if == 1, name fits in .namebuf */
88 185 uint32_t frontier : 2; /* (FRONTIER_* bitflags) */
89/* Cache Layout: 186 uint32_t attr : 8; /* entry file attributes */
90 * 187#ifdef DIRCACHE_NATIVE
91 * x - array of struct dircache_entry 188 long firstcluster; /* first file cluster - max 0x0ffffff4 */
92 * r - reserved buffer 189 uint16_t wrtdate; /* FAT write date */
93 * d - name buffer for the d_name entry of the struct dircache_entry 190 uint16_t wrttime; /* FAT write time */
94 * |xxxxxx|rrrrrrrrr|dddddd| 191#else
95 * 192 time_t mtime; /* file last-modified time */
96 * subsequent x are allocated from the front, d are allocated from the back,
97 * using the reserve buffer for entries added after initial scan
98 *
99 * after a while the cache may look like:
100 * |xxxxxxxx|rrrrr|dddddddd|
101 *
102 * after a reboot, the reserve buffer is restored in it's size, so that the
103 * total allocation size grows
104 * |xxxxxxxx|rrrrrrrrr|dddddddd|
105 */
106/* this points to the beginnging of the buffer and the first entry */
107static struct dircache_entry *dircache_root;
108/* these point to the start and end of the name buffer (d above) */
109static char *d_names_start, *d_names_end;
110/* put "." and ".." into the d_names buffer to enable easy pointer logic */
111static char *dot, *dotdot;
112#ifdef HAVE_MULTIVOLUME
113static struct dircache_entry *append_position;
114#endif 193#endif
194 dc_serial_t serialnum; /* entry serial number */
195};
115 196
116static DIR_CACHED opendirs[MAX_OPEN_DIRS]; 197/* spare us some tedium */
117static struct dircache_entry *fd_bindings[MAX_OPEN_FILES]; 198#define ENTRYSIZE (sizeof (struct dircache_entry))
118
119static bool dircache_initialized = false;
120static bool dircache_initializing = false;
121static bool thread_enabled = false;
122static unsigned long allocated_size = 0;
123static unsigned long dircache_size = 0;
124static unsigned long entry_count = 0;
125static unsigned long reserve_used = 0;
126static unsigned int cache_build_ticks = 0;
127static unsigned long appflags = 0;
128 199
200/* thread and kernel stuff */
129static struct event_queue dircache_queue SHAREDBSS_ATTR; 201static struct event_queue dircache_queue SHAREDBSS_ATTR;
130static long dircache_stack[(DEFAULT_STACK_SIZE + 0x400)/sizeof(long)]; 202static uintptr_t dircache_stack[DIRCACHE_STACK_SIZE / sizeof (uintptr_t)];
131static const char dircache_thread_name[] = "dircache"; 203static const char dircache_thread_name[] = "dircache";
132 204
133static struct fdbind_queue fdbind_cache[MAX_PENDING_BINDINGS]; 205/* struct that is both used during run time and for persistent storage */
134static int fdbind_idx = 0; 206static struct dircache
207{
208 /* cache-wide data */
209 int free_list; /* first index of free entry list */
210 size_t size; /* total size of data (including holes) */
211 size_t sizeused; /* bytes of .size bytes actually used */
212 union {
213 unsigned int numentries; /* entry count (including holes) */
214#ifdef HAVE_EEPROM_SETTINGS
215 size_t sizeentries; /* used when persisting */
216#endif
217 };
218 int names; /* index of first name in name block */
219 size_t sizenames; /* size of all names (including holes) */
220 size_t namesfree; /* amount of wasted name space */
221 int nextnamefree; /* hint of next free name in buffer */
222 /* per-volume data */
223 struct dircache_volume /* per volume cache data */
224 {
225 uint32_t status : 2; /* cache status of this volume */
226 uint32_t frontier : 2; /* (FRONTIER_* bitflags) */
227 dc_serial_t serialnum; /* dircache serial number of root */
228 int root_down; /* index of first entry of volume root */
229 union {
230 long start_tick; /* when did scan start (scanning) */
231 long build_ticks; /* how long to build volume? (ready) */
232 };
233 } dcvol[NUM_VOLUMES];
234 /* these remain unchanged between cache resets */
235 size_t last_size; /* last reported size at boot */
236 size_t reserve_used; /* reserved used at last build */
237 dc_serial_t last_serialnum; /* last serialnumber generated */
238} dircache;
239
240/* struct that is used only for the cache in main memory */
241struct dircache_runinfo
242{
243 /* cache setting and build info */
244 int suspended; /* dircache suspend count */
245 bool enabled; /* dircache master enable switch */
246 unsigned int thread_id; /* current/last thread id */
247 bool thread_done; /* thread has exited */
248 /* cache buffer info */
249 int handle; /* buflib buffer handle */
250 size_t bufsize; /* size of buflib allocation - 1 */
251 int buflocked; /* don't move due to other allocs */
252 union {
253 void *p; /* address of buffer - ENTRYSIZE */
254 struct dircache_entry *pentry; /* alias of .p to assist entry resolution */
255 unsigned char *pname; /* alias of .p to assist name resolution */
256 };
257 struct buflib_callbacks ops; /* buflib ops callbacks */
258 /* per-volume data */
259 struct dircache_runinfo_volume
260 {
261 struct file_base_binding *resolved0; /* first resolved binding in list */
262 struct file_base_binding *queued0; /* first queued binding in list */
263 struct sab *sabp; /* if building, struct sab in use */
264 } dcrivol[NUM_VOLUMES];
265} dircache_runinfo;
266
267#define BINDING_NEXT(bindp) \
268 ((struct file_base_binding *)(bindp)->node.next)
269
270#define FOR_EACH_BINDING(start, p) \
271 for (struct file_base_binding *p = (start); p; p = BINDING_NEXT(p))
272
273#define FOR_EACH_CACHE_ENTRY(ce) \
274 for (struct dircache_entry *ce = &dircache_runinfo.pentry[1], \
275 *_ceend = ce + dircache.numentries; \
276 ce < _ceend; ce++) if (ce->serialnum)
277
278#define FOR_EACH_SAB_COMP(sabp, p) \
279 for (struct sab_component *p = (sabp)->top; p < (sabp)->stackend; p++)
280
281/* "overloaded" macros to get volume structures */
282#define DCVOL_i(i) (&dircache.dcvol[i])
283#define DCVOL_volume(volume) (&dircache.dcvol[volume])
284#define DCVOL_infop(infop) (&dircache.dcvol[BASEINFO_VOL(infop)])
285#define DCVOL_dirinfop(dirinfop) (&dircache.dcvol[BASEINFO_VOL(dirinfop)])
286#define DCVOL(x) DCVOL_##x(x)
287
288#define DCRIVOL_i(i) (&dircache_runinfo.dcrivol[i])
289#define DCRIVOL_infop(infop) (&dircache_runinfo.dcrivol[BASEINFO_VOL(infop)])
290#define DCRIVOL_bindp(bindp) (&dircache_runinfo.dcrivol[BASEBINDING_VOL(bindp)])
291#define DCRIVOL(x) DCRIVOL_##x(x)
292
293/* reserve over 75% full? */
294#define DIRCACHE_STUFFED(reserve_used) \
295 ((reserve_used) > 3*DIRCACHE_RESERVE / 4)
135 296
136/* --- Internal cache structure control functions --- */ 297#ifdef HAVE_EEPROM_SETTINGS
298/**
299 * remove the snapshot file
300 */
301static int remove_dircache_file(void)
302{
303 return remove(DIRCACHE_FILE);
304}
305
306/**
307 * open or create the snapshot file
308 */
309static int open_dircache_file(int oflag)
310{
311 return open(DIRCACHE_FILE, oflag, 0666);
312}
313#endif /* HAVE_EEPROM_SETTINGS */
137 314
138static inline struct dircache_entry* get_entry(int id) 315#ifdef DIRCACHE_DUMPSTER
316/**
317 * clean up the memory allocation to make viewing in a hex editor more friendly
318 * and highlight problems
319 */
320static inline void dumpster_clean_buffer(void *p, size_t size)
139{ 321{
140 return &dircache_root[id]; 322 memset(p, 0xAA, size);
141} 323}
324#endif /* DIRCACHE_DUMPSTER */
142 325
143/* flag to make sure buffer doesn't move due to other allocs. 326/**
144 * this is set to true completely during dircache build */ 327 * relocate the cache when the buffer has moved
145static bool dont_move = false; 328 */
146static int dircache_handle; 329static int move_callback(int handle, void *current, void *new)
147static int move_callback(int handle, void* current, void* new)
148{ 330{
149 (void)handle; 331 if (dircache_runinfo.buflocked)
150 if (dont_move)
151 return BUFLIB_CB_CANNOT_MOVE; 332 return BUFLIB_CB_CANNOT_MOVE;
152 333
153#define UPDATE(x) if (x) { x = PTR_ADD(x, diff); } 334 dircache_runinfo.p = new - ENTRYSIZE;
154 /* relocate the cache */ 335
155 ptrdiff_t diff = new - current; 336 return BUFLIB_CB_OK;
156 for(unsigned i = 0; i < entry_count; i++) 337 (void)handle; (void)current;
338}
339
340/**
341 * add a "don't move" lock count
342 */
343static inline void buffer_lock(void)
344{
345 dircache_runinfo.buflocked++;
346}
347
348/**
349 * remove a "don't move" lock count
350 */
351static inline void buffer_unlock(void)
352{
353 dircache_runinfo.buflocked--;
354}
355
356
357/** Open file bindings management **/
358
359/* compare the basic file information and return 'true' if they are logically
360 equivalent or the same item, else return 'false' if not */
361static inline bool binding_compare(const struct file_base_info *infop1,
362 const struct file_base_info *infop2)
363{
364#ifdef DIRCACHE_NATIVE
365 return fat_file_is_same(&infop1->fatfile, &infop2->fatfile);
366#else
367 #error hey watch it!
368#endif
369}
370
371/**
372 * bind a file to the cache; "queued" or "resolved" depending upon whether or
373 * not it has entry information
374 */
375static void binding_open(struct file_base_binding *bindp)
376{
377 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp);
378 if (bindp->info.dcfile.serialnum)
157 { 379 {
158 UPDATE(dircache_root[i].d_name); 380 /* already resolved */
159 UPDATE(dircache_root[i].next_char); 381 dcrivolp->resolved0 = bindp;
160 UPDATE(dircache_root[i].up_char); 382 file_binding_insert_first(bindp);
161 UPDATE(dircache_root[i].down_char);
162 } 383 }
163 dircache_root = new; 384 else
164 UPDATE(d_names_start); 385 {
165 UPDATE(d_names_end); 386 if (dcrivolp->queued0 == NULL)
166 UPDATE(dot); 387 dcrivolp->queued0 = bindp;
167 UPDATE(dotdot);
168 388
169 for(unsigned i = 0; i < MAX_OPEN_FILES; i++) 389 file_binding_insert_last(bindp);
170 UPDATE(fd_bindings[i]); 390 }
391}
171 392
172#ifdef HAVE_MULTIVOLUME 393/**
173 UPDATE(append_position); 394 * remove a binding from the cache
174#endif 395 */
175 return BUFLIB_CB_OK; 396static void binding_close(struct file_base_binding *bindp)
397{
398 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp);
399
400 if (bindp == dcrivolp->queued0)
401 dcrivolp->queued0 = BINDING_NEXT(bindp);
402 else if (bindp == dcrivolp->resolved0)
403 {
404 struct file_base_binding *nextp = BINDING_NEXT(bindp);
405 dcrivolp->resolved0 = (nextp == dcrivolp->queued0) ? NULL : nextp;
406 }
407
408 file_binding_remove(bindp);
409 /* no need to reset it */
176} 410}
177 411
178static struct buflib_callbacks ops = { 412/**
179 .move_callback = move_callback, 413 * resolve a queued binding with the information from the given source file
180 .shrink_callback = NULL, 414 */
181}; 415static void binding_resolve(const struct file_base_info *infop)
416{
417 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop);
418
419 /* quickly check the queued list to see if it's there */
420 struct file_base_binding *prevp = NULL;
421 FOR_EACH_BINDING(dcrivolp->queued0, p)
422 {
423 if (!binding_compare(infop, &p->info))
424 {
425 prevp = p;
426 continue;
427 }
428
429 if (p == dcrivolp->queued0)
430 dcrivolp->queued0 = BINDING_NEXT(p);
431 else
432 {
433 file_binding_remove_next(prevp, p);
434 file_binding_insert_first(p);
435 }
436
437 dcrivolp->resolved0 = p;
438
439 /* srcinfop may be the actual one */
440 if (&p->info != infop)
441 p->info.dcfile = infop->dcfile;
442
443 break;
444 }
445}
182 446
183#ifdef HAVE_EEPROM_SETTINGS
184/** 447/**
185 * Open the dircache file to save a snapshot on disk 448 * dissolve a resolved binding on its volume
186 */ 449 */
187static int open_dircache_file(unsigned flags, int permissions) 450static void binding_dissolve(struct file_base_binding *prevp,
451 struct file_base_binding *bindp)
188{ 452{
189 if (permissions != 0) 453 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(bindp);
190 return open(DIRCACHE_FILE, flags, permissions);
191 454
192 return open(DIRCACHE_FILE, flags); 455 if (bindp == dcrivolp->resolved0)
456 {
457 struct file_base_binding *nextp = BINDING_NEXT(bindp);
458 dcrivolp->resolved0 = (nextp == dcrivolp->queued0) ? NULL : nextp;
459 }
460
461 if (dcrivolp->queued0 == NULL)
462 dcrivolp->queued0 = bindp;
463
464 file_binding_remove_next(prevp, bindp);
465 file_binding_insert_last(bindp);
466
467 dircache_dcfile_init(&bindp->info.dcfile);
193} 468}
194 469
195/** 470/**
196 * Remove the snapshot file 471 * dissolve all resolved bindings on a given volume
197 */ 472 */
198static int remove_dircache_file(void) 473static void binding_dissolve_volume(struct dircache_runinfo_volume *dcrivolp)
199{ 474{
200 return remove(DIRCACHE_FILE); 475 if (!dcrivolp->resolved0)
476 return;
477
478 FOR_EACH_BINDING(dcrivolp->resolved0, p)
479 {
480 if (p == dcrivolp->queued0)
481 break;
482
483 dircache_dcfile_init(&p->info.dcfile);
484 }
485
486 dcrivolp->queued0 = dcrivolp->resolved0;
487 dcrivolp->resolved0 = NULL;
201} 488}
202#endif 489
203/** 490
204 * Internal function to allocate a new dircache_entry from memory. 491/** Dircache buffer management **/
492
493/**
494 * allocate the cache's memory block
205 */ 495 */
206static struct dircache_entry* allocate_entry(void) 496static int alloc_cache(size_t size)
207{ 497{
208 struct dircache_entry *next_entry; 498 /* pad with one extra-- see alloc_name() and free_name() */
209 499 return core_alloc_ex("dircache", size + 1, &dircache_runinfo.ops);
210 if (dircache_size > allocated_size - MAX_PATH*2) 500}
501
502/**
503 * put the allocation in dircache control
504 */
505static void set_buffer(int handle, size_t size)
506{
507 void *p = core_get_data(handle);
508
509#ifdef DIRCACHE_DUMPSTER
510 dumpster_clean_buffer(p, size);
511#endif /* DIRCACHE_DUMPSTER */
512
513 /* set it up as a 1-based array */
514 dircache_runinfo.p = p - ENTRYSIZE;
515
516 if (dircache_runinfo.handle != handle)
211 { 517 {
212 logf("size limit reached"); 518 /* new buffer */
213 return NULL; 519 dircache_runinfo.handle = handle;
520 dircache_runinfo.bufsize = size;
521 dircache.names = size + ENTRYSIZE;
522 dircache_runinfo.pname[dircache.names - 1] = 0;
523 dircache_runinfo.pname[dircache.names ] = 0;
214 } 524 }
215 525}
216 next_entry = &dircache_root[entry_count++];
217 next_entry->d_name = NULL;
218 next_entry->up = NULL;
219 next_entry->down = NULL;
220 next_entry->next = NULL;
221 526
222 dircache_size += sizeof(struct dircache_entry); 527/**
528 * remove the allocation from dircache control and return the handle
529 */
530static int reset_buffer(void)
531{
532 int handle = dircache_runinfo.handle;
533 if (handle > 0)
534 {
535 /* don't mind .p; it might get changed by the callback even after
536 this call; buffer presence is determined by the following: */
537 dircache_runinfo.handle = 0;
538 dircache_runinfo.bufsize = 0;
539 }
540
541 return handle;
542}
543
544/**
545 * return the number of bytes remaining in the buffer
546 */
547static size_t dircache_buf_remaining(void)
548{
549 if (!dircache_runinfo.handle)
550 return 0;
551
552 return dircache_runinfo.bufsize - dircache.size;
553}
554
555/**
556 * return the amount of reserve space used
557 */
558static size_t reserve_buf_used(void)
559{
560 size_t remaining = dircache_buf_remaining();
561 return (remaining < DIRCACHE_RESERVE) ?
562 DIRCACHE_RESERVE - remaining : 0;
563}
564
565
566/** Internal cache structure control functions **/
567
568/**
569 * generate the next serial number in the sequence
570 */
571static dc_serial_t next_serialnum(void)
572{
573 dc_serial_t serialnum = MAX(dircache.last_serialnum + 1, 1);
574 dircache.last_serialnum = serialnum;
575 return serialnum;
576}
577
578/**
579 * return the dircache volume pointer for the special index
580 */
581static struct dircache_volume * get_idx_dcvolp(int idx)
582{
583 if (idx >= 0)
584 return NULL;
585
586 return &dircache.dcvol[IF_MV_VOL(-idx - 1)];
587}
588
589/**
590 * return the cache entry referenced by idx (NULL if outside buffer)
591 */
592static struct dircache_entry * get_entry(int idx)
593{
594 if (idx <= 0 || (unsigned int)idx > dircache.numentries)
595 return NULL;
223 596
224 return next_entry; 597 return &dircache_runinfo.pentry[idx];
225} 598}
226 599
227/** 600/**
228 * Internal function to allocate a dircache_entry and set 601 * return the index of the cache entry (0 if outside buffer)
229 * ->next entry pointers.
230 */ 602 */
231static struct dircache_entry* dircache_gen_next(struct dircache_entry *ce) 603static int get_index(const struct dircache_entry *ce)
232{ 604{
233 struct dircache_entry *next_entry; 605 if (!PTR_IN_ARRAY(dircache_runinfo.pentry + 1, ce,
606 dircache.numentries + 1))
607 {
608 return 0;
609 }
610
611 return ce - dircache_runinfo.pentry;
612}
234 613
235 if ( (next_entry = allocate_entry()) == NULL) 614/**
615 * return the sublist down pointer for the sublist that contains entry 'idx'
616 */
617static int * get_downidxp(int idx)
618{
619 /* NOTE: 'idx' must refer to a directory or the result is undefined */
620 if (idx == 0 || idx < -NUM_VOLUMES)
236 return NULL; 621 return NULL;
237 next_entry->up = ce->up; 622
238 ce->next = next_entry; 623 if (idx > 0)
239 624 {
240 return next_entry; 625 /* a normal entry */
626 struct dircache_entry *ce = get_entry(idx);
627 return ce ? &ce->down : NULL;
628 }
629 else
630 {
631 /* a volume root */
632 return &get_idx_dcvolp(idx)->root_down;
633 }
241} 634}
242 635
243/* 636/**
244 * Internal function to allocate a dircache_entry and set 637 * return a pointer to the index referencing the cache entry that 'idx'
245 * ->down entry pointers. 638 * references
246 */ 639 */
247static struct dircache_entry* dircache_gen_down(struct dircache_entry *ce) 640static int * get_previdxp(int idx)
248{ 641{
249 struct dircache_entry *next_entry; 642 struct dircache_entry *ce = get_entry(idx);
250 643
251 if ( (next_entry = allocate_entry()) == NULL) 644 int *prevp = get_downidxp(ce->up);
645 if (!prevp)
252 return NULL; 646 return NULL;
253 next_entry->up = ce; 647
254 ce->down = next_entry; 648 while (1)
255 649 {
256 return next_entry; 650 int next = *prevp;
651 if (!next || next == idx)
652 break;
653
654 prevp = &get_entry(next)->next;
655 }
656
657 return prevp;
658}
659
660/**
661 * if required, adjust the lists and directory read of any scan and build in
662 * progress
663 */
664static void sab_sync_scan(struct sab *sabp, int *prevp, int *nextp)
665{
666 struct sab_component *abovep = NULL;
667 FOR_EACH_SAB_COMP(sabp, p)
668 {
669 if (nextp != p->prevp)
670 {
671 abovep = p;
672 continue;
673 }
674
675 /* removing an item being scanned; set the component position to the
676 entry before this */
677 p->prevp = prevp;
678
679 if (p == sabp->top)
680 {
681 /* removed at item in the directory who's immediate contents are
682 being scanned */
683 if (prevp == p->downp)
684 {
685 /* was first item; rewind it */
686 dircache_rewinddir_internal(&sabp->info);
687 }
688 else
689 {
690 struct dircache_entry *ceprev =
691 container_of(prevp, struct dircache_entry, next);
692 #ifdef DIRCACHE_NATIVE
693 sabp->info.fatfile.e.entry = ceprev->direntry;
694 sabp->info.fatfile.e.entries = ceprev->direntries;
695 #endif
696 }
697 }
698 else if (abovep)
699 {
700 /* the directory being scanned or a parent of it has been removed;
701 abort its build or cache traversal */
702 abovep->idx = 0;
703 }
704
705 break;
706 }
707}
708
709/**
710 * get a pointer to an allocated name given a cache index
711 */
712static inline unsigned char * get_name(int nameidx)
713{
714 return &dircache_runinfo.pname[nameidx];
715}
716
717/**
718 * get the cache buffer index of the given name
719 */
720static inline int get_nameidx(const unsigned char *pname)
721{
722 return pname - dircache_runinfo.pname;
723}
724
725/**
726 * copy the entry's name to a buffer (which assumed to be of sufficient size)
727 */
728static void entry_name_copy(char *dst, const struct dircache_entry *ce)
729{
730 if (LIKELY(!ce->tinyname))
731 {
732 strmemcpy(dst, get_name(ce->name), ce->length);
733 return;
734 }
735
736 const unsigned char *src = ce->namebuf;
737 size_t len = 0;
738 while (len++ < MAX_TINYNAME && *src)
739 *dst++ = *src++;
740
741 *dst = '\0';
742}
743
744/**
745 * set the namesfree hint to a new position
746 */
747static void set_namesfree_hint(const unsigned char *hintp)
748{
749 int hintidx = get_nameidx(hintp);
750
751 if (hintidx >= (int)(dircache.names + dircache.sizenames))
752 hintidx = dircache.names;
753
754 dircache.nextnamefree = hintidx;
257} 755}
258 756
259/** 757/**
260 * Returns true if there is an event waiting in the queue 758 * allocate a buffer to use for a new name
261 * that requires the current operation to be aborted.
262 */ 759 */
263static bool check_event_queue(void) 760static int alloc_name(size_t size)
761{
762 int nameidx = 0;
763
764 if (dircache.namesfree >= size)
765 {
766 /* scan for a free gap starting at the hint point - first fit */
767 unsigned char *start = get_name(dircache.nextnamefree), *p = start;
768 unsigned char *namesend = get_name(dircache.names + dircache.sizenames);
769 size_t gapsize = 0;
770
771 while (gapsize < size)
772 {
773 if ((p = memchr(p, 0xff, namesend - p)))
774 {
775 /* found a sentinel; see if there are enough in a row */
776 gapsize = 1;
777 while (*++p == 0xff && gapsize < size)
778 gapsize++;
779 }
780 else
781 {
782 if (namesend == start)
783 break; /* exhausted */
784
785 /* wrap */
786 namesend = start;
787 p = get_name(dircache.names);
788
789 if (p == namesend)
790 break; /* initial hint was at names start */
791 }
792 }
793
794 if (gapsize >= size)
795 {
796 unsigned char *namep = p - gapsize;
797 nameidx = get_nameidx(namep);
798
799 if (*p == 0xff)
800 {
801 /* if only a tiny block remains after buffer, claim it too */
802 size_t tinysize = 1;
803 while (*++p == 0xff && tinysize <= MAX_TINYNAME)
804 tinysize++;
805
806 if (tinysize <= MAX_TINYNAME)
807 {
808 /* mark with tiny block sentinel */
809 memset(p - tinysize, 0xfe, tinysize);
810 size += tinysize;
811 }
812 }
813
814 dircache.namesfree -= size;
815 dircache.sizeused += size;
816 set_namesfree_hint(namep + size);
817 }
818 }
819
820 if (!nameidx)
821 {
822 /* no sufficiently long free gaps; allocate anew */
823 if (dircache_buf_remaining() <= size)
824 {
825 dircache.last_size = 0;
826 return 0;
827 }
828
829 dircache.names -= size;
830 dircache.sizenames += size;
831 nameidx = dircache.names;
832 dircache.size += size;
833 dircache.sizeused += size;
834 *get_name(dircache.names - 1) = 0;
835 }
836
837 return nameidx;
838}
839
840/**
841 * mark a name as free and note that its bytes are available
842 */
843static void free_name(int nameidx, size_t size)
844{
845 unsigned char *beg = get_name(nameidx);
846 unsigned char *end = beg + size;
847
848 /* merge with any adjacent tiny blocks */
849 while (beg[-1] == 0xfe)
850 --beg;
851
852 while (end[1] == 0xfe)
853 ++end;
854
855 size = end - beg;
856 memset(beg, 0xff, size);
857 dircache.namesfree += size;
858 dircache.sizeused -= size;
859 set_namesfree_hint(beg);
860}
861
862/**
863 * allocate and assign a name to the entry
864 */
865static bool entry_assign_name(struct dircache_entry *ce,
866 const unsigned char *name, size_t size)
867{
868 unsigned char *copyto;
869
870 if (size <= MAX_TINYNAME)
871 {
872 copyto = ce->namebuf;
873
874 if (size < MAX_TINYNAME)
875 copyto[size] = '\0';
876
877 ce->tinyname = 1;
878 }
879 else
880 {
881 if (size > DC_MAX_NAME)
882 size = DC_MAX_NAME;
883
884 int nameidx = alloc_name(size);
885 if (!nameidx)
886 return false;
887
888 copyto = get_name(nameidx);
889
890 ce->tinyname = 0;
891 ce->name = nameidx;
892 ce->length = size;
893 }
894
895 memcpy(copyto, name, size);
896 return true;
897}
898
899/**
900 * free the name for the entry
901 */
902static void entry_unassign_name(struct dircache_entry *ce)
903{
904 if (!ce->tinyname)
905 free_name(ce->name, ce->length);
906}
907
908/**
909 * assign a new name to the entry
910 */
911static bool entry_reassign_name(struct dircache_entry *ce,
912 const unsigned char *newname)
913{
914 size_t oldlen = ce->tinyname ? 0 : ce->length;
915 size_t newlen = strlen(newname);
916
917 if (oldlen == newlen || (oldlen == 0 && newlen <= MAX_TINYNAME))
918 {
919 char *p = mempcpy(oldlen == 0 ? ce->namebuf : get_name(ce->name),
920 newname, newlen);
921 if (newlen < MAX_TINYNAME)
922 *p = '\0';
923 return true;
924 }
925
926 /* needs a new name allocation; if the new name fits in the freed block,
927 it will use it immediately without a lengthy search */
928 entry_unassign_name(ce);
929 return entry_assign_name(ce, newname, newlen);
930}
931
932/**
933 * allocate a dircache_entry from memory using freed ones if available
934 */
935static int alloc_entry(struct dircache_entry **res)
936{
937 struct dircache_entry *ce;
938 int idx = dircache.free_list;
939
940 if (idx)
941 {
942 /* reuse a freed entry */
943 ce = get_entry(idx);
944 dircache.free_list = ce->next;
945 }
946 else if (dircache_buf_remaining() > ENTRYSIZE)
947 {
948 /* allocate a new one */
949 idx = ++dircache.numentries;
950 dircache.size += ENTRYSIZE;
951 ce = get_entry(idx);
952 }
953 else
954 {
955 dircache.last_size = 0;
956 *res = NULL;
957 return 0;
958 }
959
960 dircache.sizeused += ENTRYSIZE;
961
962 ce->next = 0;
963 ce->up = 0;
964 ce->down = 0;
965 ce->length = 0;
966 ce->frontier = FRONTIER_SETTLED;
967 ce->serialnum = next_serialnum();
968
969 *res = ce;
970 return idx;
971}
972
973/**
974 * free an entry's allocations in the cache; must not be linked to anything
975 * by this time (orphan!)
976 */
977static void free_orphan_entry(struct dircache_runinfo_volume *dcrivolp,
978 struct dircache_entry *ce, int idx)
979{
980 if (dcrivolp)
981 {
982 /* was an established entry; find any associated resolved binding and
983 dissolve it; bindings are kept strictly synchronized with changes
984 to the storage so a simple serial number comparison is sufficient */
985 struct file_base_binding *prevp = NULL;
986 FOR_EACH_BINDING(dcrivolp->resolved0, p)
987 {
988 if (p == dcrivolp->queued0)
989 break;
990
991 if (ce->serialnum == p->info.dcfile.serialnum)
992 {
993 binding_dissolve(prevp, p);
994 break;
995 }
996
997 prevp = p;
998 }
999 }
1000
1001 entry_unassign_name(ce);
1002
1003 /* no serialnum says "it's free" (for cache-wide iterators) */
1004 ce->serialnum = 0;
1005
1006 /* add to free list */
1007 ce->next = dircache.free_list;
1008 dircache.free_list = idx;
1009 dircache.sizeused -= ENTRYSIZE;
1010}
1011
1012/**
1013 * allocates a new entry of with the name specified by 'basename'
1014 */
1015static int create_entry(const char *basename,
1016 struct dircache_entry **res)
1017{
1018 int idx = alloc_entry(res);
1019 if (!idx)
1020 {
1021 logf("size limit reached (entry)");
1022 return 0; /* full */
1023 }
1024
1025 if (!entry_assign_name(*res, basename, strlen(basename)))
1026 {
1027 free_orphan_entry(NULL, *res, idx);
1028 logf("size limit reached (name)");
1029 return 0; /* really full! */
1030 }
1031
1032 return idx;
1033}
1034
1035/**
1036 * unlink the entry at *prevp and adjust the scanner if needed
1037 */
1038static void remove_entry(struct dircache_runinfo_volume *dcrivolp,
1039 struct dircache_entry *ce, int *prevp)
1040{
1041 /* unlink it from its list */
1042 *prevp = ce->next;
1043
1044 if (dcrivolp)
1045 {
1046 /* adjust scanner iterator if needed */
1047 struct sab *sabp = dcrivolp->sabp;
1048 if (sabp)
1049 sab_sync_scan(sabp, prevp, &ce->next);
1050 }
1051}
1052
1053/**
1054 * free the entire subtree in the referenced parent down index
1055 */
1056static void free_subentries(struct dircache_runinfo_volume *dcrivolp, int *downp)
1057{
1058 while (1)
1059 {
1060 int idx = *downp;
1061 struct dircache_entry *ce = get_entry(idx);
1062 if (!ce)
1063 break;
1064
1065 if ((ce->attr & ATTR_DIRECTORY) && ce->down)
1066 free_subentries(dcrivolp, &ce->down);
1067
1068 remove_entry(dcrivolp, ce, downp);
1069 free_orphan_entry(dcrivolp, ce, idx);
1070 }
1071}
1072
1073/**
1074 * free the specified file entry and its children
1075 */
1076static void free_file_entry(struct file_base_info *infop)
1077{
1078 int idx = infop->dcfile.idx;
1079 if (idx <= 0)
1080 return; /* can't remove a root/invalid */
1081
1082 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop);
1083
1084 struct dircache_entry *ce = get_entry(idx);
1085 if ((ce->attr & ATTR_DIRECTORY) && ce->down)
1086 {
1087 /* gonna get all this contents (normally the "." and "..") */
1088 free_subentries(dcrivolp, &ce->down);
1089 }
1090
1091 remove_entry(dcrivolp, ce, get_previdxp(idx));
1092 free_orphan_entry(dcrivolp, ce, idx);
1093}
1094
1095/**
1096 * insert the new entry into the parent, sorted into position
1097 */
1098static void insert_file_entry(struct file_base_info *dirinfop,
1099 struct dircache_entry *ce)
1100{
1101 /* DIRCACHE_NATIVE: the entires are sorted into the spot it would be on
1102 * the storage medium based upon the directory entry number, in-progress
1103 * scans will catch it or miss it in just the same way they would if
1104 * directly scanning the disk. If this is behind an init scan, it gets
1105 * added anyway; if in front of it, then scanning will compare what it
1106 * finds in order to avoid adding a duplicate.
1107 *
1108 * All others: the order of entries of the host filesystem is not known so
1109 * this must be placed at the end so that a build scan won't miss it and
1110 * add a duplicate since it will be comparing any entries it finds in front
1111 * of it.
1112 */
1113 int diridx = dirinfop->dcfile.idx;
1114 int *nextp = get_downidxp(diridx);
1115
1116 while (8675309)
1117 {
1118 struct dircache_entry *nextce = get_entry(*nextp);
1119 if (!nextce)
1120 break;
1121
1122#ifdef DIRCACHE_NATIVE
1123 if (nextce->direntry > ce->direntry)
1124 break;
1125
1126 /* now, nothing should be equal to ours or that is a bug since it
1127 would already exist (and it shouldn't because it's just been
1128 created or moved) */
1129#endif /* DIRCACHE_NATIVE */
1130
1131 nextp = &nextce->next;
1132 }
1133
1134 ce->up = diridx;
1135 ce->next = *nextp;
1136 *nextp = get_index(ce);
1137}
1138
1139/**
1140 * unlink the entry from its parent and return its pointer to the caller
1141 */
1142static struct dircache_entry * remove_file_entry(struct file_base_info *infop)
1143{
1144 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(infop);
1145 struct dircache_entry *ce = get_entry(infop->dcfile.idx);
1146 remove_entry(dcrivolp, ce, get_previdxp(infop->dcfile.idx));
1147 return ce;
1148}
1149
1150/**
1151 * set the frontier indicator for the given cache index
1152 */
1153static void establish_frontier(int idx, uint32_t code)
1154{
1155 if (idx < 0)
1156 {
1157 int volume = IF_MV_VOL(-idx - 1);
1158 uint32_t val = dircache.dcvol[volume].frontier;
1159 if (code & FRONTIER_RENEW)
1160 val &= ~FRONTIER_ZONED;
1161 dircache.dcvol[volume].frontier = code | (val & FRONTIER_ZONED);
1162 }
1163 else if (idx > 0)
1164 {
1165 struct dircache_entry *ce = get_entry(idx);
1166 uint32_t val = ce->frontier;
1167 if (code & FRONTIER_RENEW)
1168 val &= ~FRONTIER_ZONED;
1169 ce->frontier = code | (val & FRONTIER_ZONED);
1170 }
1171}
1172
1173/**
1174 * remove all messages from the queue, responding to anyone waiting
1175 */
1176static void clear_dircache_queue(void)
264{ 1177{
265 struct queue_event ev; 1178 struct queue_event ev;
266 1179
267 if(!queue_peek(&dircache_queue, &ev)) 1180 while (1)
268 return false; 1181 {
269 1182 queue_wait_w_tmo(&dircache_queue, &ev, 0);
270 switch (ev.id) 1183 if (ev.id == SYS_TIMEOUT)
271 { 1184 break;
272 case DIRCACHE_STOP: 1185
273 case SYS_USB_CONNECTED: 1186 /* respond to any synchronous build queries; since we're already
274#ifdef HAVE_HOTSWAP 1187 building and thusly allocated, any additional requests can be
275 case SYS_FS_CHANGED: 1188 processed async */
276#endif 1189 if (ev.id == DCM_BUILD)
277 return true; 1190 {
1191 int *rcp = (int *)ev.data;
1192 if (rcp)
1193 *rcp = 0;
1194 }
278 } 1195 }
279
280 return false;
281} 1196}
282 1197
283#if (CONFIG_PLATFORM & PLATFORM_NATIVE) 1198/**
284/* scan and build static data (avoid redundancy on stack) */ 1199 * service dircache_queue during a scan and build
285static struct 1200 */
1201static void process_events(void)
286{ 1202{
287#ifdef HAVE_MULTIVOLUME 1203 yield();
288 int volume; 1204
289#endif 1205 /* only count externally generated commands */
290 struct fat_dir *dir; 1206 if (!queue_peek_ex(&dircache_queue, NULL, 0, QPEEK_FILTER1(DCM_BUILD)))
291 struct fat_direntry *direntry; 1207 return;
292}sab; 1208
293 1209 clear_dircache_queue();
294static int sab_process_dir(unsigned long startcluster, struct dircache_entry *ce) 1210
295{ 1211 /* this reminds us to keep moving after we're done here; a volume we passed
296 /* normally, opendir expects a full fat_dir as parent but in our case, 1212 up earlier could have been mounted and need refreshing; it just condenses
297 * it's completely useless because we don't modify anything 1213 a slew of requests into one and isn't mistaken for an externally generated
298 * WARNING: this heavily relies on current FAT implementation ! */ 1214 command */
299 1215 queue_post(&dircache_queue, DCM_PROCEED, 0);
300 /* those field are necessary to update the FAT entry in case of modification 1216}
301 here we don't touch anything so we put dummy values */ 1217
302 sab.dir->entry = 0; 1218#if defined (DIRCACHE_NATIVE)
303 sab.dir->entrycount = 0; 1219/**
304 sab.dir->file.firstcluster = 0; 1220 * scan and build the contents of a subdirectory
305 /* open directory */ 1221 */
306 int rc = fat_opendir(IF_MV(sab.volume,) sab.dir, startcluster, sab.dir); 1222static void sab_process_sub(struct sab *sabp)
307 if(rc < 0) 1223{
308 { 1224 struct fat_direntry *const fatentp = get_dir_fatent();
309 logf("fat_opendir failed: %d", rc); 1225 struct filestr_base *const streamp = &sabp->stream;
310 return rc; 1226 struct file_base_info *const infop = &sabp->info;
1227
1228 int idx = infop->dcfile.idx;
1229 int *downp = get_downidxp(idx);
1230 if (!downp)
1231 return;
1232
1233 while (1)
1234 {
1235 struct sab_component *compp = --sabp->top;
1236 compp->idx = idx;
1237 compp->downp = downp;
1238 compp->prevp = downp;
1239
1240 /* open directory stream */
1241 filestr_base_init(streamp);
1242 fileobj_fileop_open(streamp, infop, FO_DIRECTORY);
1243 fat_rewind(&streamp->fatstr);
1244 uncached_rewinddir_internal(infop);
1245
1246 const long dircluster = streamp->infop->fatfile.firstcluster;
1247
1248 /* first pass: read directory */
1249 while (1)
1250 {
1251 if (sabp->stack + 1 < sabp->stackend)
1252 {
1253 /* release control and process queued events */
1254 dircache_unlock();
1255 process_events();
1256 dircache_lock();
1257
1258 if (sabp->quit || !compp->idx)
1259 break;
1260 }
1261 /* else an immediate-contents directory scan */
1262
1263 int rc = uncached_readdir_internal(streamp, infop, fatentp);
1264 if (rc <= 0)
1265 {
1266 if (rc < 0)
1267 sabp->quit = true;
1268 else
1269 compp->prevp = downp; /* rewind list */
1270
1271 break;
1272 }
1273
1274 struct dircache_entry *ce;
1275 int prev = *compp->prevp;
1276
1277 if (prev)
1278 {
1279 /* there are entries ahead of us; they will be what was just
1280 read or something to be subsequently read; if it belongs
1281 ahead of this one, insert a new entry before it; if it's
1282 the entry just scanned, do nothing further and continue
1283 with the next */
1284 ce = get_entry(prev);
1285 if (ce->direntry == infop->fatfile.e.entry)
1286 {
1287 compp->prevp = &ce->next;
1288 continue; /* already there */
1289 }
1290 }
1291
1292 int idx = create_entry(fatentp->name, &ce);
1293 if (!idx)
1294 {
1295 sabp->quit = true;
1296 break;
1297 }
1298
1299 /* link it in */
1300 ce->up = compp->idx;
1301 ce->next = prev;
1302 *compp->prevp = idx;
1303 compp->prevp = &ce->next;
1304
1305 if (!(fatentp->attr & ATTR_DIRECTORY))
1306 ce->filesize = fatentp->filesize;
1307 else if (!is_dotdir_name(fatentp->name))
1308 ce->frontier = FRONTIER_NEW; /* this needs scanning */
1309
1310 /* copy remaining FS info */
1311 ce->direntry = infop->fatfile.e.entry;
1312 ce->direntries = infop->fatfile.e.entries;
1313 ce->attr = fatentp->attr;
1314 ce->firstcluster = fatentp->firstcluster;
1315 ce->wrtdate = fatentp->wrtdate;
1316 ce->wrttime = fatentp->wrttime;
1317
1318 /* resolve queued user bindings */
1319 infop->fatfile.firstcluster = fatentp->firstcluster;
1320 infop->fatfile.dircluster = dircluster;
1321 infop->dcfile.idx = idx;
1322 infop->dcfile.serialnum = ce->serialnum;
1323 binding_resolve(infop);
1324 } /* end while */
1325
1326 close_stream_internal(streamp);
1327
1328 if (sabp->quit)
1329 return;
1330
1331 establish_frontier(compp->idx, FRONTIER_SETTLED);
1332
1333 /* second pass: "recurse!" */
1334 struct dircache_entry *ce = NULL;
1335
1336 while (1)
1337 {
1338 idx = compp->idx && compp > sabp->stack ? *compp->prevp : 0;
1339 if (idx)
1340 {
1341 ce = get_entry(idx);
1342 compp->prevp = &ce->next;
1343
1344 if (ce->frontier != FRONTIER_SETTLED)
1345 break;
1346 }
1347 else
1348 {
1349 /* directory completed or removed/deepest level */
1350 compp = ++sabp->top;
1351 if (compp >= sabp->stackend)
1352 return; /* scan completed/initial directory removed */
1353 }
1354 }
1355
1356 /* even if it got zoned from outside it is about to be scanned in
1357 its entirety and may be considered new again */
1358 ce->frontier = FRONTIER_NEW;
1359 downp = &ce->down;
1360
1361 /* set up info for next open
1362 * IF_MV: "volume" was set when scan began */
1363 infop->fatfile.firstcluster = ce->firstcluster;
1364 infop->fatfile.dircluster = dircluster;
1365 infop->fatfile.e.entry = ce->direntry;
1366 infop->fatfile.e.entries = ce->direntries;
1367 } /* end while */
1368}
1369
1370/**
1371 * scan and build the contents of a directory or volume root
1372 */
1373static void sab_process_dir(struct file_base_info *infop, bool issab)
1374{
1375 /* infop should have been fully opened meaning that all its parent
1376 directory information is filled in and intact; the binding information
1377 should also filled in beforehand */
1378
1379 /* allocate the stack right now to the max demand */
1380 struct dirsab
1381 {
1382 struct sab sab;
1383 struct sab_component stack[issab ? DIRCACHE_MAX_DEPTH : 1];
1384 } dirsab;
1385 struct sab *sabp = &dirsab.sab;
1386
1387 sabp->quit = false;
1388 sabp->stackend = &sabp->stack[ARRAYLEN(dirsab.stack)];
1389 sabp->top = sabp->stackend;
1390 sabp->info = *infop;
1391
1392 if (issab)
1393 DCRIVOL(infop)->sabp = sabp;
1394
1395 establish_frontier(infop->dcfile.idx, FRONTIER_NEW | FRONTIER_RENEW);
1396 sab_process_sub(sabp);
1397
1398 if (issab)
1399 DCRIVOL(infop)->sabp = NULL;
1400}
1401
1402/**
1403 * scan and build the entire tree for a volume
1404 */
1405static void sab_process_volume(struct dircache_volume *dcvolp)
1406{
1407 int rc;
1408
1409 int volume = IF_MV_VOL(dcvolp - dircache.dcvol);
1410 int idx = -volume - 1;
1411
1412 logf("dircache - building volume %d", volume);
1413
1414 /* gather everything sab_process_dir() needs in order to begin a scan */
1415 struct file_base_info info;
1416 rc = fat_open_rootdir(IF_MV(volume,) &info.fatfile);
1417 if (rc < 0)
1418 {
1419 /* probably not mounted */
1420 logf("SAB - no root %d: %d", volume, rc);
1421 establish_frontier(idx, FRONTIER_NEW);
1422 return;
311 } 1423 }
312 1424
313 /* first pass : read dir */ 1425 info.dcfile.idx = idx;
314 struct dircache_entry *first_ce = ce; 1426 info.dcfile.serialnum = dcvolp->serialnum;
315 1427 sab_process_dir(&info, true);
316 /* read through directory */ 1428}
317 while((rc = fat_getnext(sab.dir, sab.direntry)) >= 0 && sab.direntry->name[0]) 1429
1430/**
1431 * this function is the back end to the public API's like readdir()
1432 */
1433int dircache_readdir_dirent(struct filestr_base *stream,
1434 struct dirscan_info *scanp,
1435 struct dirent *entry)
1436{
1437 struct file_base_info *dirinfop = stream->infop;
1438
1439 if (!dirinfop->dcfile.serialnum)
1440 goto read_uncached; /* no parent cached => no entries cached */
1441
1442 struct dircache_volume *dcvolp = DCVOL(dirinfop);
1443
1444 int diridx = dirinfop->dcfile.idx;
1445 unsigned int frontier = diridx <= 0 ? dcvolp->frontier :
1446 get_entry(diridx)->frontier;
1447
1448 /* if not settled, just readthrough; no binding information is needed for
1449 this; if it becomes settled, we'll get scan->dcfile caught up and do
1450 subsequent reads with the cache */
1451 if (frontier != FRONTIER_SETTLED)
1452 goto read_uncached;
1453
1454 int idx = scanp->dcscan.idx;
1455 struct dircache_entry *ce;
1456
1457 unsigned int direntry = scanp->fatscan.entry;
1458 while (1)
318 { 1459 {
319 if(!strcmp(".", sab.direntry->name) || 1460 if (idx == 0 || direntry == FAT_RW_VAL) /* rewound? */
320 !strcmp("..", sab.direntry->name)) 1461 {
321 continue; 1462 idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down;
1463 break;
1464 }
322 1465
323 size_t size = strlen(sab.direntry->name) + 1; 1466 /* validate last entry scanned; it might have been replaced between
324 ce->d_name = (d_names_start -= size); 1467 calls or not there at all any more; if so, start the cache reader
325 ce->startcluster = sab.direntry->firstcluster; 1468 at the beginning and fast-forward to the correct point as indicated
326 ce->info.size = sab.direntry->filesize; 1469 by the FS scanner */
327 ce->info.attribute = sab.direntry->attr; 1470 ce = get_entry(idx);
328 ce->info.wrtdate = sab.direntry->wrtdate; 1471 if (ce && ce->serialnum == scanp->dcscan.serialnum)
329 ce->info.wrttime = sab.direntry->wrttime; 1472 {
1473 idx = ce->next;
1474 break;
1475 }
330 1476
331 strcpy(ce->d_name, sab.direntry->name); 1477 idx = 0;
332 dircache_size += size; 1478 }
333 1479
334 if(ce->info.attribute & FAT_ATTR_DIRECTORY) 1480 while (1)
335 dircache_gen_down(ce); 1481 {
336 1482 ce = get_entry(idx);
337 ce = dircache_gen_next(ce); 1483 if (!ce)
338 if(ce == NULL)
339 return -5;
340
341 /* When simulator is used, it's only safe to yield here. */
342 if(thread_enabled)
343 { 1484 {
344 /* Stop if we got an external signal. */ 1485 empty_dirent(entry);
345 if(check_event_queue()) 1486 scanp->fatscan.entries = 0;
346 return -6; 1487 return 0; /* end of dir */
347 yield();
348 } 1488 }
1489
1490 if (ce->direntry > direntry || direntry == FAT_RW_VAL)
1491 break; /* cache reader is caught up to FS scan */
1492
1493 idx = ce->next;
349 } 1494 }
350 1495
351 /* add "." and ".." */ 1496 /* basic dirent information */
352 ce->d_name = dot; 1497 entry_name_copy(entry->d_name, ce);
353 ce->info.attribute = FAT_ATTR_DIRECTORY; 1498 entry->info.attr = ce->attr;
354 ce->startcluster = startcluster; 1499 entry->info.size = (ce->attr & ATTR_DIRECTORY) ? 0 : ce->filesize;
355 ce->info.size = 0; 1500 entry->info.wrtdate = ce->wrtdate;
356 ce->down = first_ce; 1501 entry->info.wrttime = ce->wrttime;
357 1502
358 ce = dircache_gen_next(ce); 1503 /* FS scan information */
359 1504 scanp->fatscan.entry = ce->direntry;
360 ce->d_name = dotdot; 1505 scanp->fatscan.entries = ce->direntries;
361 ce->info.attribute = FAT_ATTR_DIRECTORY; 1506
362 ce->startcluster = (first_ce->up ? first_ce->up->startcluster : 0); 1507 /* dircache scan information */
363 ce->info.size = 0; 1508 scanp->dcscan.idx = idx;
364 ce->down = first_ce->up; 1509 scanp->dcscan.serialnum = ce->serialnum;
365 1510
366 /* second pass: recurse ! */ 1511 /* return whether this needs decoding */
367 ce = first_ce; 1512 int rc = ce->direntries == 1 ? 2 : 1;
368 1513
369 while(rc >= 0 && ce) 1514 yield();
370 {
371 if(ce->d_name != NULL && ce->down != NULL && strcmp(ce->d_name, ".")
372 && strcmp(ce->d_name, ".."))
373 rc = sab_process_dir(ce->startcluster, ce->down);
374
375 ce = ce->next;
376 }
377
378 return rc; 1515 return rc;
1516
1517read_uncached:
1518 dircache_dcfile_init(&scanp->dcscan);
1519 return uncached_readdir_dirent(stream, scanp, entry);
379} 1520}
380 1521
381/* used during the generation */ 1522/**
382static struct fat_dir sab_fat_dir; 1523 * rewind the directory scan cursor
1524 */
1525void dircache_rewinddir_dirent(struct dirscan_info *scanp)
1526{
1527 uncached_rewinddir_dirent(scanp);
1528 dircache_dcfile_init(&scanp->dcscan);
1529}
383 1530
384static int dircache_scan_and_build(IF_MV(int volume,) struct dircache_entry *ce) 1531/**
1532 * this function is the back end to file API internal scanning, which requires
1533 * much more detail about the directory entries; this is allowed to make
1534 * assumptions about cache state because the cache will not be altered during
1535 * the scan process; an additional important property of internal scanning is
1536 * that any available binding information is not ignored even when a scan
1537 * directory is frontier zoned.
1538 */
1539int dircache_readdir_internal(struct filestr_base *stream,
1540 struct file_base_info *infop,
1541 struct fat_direntry *fatent)
385{ 1542{
386 memset(ce, 0, sizeof(struct dircache_entry)); 1543 /* call with writer exclusion */
1544 struct file_base_info *dirinfop = stream->infop;
1545 struct dircache_volume *dcvolp = DCVOL(dirinfop);
387 1546
388#ifdef HAVE_MULTIVOLUME 1547 /* assume binding "not found" */
389 if (volume > 0) 1548 infop->dcfile.serialnum = 0;
390 { 1549
391 /* broken for 100+ volumes because the format string is too small 1550 /* is parent cached? if not, readthrough because nothing is here yet */
392 * and we use that for size calculation */ 1551 if (!dirinfop->dcfile.serialnum)
393 const size_t max_len = VOL_ENUM_POS + 3; 1552 return uncached_readdir_internal(stream, infop, fatent);
394 ce->d_name = (d_names_start -= max_len); 1553
395 snprintf(ce->d_name, max_len, VOL_NAMES, volume); 1554 int diridx = dirinfop->dcfile.idx;
396 dircache_size += max_len; 1555 unsigned int frontier = diridx < 0 ?
397 ce->info.attribute = FAT_ATTR_DIRECTORY | FAT_ATTR_VOLUME; 1556 dcvolp->frontier : get_entry(diridx)->frontier;
398 ce->info.size = 0; 1557
399 append_position = dircache_gen_next(ce); 1558 int idx = infop->dcfile.idx;
400 ce = dircache_gen_down(ce); 1559 if (idx == 0) /* rewound? */
1560 idx = diridx <= 0 ? dcvolp->root_down : get_entry(diridx)->down;
1561 else
1562 idx = get_entry(idx)->next;
1563
1564 struct dircache_entry *ce = get_entry(idx);
1565 if (frontier != FRONTIER_SETTLED)
1566 {
1567 /* the directory being read is reported to be incompletely cached;
1568 readthrough and if the entry exists, return it with its binding
1569 information; otherwise return the uncached read result while
1570 maintaining the last index */
1571 int rc = uncached_readdir_internal(stream, infop, fatent);
1572 if (rc <= 0 || !ce || ce->direntry > infop->fatfile.e.entry)
1573 return rc;
1574
1575 /* entry matches next one to read */
1576 }
1577 else if (!ce)
1578 {
1579 /* end of dir */
1580 fat_empty_fat_direntry(fatent);
1581 infop->fatfile.e.entries = 0;
1582 return 0;
401 } 1583 }
402#endif
403 1584
404 struct fat_direntry direntry; /* ditto */ 1585 /* FS entry information that we maintain */
405#ifdef HAVE_MULTIVOLUME 1586 entry_name_copy(fatent->name, ce);
406 sab.volume = volume; 1587 fatent->shortname[0] = '\0';
407#endif 1588 fatent->attr = ce->attr;
408 sab.dir = &sab_fat_dir; 1589 /* file code file scanning does not need time information */
409 sab.direntry = &direntry; 1590 fatent->filesize = (ce->attr & ATTR_DIRECTORY) ? 0 : ce->filesize;
410 1591 fatent->firstcluster = ce->firstcluster;
411 return sab_process_dir(0, ce); 1592
1593 /* FS entry directory information */
1594 infop->fatfile.e.entry = ce->direntry;
1595 infop->fatfile.e.entries = ce->direntries;
1596
1597 /* dircache file binding information */
1598 infop->dcfile.idx = idx;
1599 infop->dcfile.serialnum = ce->serialnum;
1600
1601 /* return whether this needs decoding */
1602 int rc = ce->direntries == 1 ? 2 : 1;
1603
1604 if (frontier == FRONTIER_SETTLED)
1605 {
1606 static long next_yield;
1607 if (TIME_AFTER(current_tick, next_yield))
1608 {
1609 yield();
1610 next_yield = current_tick + HZ/50;
1611 }
1612 }
1613
1614 return rc;
412} 1615}
413#elif (CONFIG_PLATFORM & PLATFORM_HOSTED) /* PLATFORM_HOSTED */ 1616
1617/**
1618 * rewind the scan position for an internal scan
1619 */
1620void dircache_rewinddir_internal(struct file_base_info *infop)
1621{
1622 uncached_rewinddir_internal(infop);
1623 dircache_dcfile_init(&infop->dcfile);
1624}
1625
1626#else /* !DIRCACHE_NATIVE (for all others) */
1627
1628#####################
1629/* we require access to the host functions */
1630#undef opendir
1631#undef readdir
1632#undef closedir
1633#undef rewinddir
1634
414static char sab_path[MAX_PATH]; 1635static char sab_path[MAX_PATH];
415 1636
416static int sab_process_dir(struct dircache_entry *ce) 1637static int sab_process_dir(struct dircache_entry *ce)
417{ 1638{
418 struct dirent_uncached *entry; 1639 struct dirent_uncached *entry;
419 struct dircache_entry *first_ce = ce; 1640 struct dircache_entry *first_ce = ce;
420 DIR_UNCACHED *dir = opendir_uncached(sab_path); 1641 DIR *dir = opendir(sab_path);
421 if(dir == NULL) 1642 if(dir == NULL)
422 { 1643 {
423 logf("Failed to opendir_uncached(%s)", sab_path); 1644 logf("Failed to opendir_uncached(%s)", sab_path);
424 return -1; 1645 return -1;
425 } 1646 }
426 1647
427 while((entry = readdir_uncached(dir))) 1648 while (1)
428 { 1649 {
429 if(!strcmp(".", entry->d_name) || 1650 if (!(entry = readdir(dir)))
430 !strcmp("..", entry->d_name)) 1651 break;
1652
1653 if (IS_DOTDIR_NAME(entry->d_name))
1654 {
1655 /* add "." and ".." */
1656 ce->info.attribute = ATTR_DIRECTORY;
1657 ce->info.size = 0;
1658 ce->down = entry->d_name[1] == '\0' ? first_ce : first_ce->up;
1659 strcpy(ce->dot_d_name, entry->d_name);
431 continue; 1660 continue;
1661 }
432 1662
433 size_t size = strlen(entry->d_name) + 1; 1663 size_t size = strlen(entry->d_name) + 1;
434 ce->d_name = (d_names_start -= size); 1664 ce->d_name = (d_names_start -= size);
@@ -436,10 +1666,10 @@ static int sab_process_dir(struct dircache_entry *ce)
436 1666
437 strcpy(ce->d_name, entry->d_name); 1667 strcpy(ce->d_name, entry->d_name);
438 dircache_size += size; 1668 dircache_size += size;
439 1669
440 if(entry->info.attribute & ATTR_DIRECTORY) 1670 if(entry->info.attribute & ATTR_DIRECTORY)
441 { 1671 {
442 dircache_gen_down(ce); 1672 dircache_gen_down(ce, ce);
443 if(ce->down == NULL) 1673 if(ce->down == NULL)
444 { 1674 {
445 closedir_uncached(dir); 1675 closedir_uncached(dir);
@@ -450,1189 +1680,1504 @@ static int sab_process_dir(struct dircache_entry *ce)
450 /* append entry */ 1680 /* append entry */
451 strlcpy(&sab_path[pathpos], "/", sizeof(sab_path) - pathpos); 1681 strlcpy(&sab_path[pathpos], "/", sizeof(sab_path) - pathpos);
452 strlcpy(&sab_path[pathpos+1], entry->d_name, sizeof(sab_path) - pathpos - 1); 1682 strlcpy(&sab_path[pathpos+1], entry->d_name, sizeof(sab_path) - pathpos - 1);
453 1683
454 int rc = sab_process_dir(ce->down); 1684 int rc = sab_process_dir(ce->down);
455 /* restore path */ 1685 /* restore path */
456 sab_path[pathpos] = '\0'; 1686 sab_path[pathpos] = '\0';
457 1687
458 if(rc < 0) 1688 if(rc < 0)
459 { 1689 {
460 closedir_uncached(dir); 1690 closedir_uncached(dir);
461 return rc; 1691 return rc;
462 } 1692 }
463 } 1693 }
464 1694
465 ce = dircache_gen_next(ce); 1695 ce = dircache_gen_entry(ce);
466 if(ce == NULL) 1696 if (ce == NULL)
467 return -5; 1697 return -5;
468 1698
469 /* When simulator is used, it's only safe to yield here. */ 1699 yield();
470 if(thread_enabled)
471 {
472 /* Stop if we got an external signal. */
473 if(check_event_queue())
474 return -1;
475 yield();
476 }
477 } 1700 }
478 1701
479 /* add "." and ".." */
480 ce->d_name = dot;
481 ce->info.attribute = ATTR_DIRECTORY;
482 ce->info.size = 0;
483 ce->down = first_ce;
484
485 ce = dircache_gen_next(ce);
486
487 ce->d_name = dotdot;
488 ce->info.attribute = ATTR_DIRECTORY;
489 ce->info.size = 0;
490 ce->down = first_ce->up;
491
492 closedir_uncached(dir); 1702 closedir_uncached(dir);
493 return 0; 1703 return 1;
494} 1704}
495 1705
496static int dircache_scan_and_build(IF_MV(int volume,) struct dircache_entry *ce) 1706static int sab_process_volume(IF_MV(int volume,) struct dircache_entry *ce)
497{ 1707{
498 #ifdef HAVE_MULTIVOLUME
499 (void) volume;
500 #endif
501 memset(ce, 0, sizeof(struct dircache_entry)); 1708 memset(ce, 0, sizeof(struct dircache_entry));
502
503 strlcpy(sab_path, "/", sizeof sab_path); 1709 strlcpy(sab_path, "/", sizeof sab_path);
504 return sab_process_dir(ce); 1710 return sab_process_dir(ce);
505} 1711}
506#endif /* PLATFORM_NATIVE */
507 1712
508/** 1713int dircache_readdir_r(struct dircache_dirscan *dir, struct dirent *result)
509 * Internal function to get a pointer to dircache_entry for a given filename. 1714{
510 * path: Absolute path to a file or directory (see comment) 1715 if (dircache_state != DIRCACHE_READY)
511 * go_down: Returns the first entry of the directory given by the path (see comment) 1716 return readdir_r(dir->###########3, result, &result);
512 *
513 * As a a special case, accept path="" as an alias for "/".
514 * Also if the path omits the first '/', it will be accepted.
515 *
516 * * If get_down=true:
517 * If path="/", the returned entry is the first of root directory (ie dircache_root)
518 * Otherwise, if 'entry' is the returned value when get_down=false,
519 * the functions returns entry->down (which can be NULL)
520 *
521 * * If get_down=false:
522 * If path="/chunk_1/chunk_2/.../chunk_n" then this functions returns the entry
523 * root_entry()->chunk_1->chunk_2->...->chunk_(n-1)
524 * Which means that
525 * dircache_get_entry(path)->d_name == chunk_n
526 *
527 * If path="/", the returned entry is NULL.
528 * If the entry doesn't exist, return NULL
529 *
530 * NOTE: this functions silently handles double '/'
531 */
532static struct dircache_entry* dircache_get_entry(const char *path, bool go_down)
533{
534 char namecopy[MAX_PATH];
535 char* part;
536 char* end;
537
538 bool at_root = true;
539 struct dircache_entry *cache_entry = dircache_root;
540
541 strlcpy(namecopy, path, sizeof(namecopy));
542
543 for(part = strtok_r(namecopy, "/", &end); part; part = strtok_r(NULL, "/", &end))
544 {
545 /* If request another chunk, the current entry has to be directory
546 * and so cache_entry->down has to be non-NULL/
547 * Special case of root because it's already the first entry of the root directory
548 *
549 * NOTE: this is safe even if cache_entry->down is NULL */
550 if(!at_root)
551 cache_entry = cache_entry->down;
552 else
553 at_root = false;
554
555 /* scan dir for name */
556 while(cache_entry != NULL)
557 {
558 /* skip unused entries */
559 if(cache_entry->d_name == NULL)
560 {
561 cache_entry = cache_entry->next;
562 continue;
563 }
564 /* compare names */
565 if(!strcasecmp(part, cache_entry->d_name))
566 break;
567 /* go to next entry */
568 cache_entry = cache_entry->next;
569 }
570
571 /* handle not found case */
572 if(cache_entry == NULL)
573 return NULL;
574 }
575 1717
576 /* NOTE: here cache_entry!=NULL so taking ->down is safe */ 1718 bool first = dir->dcinfo.scanidx == REWIND_INDEX;
577 if(go_down) 1719 struct dircache_entry *ce = get_entry(first ? dir->dcinfo.index :
578 return at_root ? cache_entry : cache_entry->down; 1720 dir->dcinfo.scanidx);
579 else
580 return at_root ? NULL : cache_entry;
581}
582 1721
583#ifdef HAVE_EEPROM_SETTINGS 1722 ce = first ? ce->down : ce->next;
584 1723
585#define DIRCACHE_MAGIC 0x00d0c0a1 1724 if (ce == NULL)
586struct dircache_maindata { 1725 return 0;
587 long magic; 1726
588 long size; 1727 dir->scanidx = ce - dircache_root;
589 long entry_count; 1728
590 long appflags; 1729 strlcpy(result->d_name, ce->d_name, sizeof (result->d_name));
591 struct dircache_entry *root_entry; 1730 result->info = ce->dirinfo;
592 char *d_names_start; 1731
593}; 1732 return 1;
1733}
1734
1735#endif /* DIRCACHE_* */
594 1736
595/** 1737/**
596 * Function to load the internal cache structure from disk to initialize 1738 * reset the cache for the specified volume
597 * the dircache really fast and little disk access.
598 */ 1739 */
599int dircache_load(void) 1740static void reset_volume(IF_MV_NONVOID(int volume))
600{ 1741{
601 struct dircache_maindata maindata; 1742 FOR_EACH_VOLUME(volume, i)
602 ssize_t bytes_read;
603 int fd;
604
605 if (dircache_initialized)
606 return -1;
607
608 logf("Loading directory cache");
609 dircache_size = 0;
610
611 fd = open_dircache_file(O_RDONLY, 0);
612 if (fd < 0)
613 return -2;
614
615 bytes_read = read(fd, &maindata, sizeof(struct dircache_maindata));
616 if (bytes_read != sizeof(struct dircache_maindata)
617 || maindata.magic != DIRCACHE_MAGIC || maindata.size <= 0)
618 { 1743 {
619 logf("Dircache file header error"); 1744 struct dircache_volume *dcvolp = DCVOL(i);
620 close(fd); 1745
621 remove_dircache_file(); 1746 if (dcvolp->status == DIRCACHE_IDLE)
622 return -3; 1747 continue; /* idle => nothing happening there */
623 } 1748
624 1749 struct dircache_runinfo_volume *dcrivolp = DCRIVOL(i);
625 allocated_size = maindata.size + DIRCACHE_RESERVE; 1750
626 dircache_handle = core_alloc_ex("dircache", allocated_size, &ops); 1751 /* stop any scan and build on this one */
627 /* block movement during upcoming I/O */ 1752 if (dcrivolp->sabp)
628 dont_move = true; 1753 dcrivolp->sabp->quit = true;
629 dircache_root = core_get_data(dircache_handle); 1754
630 ALIGN_BUFFER(dircache_root, allocated_size, sizeof(struct dircache_entry*)); 1755 #ifdef HAVE_MULTIVOLUME
631 entry_count = maindata.entry_count; 1756 /* if this call is for all volumes, subsequent code will just reset
632 appflags = maindata.appflags; 1757 the cache memory usage and the freeing of individual entries may
633 1758 be skipped */
634 /* read the dircache file into memory, 1759 if (volume >= 0)
635 * start with the struct dircache_entries */ 1760 free_subentries(NULL, &dcvolp->root_down);
636 ssize_t bytes_to_read = entry_count*sizeof(struct dircache_entry); 1761 else
637 bytes_read = read(fd, dircache_root, bytes_to_read); 1762 #endif
638 1763 binding_dissolve_volume(dcrivolp);
639 if (bytes_read != bytes_to_read) 1764
640 { 1765 /* set it back to unscanned */
641 logf("Dircache read failed #1"); 1766 dcvolp->status = DIRCACHE_IDLE;
642 return -6; 1767 dcvolp->frontier = FRONTIER_NEW;
643 } 1768 dcvolp->root_down = 0;
644 1769 dcvolp->build_ticks = 0;
645 /* continue with the d_names. Fix up pointers to them if needed */ 1770 dcvolp->serialnum = 0;
646 bytes_to_read = maindata.size - bytes_to_read;
647 d_names_start = (char*)dircache_root + allocated_size - bytes_to_read;
648 bytes_read = read(fd, d_names_start, bytes_to_read);
649 close(fd);
650 remove_dircache_file();
651 if (bytes_read != bytes_to_read)
652 {
653 logf("Dircache read failed #2");
654 return -7;
655 } 1771 }
1772}
656 1773
657 d_names_end = d_names_start + bytes_read; 1774/**
658 dot = d_names_end - sizeof("."); 1775 * reset the entire cache state for all volumes
659 dotdot = dot - sizeof(".."); 1776 */
1777static void reset_cache(void)
1778{
1779 if (!dircache_runinfo.handle)
1780 return; /* no buffer => nothing cached */
1781
1782 /* blast all the volumes */
1783 reset_volume(IF_MV(-1));
1784
1785#ifdef DIRCACHE_DUMPSTER
1786 dumpster_clean_buffer(dircache_runinfo.p + ENTRYSIZE,
1787 dircache_runinfo.bufsize);
1788#endif /* DIRCACHE_DUMPSTER */
1789
1790 /* reset the memory */
1791 dircache.free_list = 0;
1792 dircache.size = 0;
1793 dircache.sizeused = 0;
1794 dircache.numentries = 0;
1795 dircache.names = dircache_runinfo.bufsize + ENTRYSIZE;
1796 dircache.sizenames = 0;
1797 dircache.namesfree = 0;
1798 dircache.nextnamefree = 0;
1799 *get_name(dircache.names - 1) = 0;
1800 /* dircache.last_serialnum stays */
1801 /* dircache.reserve_used stays */
1802 /* dircache.last_size stays */
1803}
660 1804
661 /* d_names are in reverse order, so the last entry points to the first string */ 1805/**
662 ptrdiff_t offset_d_names = maindata.d_names_start - d_names_start; 1806 * checks each "idle" volume and builds it
663 ptrdiff_t offset_entries = maindata.root_entry - dircache_root; 1807 */
664 offset_entries *= sizeof(struct dircache_entry); /* make it bytes */ 1808static void build_volumes(void)
1809{
1810 buffer_lock();
665 1811
666 /* offset_entries is less likely to differ, so check if it's 0 in the loop 1812 for (int i = 0; i < NUM_VOLUMES; i++)
667 * offset_d_names however is almost always non-zero, since dircache_save()
668 * creates a file which causes the reserve buffer to be used. since
669 * we allocate a new, empty DIRCACHE_RESERVE here, the strings are
670 * farther behind */
671 if (offset_entries != 0 || offset_d_names != 0)
672 { 1813 {
673 for(unsigned i = 0; i < entry_count; i++) 1814 /* this does reader locking but we already own that */
674 { 1815 if (!volume_ismounted(IF_MV(i)))
675 if (dircache_root[i].d_name) 1816 continue;
676 dircache_root[i].d_name -= offset_d_names;
677 1817
678 if (offset_entries == 0) 1818 struct dircache_volume *dcvolp = DCVOL(i);
679 continue; 1819
680 if (dircache_root[i].next_char) 1820 /* can't already be "scanning" because that's us; doesn't retry
681 dircache_root[i].next_char -= offset_entries; 1821 "ready" volumes */
682 if (dircache_root[i].up_char) 1822 if (dcvolp->status == DIRCACHE_READY)
683 dircache_root[i].up_char -= offset_entries; 1823 continue;
684 if (dircache_root[i].down_char) 1824
685 dircache_root[i].down_char -= offset_entries; 1825 /* measure how long it takes to build the cache for each volume */
686 } 1826 if (!dcvolp->serialnum)
1827 dcvolp->serialnum = next_serialnum();
1828
1829 dcvolp->status = DIRCACHE_SCANNING;
1830 dcvolp->start_tick = current_tick;
1831
1832 sab_process_volume(dcvolp);
1833
1834 if (dircache_runinfo.suspended)
1835 break;
1836
1837 /* whatever happened, it's ready unless reset */
1838 dcvolp->build_ticks = current_tick - dcvolp->start_tick;
1839 dcvolp->status = DIRCACHE_READY;
687 } 1840 }
688 1841
689 /* Cache successfully loaded. */ 1842 size_t reserve_used = reserve_buf_used();
690 dircache_size = maindata.size; 1843 if (reserve_used > dircache.reserve_used)
691 reserve_used = 0; 1844 dircache.reserve_used = reserve_used;
692 logf("Done, %ld KiB used", dircache_size / 1024); 1845
693 dircache_initialized = true; 1846 if (DIRCACHE_STUFFED(reserve_used))
694 memset(fd_bindings, 0, sizeof(fd_bindings)); 1847 dircache.last_size = 0; /* reset */
695 dont_move = false; 1848 else if (dircache.size > dircache.last_size ||
1849 dircache.last_size - dircache.size > DIRCACHE_RESERVE)
1850 dircache.last_size = dircache.size;
1851
1852 logf("Done, %ld KiB used", dircache.size / 1024);
696 1853
697 return 0; 1854 buffer_unlock();
698} 1855}
699 1856
700/** 1857/**
701 * Function to save the internal cache stucture to disk for fast loading 1858 * allocate buffer and return whether or not a synchronous build should take
702 * on boot. 1859 * place; if 'realloced' is NULL, it's just a query about what will happen
703 */ 1860 */
704int dircache_save(void) 1861static int prepare_build(bool *realloced)
705{ 1862{
706 struct dircache_maindata maindata; 1863 /* called holding dircache lock */
707 int fd; 1864 size_t size = dircache.last_size;
708 unsigned long bytes_written;
709 1865
710 remove_dircache_file(); 1866#ifdef HAVE_EEPROM_SETTINGS
711 1867 if (realloced)
712 if (!dircache_initialized) 1868 {
713 return -1; 1869 dircache_unlock();
1870 remove_dircache_file();
1871 dircache_lock();
714 1872
715 logf("Saving directory cache"); 1873 if (dircache_runinfo.suspended)
716 dont_move = true; 1874 return -1;
717 fd = open_dircache_file(O_WRONLY | O_CREAT | O_TRUNC, 0666); 1875 }
1876#endif /* HAVE_EEPROM_SETTINGS */
1877
1878 bool stuffed = DIRCACHE_STUFFED(dircache.reserve_used);
1879 if (dircache_runinfo.bufsize > size && !stuffed)
1880 {
1881 if (realloced)
1882 *realloced = false;
718 1883
719 maindata.magic = DIRCACHE_MAGIC; 1884 return 0; /* start a transparent rebuild */
720 maindata.size = dircache_size; 1885 }
721 maindata.root_entry = dircache_root;
722 maindata.d_names_start = d_names_start;
723 maindata.entry_count = entry_count;
724 maindata.appflags = appflags;
725 1886
726 /* Save the info structure */ 1887 int syncbuild = size > 0 && !stuffed ? 0 : 1;
727 bytes_written = write(fd, &maindata, sizeof(struct dircache_maindata)); 1888
728 if (bytes_written != sizeof(struct dircache_maindata)) 1889 if (!realloced)
1890 return syncbuild;
1891
1892 if (syncbuild)
729 { 1893 {
730 close(fd); 1894 /* start a non-transparent rebuild */
731 logf("dircache: write failed #1"); 1895 /* we'll use the entire audiobuf to allocate the dircache */
732 return -2; 1896 size = audio_buffer_available() + dircache_runinfo.bufsize;
1897 /* try to allocate at least the min and no more than the limit */
1898 size = MAX(DIRCACHE_MIN, MIN(size, DIRCACHE_LIMIT));
733 } 1899 }
1900 else
1901 {
1902 /* start a transparent rebuild */
1903 size = MAX(size, DIRCACHE_RESERVE) + DIRCACHE_RESERVE*2;
1904 }
1905
1906 *realloced = true;
1907 reset_cache();
734 1908
735 /* Dump whole directory cache to disk 1909 buffer_lock();
736 * start by writing the dircache_entries */ 1910
737 size_t bytes_to_write = entry_count*sizeof(struct dircache_entry); 1911 int handle = reset_buffer();
738 bytes_written = write(fd, dircache_root, bytes_to_write); 1912 dircache_unlock();
739 if (bytes_written != bytes_to_write) 1913
1914 if (handle > 0)
1915 core_free(handle);
1916
1917 handle = alloc_cache(size);
1918
1919 dircache_lock();
1920
1921 if (dircache_runinfo.suspended && handle > 0)
740 { 1922 {
741 logf("dircache: write failed #2"); 1923 /* if we got suspended, don't keep this huge buffer around */
742 return -3; 1924 dircache_unlock();
1925 core_free(handle);
1926 handle = 0;
1927 dircache_lock();
743 } 1928 }
744 1929
745 /* continue with the d_names */ 1930 if (handle <= 0)
746 bytes_to_write = d_names_end - d_names_start;
747 bytes_written = write(fd, d_names_start, bytes_to_write);
748 close(fd);
749 if (bytes_written != bytes_to_write)
750 { 1931 {
751 logf("dircache: write failed #3"); 1932 buffer_unlock();
752 return -4; 1933 return -1;
753 } 1934 }
754 1935
755 dont_move = false; 1936 set_buffer(handle, size);
756 return 0; 1937 buffer_unlock();
1938
1939 return syncbuild;
757} 1940}
758#endif /* HAVE_EEPROM_SETTINGS */
759 1941
760/** 1942/**
761 * Internal function which scans the disk and creates the dircache structure. 1943 * compact the dircache buffer after a successful full build
762 */ 1944 */
763static int dircache_do_rebuild(void) 1945static void compact_cache(void)
764{ 1946{
765 struct dircache_entry* root_entry; 1947 /* called holding dircache lock */
766 unsigned int start_tick; 1948 if (dircache_runinfo.suspended)
767 int i; 1949 return;
768
769 /* Measure how long it takes build the cache. */
770 start_tick = current_tick;
771 dircache_initializing = true;
772 appflags = 0;
773 1950
774 /* reset dircache and alloc root entry */ 1951 void *p = dircache_runinfo.p + ENTRYSIZE;
775 entry_count = 0; 1952 size_t leadsize = dircache.numentries * ENTRYSIZE + DIRCACHE_RESERVE;
776 root_entry = allocate_entry();
777 dont_move = true;
778 1953
779#ifdef HAVE_MULTIVOLUME 1954 void *dst = p + leadsize;
780 append_position = root_entry; 1955 void *src = get_name(dircache.names - 1);
1956 if (dst >= src)
1957 return; /* cache got bigger than expected; never mind that */
1958
1959 /* slide the names up in memory */
1960 memmove(dst, src, dircache.sizenames + 2);
781 1961
782 for (i = NUM_VOLUMES; i >= 0; i--) 1962 /* fix up name indexes */
1963 ptrdiff_t offset = dst - src;
1964
1965 FOR_EACH_CACHE_ENTRY(ce)
783 { 1966 {
784 if (fat_ismounted(i)) 1967 if (!ce->tinyname)
785 { 1968 ce->name += offset;
786#endif
787 cpu_boost(true);
788#ifdef HAVE_MULTIVOLUME
789 if (dircache_scan_and_build(IF_MV(i,) append_position) < 0)
790#else
791 if (dircache_scan_and_build(IF_MV(0,) root_entry) < 0)
792#endif /* HAVE_MULTIVOLUME */
793 {
794 logf("dircache_scan_and_build failed");
795 cpu_boost(false);
796 dircache_size = 0;
797 dircache_initializing = false;
798 dont_move = false;
799 return -2;
800 }
801 cpu_boost(false);
802#ifdef HAVE_MULTIVOLUME
803 }
804 } 1969 }
805#endif
806 1970
807 logf("Done, %ld KiB used", dircache_size / 1024); 1971 dircache.names += offset;
808
809 dircache_initialized = true;
810 dircache_initializing = false;
811 cache_build_ticks = current_tick - start_tick;
812
813 /* Initialized fd bindings. */
814 memset(fd_bindings, 0, sizeof(fd_bindings));
815 for (i = 0; i < fdbind_idx; i++)
816 dircache_bind(fdbind_cache[i].fd, fdbind_cache[i].path);
817 fdbind_idx = 0;
818
819 if (thread_enabled)
820 {
821 if (allocated_size - dircache_size < DIRCACHE_RESERVE)
822 reserve_used = DIRCACHE_RESERVE - (allocated_size - dircache_size);
823 }
824
825 dont_move = false;
826 return 1;
827}
828 1972
829/* 1973 /* assumes beelzelib doesn't do things like calling callbacks or changing
830 * Free all associated resources, if any */ 1974 the pointer as a result of the shrink operation; it doesn't as of now
831static void dircache_free(void) 1975 but if it ever does that may very well cause deadlock problems since
832{ 1976 we're holding filesystem locks */
833 if (dircache_handle > 0) 1977 size_t newsize = leadsize + dircache.sizenames + 1;
834 dircache_handle = core_free(dircache_handle); 1978 core_shrink(dircache_runinfo.handle, p, newsize + 1);
835 dircache_size = allocated_size = 0; 1979 dircache_runinfo.bufsize = newsize;
1980 dircache.reserve_used = 0;
836} 1981}
837 1982
838/** 1983/**
839 * Internal thread that controls transparent cache building. 1984 * internal thread that controls cache building; exits when no more requests
1985 * are pending or the cache is suspended
840 */ 1986 */
841static void dircache_thread(void) 1987static void dircache_thread(void)
842{ 1988{
843 struct queue_event ev; 1989 struct queue_event ev;
844 1990
1991 /* calls made within the loop reopen the lock */
1992 dircache_lock();
1993
845 while (1) 1994 while (1)
846 { 1995 {
847 queue_wait(&dircache_queue, &ev); 1996 queue_wait_w_tmo(&dircache_queue, &ev, 0);
848 1997 if (ev.id == SYS_TIMEOUT || dircache_runinfo.suspended)
849 switch (ev.id)
850 { 1998 {
851#ifdef HAVE_HOTSWAP 1999 /* nothing left to do/suspended */
852 case SYS_FS_CHANGED: 2000 if (dircache_runinfo.suspended)
853 if (!dircache_initialized) 2001 clear_dircache_queue();
854 break; 2002 dircache_runinfo.thread_done = true;
855 dircache_initialized = false; 2003 break;
856#endif
857 case DIRCACHE_BUILD:
858 thread_enabled = true;
859 if (dircache_do_rebuild() < 0)
860 dircache_free();
861 thread_enabled = false;
862 break ;
863
864 case DIRCACHE_STOP:
865 logf("Stopped the rebuilding.");
866 dircache_initialized = false;
867 break ;
868
869 case SYS_USB_CONNECTED:
870 usb_acknowledge(SYS_USB_CONNECTED_ACK);
871 usb_wait_for_disconnect(&dircache_queue);
872 break ;
873 } 2004 }
874 }
875}
876 2005
877static void generate_dot_d_names(void) 2006 /* background-only builds are not allowed if a synchronous build is
878{ 2007 required first; test what needs to be done and if it checks out,
879 dot = (d_names_start -= sizeof(".")); 2008 do it for real below */
880 dotdot = (d_names_start -= sizeof("..")); 2009 int *rcp = (int *)ev.data;
881 dircache_size += sizeof(".") + sizeof(".."); 2010
882 strcpy(dot, "."); 2011 if (!rcp && prepare_build(NULL) > 0)
883 strcpy(dotdot, ".."); 2012 continue;
2013
2014 bool realloced;
2015 int rc = prepare_build(&realloced);
2016 if (rcp)
2017 *rcp = rc;
2018
2019 if (rc < 0)
2020 continue;
2021
2022 trigger_cpu_boost();
2023 build_volumes();
2024
2025 /* if it was reallocated, compact it */
2026 if (realloced)
2027 compact_cache();
2028 }
2029
2030 dircache_unlock();
884} 2031}
885 2032
886/** 2033/**
887 * Start scanning the disk to build the dircache. 2034 * post a scan and build message to the thread, starting it if required
888 * Either transparent or non-transparent build method is used.
889 */ 2035 */
890int dircache_build(int last_size) 2036static bool dircache_thread_post(int volatile *rcp)
891{ 2037{
892 if (dircache_initialized || thread_enabled) 2038 if (dircache_runinfo.thread_done)
893 return -3; 2039 {
2040 /* mustn't recreate until it exits so that the stack isn't reused */
2041 thread_wait(dircache_runinfo.thread_id);
2042 dircache_runinfo.thread_done = false;
2043 dircache_runinfo.thread_id = create_thread(
2044 dircache_thread, dircache_stack, sizeof (dircache_stack), 0,
2045 dircache_thread_name IF_PRIO(, PRIORITY_BACKGROUND)
2046 IF_COP(, CPU));
2047 }
894 2048
895 logf("Building directory cache"); 2049 bool started = dircache_runinfo.thread_id != 0;
896#ifdef HAVE_EEPROM_SETTINGS
897 remove_dircache_file();
898#endif
899 2050
900 /* Background build, dircache has been previously allocated and */ 2051 if (started)
901 if (allocated_size > MAX(last_size, 0)) 2052 queue_post(&dircache_queue, DCM_BUILD, (intptr_t)rcp);
902 {
903 d_names_start = d_names_end;
904 dircache_size = 0;
905 reserve_used = 0;
906 thread_enabled = true;
907 dircache_initializing = true;
908 generate_dot_d_names();
909
910 queue_post(&dircache_queue, DIRCACHE_BUILD, 0);
911 return 2;
912 }
913
914 /* start by freeing, as we possibly re-allocate */
915 dircache_free();
916
917 if (last_size > DIRCACHE_RESERVE && last_size < DIRCACHE_LIMIT )
918 {
919 allocated_size = last_size + DIRCACHE_RESERVE;
920 dircache_handle = core_alloc_ex("dircache", allocated_size, &ops);
921 dircache_root = core_get_data(dircache_handle);
922 ALIGN_BUFFER(dircache_root, allocated_size, sizeof(struct dircache_entry*));
923 d_names_start = d_names_end = ((char*)dircache_root)+allocated_size-1;
924 dircache_size = 0;
925 thread_enabled = true;
926 generate_dot_d_names();
927
928 /* Start a transparent rebuild. */
929 queue_post(&dircache_queue, DIRCACHE_BUILD, 0);
930 return 3;
931 }
932
933 /* We'll use the entire audiobuf to allocate the dircache
934 * struct dircache_entrys are allocated from the beginning
935 * and their corresponding d_name from the end
936 * after generation the buffer will be compacted with DIRCACHE_RESERVE
937 * free bytes inbetween */
938 size_t available = audio_buffer_available();
939 /* try to allocate at least 1MB, the more the better */
940 if (available < 1<<20) available = 1<<20;
941 if (available > DIRCACHE_LIMIT) available = DIRCACHE_LIMIT;
942
943 dircache_handle = core_alloc_ex("dircache", available, &ops);
944 if (dircache_handle <= 0)
945 return -1; /* that was not successful, should try rebooting */
946 char* buf = core_get_data(dircache_handle);
947 dircache_root = (struct dircache_entry*)ALIGN_UP(buf,
948 sizeof(struct dircache_entry*));
949 d_names_start = d_names_end = buf + available - 1;
950 dircache_size = 0;
951 generate_dot_d_names();
952
953 /* Start a non-transparent rebuild. */
954 int res = dircache_do_rebuild();
955 if (res < 0)
956 goto fail;
957
958 /* now compact the dircache buffer */
959 char* dst = ((char*)&dircache_root[entry_count] + DIRCACHE_RESERVE);
960 ptrdiff_t offset = d_names_start - dst;
961 if (offset <= 0) /* something went wrong */
962 {
963 res = -1;
964 goto fail;
965 }
966
967 /* memmove d_names down, there's a possibility of overlap
968 * equivaent to dircache_size - entry_count*sizeof(struct dircache_entry) */
969 ptrdiff_t size_to_move = d_names_end - d_names_start;
970 memmove(dst, d_names_start, size_to_move);
971
972 /* fix up pointers to the d_names */
973 for(unsigned i = 0; i < entry_count; i++)
974 dircache_root[i].d_name -= offset;
975
976 d_names_start -= offset;
977 d_names_end -= offset;
978 dot -= offset;
979 dotdot -= offset;
980
981 /* equivalent to dircache_size + DIRCACHE_RESERVE + align */
982 allocated_size = (d_names_end - buf);
983 reserve_used = 0;
984
985 core_shrink(dircache_handle, dircache_root, allocated_size);
986 return res;
987fail:
988 dircache_disable();
989 return res;
990}
991
992/**
993 * Main initialization function that must be called before any other
994 * operations within the dircache.
995 */
996void dircache_init(void)
997{
998 int i;
999 int thread_id __attribute__((unused));
1000
1001 dircache_initialized = false;
1002 dircache_initializing = false;
1003
1004 memset(opendirs, 0, sizeof(opendirs));
1005 for (i = 0; i < MAX_OPEN_DIRS; i++)
1006 {
1007 opendirs[i].theent.d_name = opendir_dnames[i];
1008 }
1009
1010 queue_init(&dircache_queue, true);
1011 thread_id = create_thread(dircache_thread, dircache_stack,
1012 sizeof(dircache_stack), 0, dircache_thread_name
1013 IF_PRIO(, PRIORITY_BACKGROUND)
1014 IF_COP(, CPU));
1015#ifdef HAVE_IO_PRIORITY
1016 thread_set_io_priority(thread_id,IO_PRIORITY_BACKGROUND);
1017#endif
1018 2053
2054 return started;
1019} 2055}
1020 2056
1021/** 2057/**
1022 * Returns true if dircache has been initialized and is ready to be used. 2058 * wait for the dircache thread to finish building; intended for waiting for a
2059 * non-transparent build to finish when dircache_resume() returns > 0
1023 */ 2060 */
1024bool dircache_is_enabled(void) 2061void dircache_wait(void)
1025{ 2062{
1026 return dircache_initialized; 2063 thread_wait(dircache_runinfo.thread_id);
1027} 2064}
1028 2065
1029/** 2066/**
1030 * Returns true if dircache is being initialized. 2067 * call after mounting a volume or all volumes
1031 */ 2068 */
1032bool dircache_is_initializing(void) 2069void dircache_mount(void)
1033{ 2070{
1034 return dircache_initializing || thread_enabled; 2071 /* call with writer exclusion */
2072 if (dircache_runinfo.suspended)
2073 return;
2074
2075 dircache_thread_post(NULL);
1035} 2076}
1036 2077
1037/** 2078/**
1038 * Set application flags used to determine if dircache is still intact. 2079 * call after unmounting a volume; specifying < 0 for all or >= 0 for the
2080 * specific one
1039 */ 2081 */
1040void dircache_set_appflag(long mask) 2082void dircache_unmount(IF_MV_NONVOID(int volume))
1041{ 2083{
1042 appflags |= mask; 2084 /* call with writer exclusion */
2085 if (dircache_runinfo.suspended)
2086 return;
2087
2088#ifdef HAVE_MULTIVOLUME
2089 if (volume >= 0)
2090 reset_volume(volume);
2091 else
2092#endif /* HAVE_MULTIVOLUME */
2093 reset_cache();
2094}
2095
2096/* backend to dircache_suspend() and dircache_disable() */
2097static void dircache_suspend_internal(bool freeit)
2098{
2099 if (dircache_runinfo.suspended++ > 0 &&
2100 (!freeit || dircache_runinfo.handle <= 0))
2101 return;
2102
2103 unsigned int thread_id = dircache_runinfo.thread_id;
2104
2105 reset_cache();
2106 clear_dircache_queue();
2107
2108 /* grab the buffer away into our control; the cache won't need it now */
2109 int handle = 0;
2110 if (freeit)
2111 handle = reset_buffer();
2112
2113 dircache_unlock();
2114
2115 if (handle > 0)
2116 core_free(handle);
2117
2118 thread_wait(thread_id);
2119
2120 dircache_lock();
2121}
2122
2123/* backend to dircache_resume() and dircache_enable() */
2124static int dircache_resume_internal(bool build_now)
2125{
2126 int volatile rc = 0;
2127
2128 if (dircache_runinfo.suspended == 0 || --dircache_runinfo.suspended == 0)
2129 rc = build_now && dircache_runinfo.enabled ? 1 : 0;
2130
2131 if (rc)
2132 rc = dircache_thread_post(&rc) ? INT_MIN : -1;
2133
2134 if (rc == INT_MIN)
2135 {
2136 dircache_unlock();
2137
2138 while (rc == INT_MIN) /* poll for response */
2139 sleep(0);
2140
2141 dircache_lock();
2142 }
2143
2144 return rc < 0 ? rc * 10 - 2 : rc;
1043} 2145}
1044 2146
1045/** 2147/**
1046 * Get application flags used to determine if dircache is still intact. 2148 * service to dircache_enable() and dircache_load(); "build_now" starts a build
2149 * immediately if the cache was not enabled
1047 */ 2150 */
1048bool dircache_get_appflag(long mask) 2151static int dircache_enable_internal(bool build_now)
1049{ 2152{
1050 return dircache_is_enabled() && (appflags & mask); 2153 int rc = 0;
2154
2155 if (!dircache_runinfo.enabled)
2156 {
2157 dircache_runinfo.enabled = true;
2158 rc = dircache_resume_internal(build_now);
2159 }
2160
2161 return rc;
1051} 2162}
1052 2163
1053/** 2164/**
1054 * Returns the current number of entries (directories and files) in the cache. 2165 * service to dircache_disable()
1055 */ 2166 */
1056int dircache_get_entry_count(void) 2167static void dircache_disable_internal(void)
1057{ 2168{
1058 return entry_count; 2169 if (dircache_runinfo.enabled)
2170 {
2171 dircache_runinfo.enabled = false;
2172 dircache_suspend_internal(true);
2173 }
1059} 2174}
1060 2175
1061/** 2176/**
1062 * Returns the allocated space for dircache (without reserve space). 2177 * disables dircache without freeing the buffer (so it can be re-enabled
2178 * afterwards with dircache_resume(); usually called when accepting an USB
2179 * connection
1063 */ 2180 */
1064int dircache_get_cache_size(void) 2181void dircache_suspend(void)
1065{ 2182{
1066 return dircache_is_enabled() ? dircache_size : 0; 2183 dircache_lock();
2184 dircache_suspend_internal(false);
2185 dircache_unlock();
1067} 2186}
1068 2187
1069/** 2188/**
1070 * Returns how many bytes of the reserve allocation for live cache 2189 * re-enables the dircache if previously suspended by dircache_suspend
1071 * updates have been used. 2190 * or dircache_steal_buffer(), re-using the already allocated buffer if
2191 * available
2192 *
2193 * returns: 0 if the background build is started or dircache is still
2194 * suspended
2195 * > 0 if the build is non-background
2196 * < 0 upon failure
1072 */ 2197 */
1073int dircache_get_reserve_used(void) 2198int dircache_resume(void)
1074{ 2199{
1075 return dircache_is_enabled() ? reserve_used : 0; 2200 dircache_lock();
2201 int rc = dircache_resume_internal(true);
2202 dircache_unlock();
2203 return rc;
1076} 2204}
1077 2205
1078/** 2206/**
1079 * Returns the time in kernel ticks that took to build the cache. 2207 * as dircache_resume() but globally enables it; called by settings and init
1080 */ 2208 */
1081int dircache_get_build_ticks(void) 2209int dircache_enable(void)
1082{ 2210{
1083 return dircache_is_enabled() ? cache_build_ticks : 0; 2211 dircache_lock();
2212 int rc = dircache_enable_internal(true);
2213 dircache_unlock();
2214 return rc;
1084} 2215}
1085 2216
1086/** 2217/**
1087 * Disables dircache without freeing the buffer (so it can be re-enabled 2218 * as dircache_suspend() but also frees the buffer; usually called on shutdown
1088 * afterwards with dircache_resume() or dircache_build()), usually 2219 * or when deactivated
1089 * called when accepting an usb connection */ 2220 */
1090void dircache_suspend(void) 2221void dircache_disable(void)
1091{ 2222{
1092 int i; 2223 dircache_lock();
1093 bool cache_in_use; 2224 dircache_disable_internal();
1094 2225 dircache_unlock();
1095 if (thread_enabled)
1096 queue_post(&dircache_queue, DIRCACHE_STOP, 0);
1097
1098 while (thread_enabled)
1099 sleep(1);
1100 dircache_initialized = false;
1101
1102 logf("Waiting for cached dirs to release");
1103 do {
1104 cache_in_use = false;
1105 for (i = 0; i < MAX_OPEN_DIRS; i++) {
1106 if (!opendirs[i].regulardir && opendirs[i].busy)
1107 {
1108 cache_in_use = true;
1109 sleep(1);
1110 break ;
1111 }
1112 }
1113 } while (cache_in_use) ;
1114
1115 logf("Cache released");
1116 entry_count = 0;
1117} 2226}
1118 2227
1119/** 2228/**
1120 * Re-enables the dircache if previous suspended by dircache_suspend 2229 * have dircache give up its allocation; call dircache_resume() to restart it
1121 * or dircache_steal_buffer(), re-using the already allocated buffer
1122 *
1123 * Returns true if the background build is started, false otherwise
1124 * (e.g. if no buffer was previously allocated)
1125 */ 2230 */
1126bool dircache_resume(void) 2231void dircache_free_buffer(void)
1127{ 2232{
1128 bool ret = allocated_size > 0; 2233 dircache_lock();
1129 if (ret) /* only resume if already allocated */ 2234 dircache_suspend_internal(true);
1130 ret = (dircache_build(0) > 0); 2235 dircache_unlock();
2236}
2237
2238
2239/** Dircache live updating **/
1131 2240
1132 return (allocated_size > 0); 2241/**
2242 * obtain binding information for the file's root volume; this is the starting
2243 * point for internal path parsing and binding
2244 */
2245void dircache_get_rootinfo(struct file_base_info *infop)
2246{
2247 int volume = BASEINFO_VOL(infop);
2248 struct dircache_volume *dcvolp = DCVOL(volume);
2249
2250 if (dcvolp->serialnum)
2251 {
2252 /* root has a binding */
2253 infop->dcfile.idx = -volume - 1;
2254 infop->dcfile.serialnum = dcvolp->serialnum;
2255 }
2256 else
2257 {
2258 /* root is idle */
2259 dircache_dcfile_init(&infop->dcfile);
2260 }
1133} 2261}
1134 2262
1135/** 2263/**
1136 * Disables the dircache entirely. Usually called on shutdown or when 2264 * called by file code when the first reference to a file or directory is
1137 * deactivated 2265 * opened
1138 */ 2266 */
1139void dircache_disable(void) 2267void dircache_bind_file(struct file_base_binding *bindp)
2268{
2269 /* requires write exclusion */
2270 logf("dc open: %u", (unsigned int)bindp->info.dcfile.serialnum);
2271 binding_open(bindp);
2272}
2273
2274/**
2275 * called by file code when the last reference to a file or directory is
2276 * closed
2277 */
2278void dircache_unbind_file(struct file_base_binding *bindp)
1140{ 2279{
1141 dircache_suspend(); 2280 /* requires write exclusion */
1142 dircache_free(); 2281 logf("dc close: %u", (unsigned int)bindp->info.dcfile.serialnum);
2282 binding_close(bindp);
1143} 2283}
1144 2284
1145/** 2285/**
1146 * Steal the allocated dircache buffer and disable dircache. 2286 * called by file code when a file is newly created
1147 */ 2287 */
1148void* dircache_steal_buffer(size_t *size) 2288void dircache_fileop_create(struct file_base_info *dirinfop,
2289 struct file_base_binding *bindp,
2290 const char *basename,
2291 const struct dirinfo_native *dinp)
1149{ 2292{
1150 dircache_suspend(); 2293 /* requires write exclusion */
1151 if (dircache_size == 0) 2294 logf("dc create: %u \"%s\"",
2295 (unsigned int)bindp->info.dcfile.serialnum, basename);
2296
2297 if (!dirinfop->dcfile.serialnum)
1152 { 2298 {
1153 *size = 0; 2299 /* no parent binding => no child binding */
1154 return NULL; 2300 return;
1155 } 2301 }
1156 2302
1157 /* since we give up the buffer (without freeing), it must not move anymore */ 2303 struct dircache_entry *ce;
1158 dont_move = true; 2304 int idx = create_entry(basename, &ce);
1159 *size = dircache_size + (DIRCACHE_RESERVE-reserve_used); 2305 if (idx == 0)
1160 2306 {
1161 return dircache_root; 2307 /* failed allocation; parent cache contents are not complete */
2308 establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED);
2309 return;
2310 }
2311
2312 struct file_base_info *infop = &bindp->info;
2313
2314#ifdef DIRCACHE_NATIVE
2315 ce->firstcluster = infop->fatfile.firstcluster;
2316 ce->direntry = infop->fatfile.e.entry;
2317 ce->direntries = infop->fatfile.e.entries;
2318 ce->wrtdate = dinp->wrtdate;
2319 ce->wrttime = dinp->wrttime;
2320#else
2321 ce->mtime = dinp->mtime;
2322#endif
2323 ce->attr = dinp->attr;
2324 if (!(dinp->attr & ATTR_DIRECTORY))
2325 ce->filesize = dinp->size;
2326
2327 insert_file_entry(dirinfop, ce);
2328
2329 /* file binding will have been queued when it was opened; just resolve */
2330 infop->dcfile.idx = idx;
2331 infop->dcfile.serialnum = ce->serialnum;
2332 binding_resolve(infop);
2333
2334 if ((dinp->attr & ATTR_DIRECTORY) && !is_dotdir_name(basename))
2335 {
2336 /* scan-in the contents of the new directory at this level only */
2337 buffer_lock();
2338 sab_process_dir(infop, false);
2339 buffer_unlock();
2340 }
1162} 2341}
1163 2342
1164/** 2343/**
1165 * Usermode function to return dircache_entry index to the given path. 2344 * called by file code when a file or directory is removed
1166 */ 2345 */
1167static int dircache_get_entry_id_ex(const char *filename, bool go_down) 2346void dircache_fileop_remove(struct file_base_binding *bindp)
1168{ 2347{
1169 if (!dircache_initialized || filename == NULL) 2348 /* requires write exclusion */
1170 return -1; 2349 logf("dc remove: %u\n", (unsigned int)bindp->info.dcfile.serialnum);
1171
1172 struct dircache_entry* res = dircache_get_entry(filename, go_down);
1173 return res ? res - dircache_root : -1;
1174}
1175 2350
1176int dircache_get_entry_id(const char* filename) 2351 if (!bindp->info.dcfile.serialnum)
1177{ 2352 return; /* no binding yet */
1178 return dircache_get_entry_id_ex(filename, false); 2353
2354 free_file_entry(&bindp->info);
2355
2356 /* if binding was resolved; it should now be queued via above call */
1179} 2357}
1180 2358
1181/** 2359/**
1182 * Internal: Get the startcluster for the index 2360 * called by file code when a file is renamed
1183 */ 2361 */
1184long _dircache_get_entry_startcluster(int id) 2362void dircache_fileop_rename(struct file_base_info *dirinfop,
2363 struct file_base_binding *bindp,
2364 const char *basename)
1185{ 2365{
1186 return get_entry(id)->startcluster; 2366 /* requires write exclusion */
2367 logf("dc rename: %u \"%s\"",
2368 (unsigned int)bindp->info.dcfile.serialnum, basename);
2369
2370 if (!dirinfop->dcfile.serialnum)
2371 {
2372 /* new parent directory not cached; there is nowhere to put it so
2373 nuke it */
2374 if (bindp->info.dcfile.serialnum)
2375 free_file_entry(&bindp->info);
2376 /* else no entry anyway */
2377
2378 return;
2379 }
2380
2381 if (!bindp->info.dcfile.serialnum)
2382 {
2383 /* binding not resolved on the old file but it's going into a resolved
2384 parent which means the parent would be missing an entry in the cache;
2385 downgrade the parent */
2386 establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED);
2387 return;
2388 }
2389
2390 /* unlink the entry but keep it; it needs to be re-sorted since the
2391 underlying FS probably changed the order */
2392 struct dircache_entry *ce = remove_file_entry(&bindp->info);
2393
2394#ifdef DIRCACHE_NATIVE
2395 /* update other name-related information before inserting */
2396 ce->direntry = bindp->info.fatfile.e.entry;
2397 ce->direntries = bindp->info.fatfile.e.entries;
2398#endif
2399
2400 /* place it into its new home */
2401 insert_file_entry(dirinfop, ce);
2402
2403 /* lastly, update the entry name itself */
2404 if (entry_reassign_name(ce, basename))
2405 {
2406 /* it's not really the same one now so re-stamp it */
2407 dc_serial_t serialnum = next_serialnum();
2408 ce->serialnum = serialnum;
2409 bindp->info.dcfile.serialnum = serialnum;
2410 }
2411 else
2412 {
2413 /* it cannot be kept around without a valid name */
2414 free_file_entry(&bindp->info);
2415 establish_frontier(dirinfop->dcfile.idx, FRONTIER_ZONED);
2416 }
1187} 2417}
1188 2418
1189/** 2419/**
1190 * Internal: Get the struct dirinfo for the index 2420 * called by file code to synchronize file entry information
1191 */ 2421 */
1192struct dirinfo* _dircache_get_entry_dirinfo(int id) 2422void dircache_fileop_sync(struct file_base_binding *bindp,
2423 const struct dirinfo_native *dinp)
1193{ 2424{
1194 return &get_entry(id)->info; 2425 /* requires write exclusion */
2426 struct file_base_info *infop = &bindp->info;
2427 logf("dc sync: %u\n", (unsigned int)infop->dcfile.serialnum);
2428
2429 if (!infop->dcfile.serialnum)
2430 return; /* binding unresolved */
2431
2432 struct dircache_entry *ce = get_entry(infop->dcfile.idx);
2433 if (!ce)
2434 {
2435 logf(" bad index %d", infop->dcfile.idx);
2436 return; /* a root (should never be called for this) */
2437 }
2438
2439#ifdef DIRCACHE_NATIVE
2440 ce->firstcluster = infop->fatfile.firstcluster;
2441 ce->wrtdate = dinp->wrtdate;
2442 ce->wrttime = dinp->wrttime;
2443#else
2444 ce->mtime = dinp->mtime;
2445#endif
2446 ce->attr = dinp->attr;
2447 if (!(dinp->attr & ATTR_DIRECTORY))
2448 ce->filesize = dinp->size;
1195} 2449}
1196 2450
1197/* 2451
1198 * build a path from an entry upto the root using recursion 2452/** Dircache paths and files **/
1199 * 2453
1200 * it appends '/' after strlcat, therefore buf[0] needs to be prepared with '/' 2454#ifdef DIRCACHE_DUMPSTER
1201 * and it will leave a trailing '/' 2455/* helper for dircache_get_path() */
1202 * 2456static ssize_t get_path_sub(int idx, char *buf, size_t size)
1203 * returns the position of that trailing '/' so it can be deleted afterwards
1204 * (or, in case of truncation, the position of the nul byte */
1205static size_t copy_path_helper(const struct dircache_entry *entry, char *buf, size_t size)
1206{ 2457{
1207 int offset = 1; 2458 if (idx == 0)
1208 /* has parent? */ 2459 return -2; /* entry is an orphan split from any root */
1209 if (entry->up)
1210 offset += copy_path_helper(entry->up, buf, size);
1211 2460
1212 size_t len = strlcpy(buf+offset, entry->d_name, size - offset) + offset; 2461 ssize_t offset;
1213 if (len < size) 2462 char *cename;
2463
2464 if (idx > 0)
1214 { 2465 {
1215 buf[len++] = '/'; 2466 /* go all the way up then move back down from the root */
1216 buf[len] = '\0'; 2467 struct dircache_entry *ce = get_entry(idx);
2468 offset = get_path_sub(ce->up, buf, size) - 1;
2469 if (offset < 0)
2470 return -3;
2471
2472 cename = alloca(MAX_NAME + 1);
2473 entry_name_copy(cename, ce);
1217 } 2474 }
1218 return len-1; 2475 else /* idx < 0 */
2476 {
2477 offset = 0;
2478 cename = "";
2479
2480 #ifdef HAVE_MULTIVOLUME
2481 int volume = IF_MV_VOL(-idx - 1);
2482 if (volume > 0)
2483 {
2484 /* prepend the volume specifier for volumes > 0 */
2485 cename = alloca(VOL_MAX_LEN+1);
2486 get_volume_name(volume, cename);
2487 }
2488 #endif /* HAVE_MULTIVOLUME */
2489 }
2490
2491 return offset + path_append(buf + offset, PA_SEP_HARD, cename,
2492 size > (size_t)offset ? size - offset : 0);
1219} 2493}
2494#endif /* DIRCACHE_DUMPSTER */
2495
2496#if 0
2497
1220/** 2498/**
1221 * Function to copy the full absolute path from dircache to the given buffer 2499 * retrieve and validate the file's entry/binding serial number
1222 * using the given dircache_entry pointer. 2500 * the dircache file's serial number must match the indexed entry's or the
1223 * 2501 * file reference is stale
1224 * Returns the size of the resulting string, or 0 if an error occured
1225 */ 2502 */
1226size_t dircache_copy_path(int index, char *buf, size_t size) 2503static dc_serial_t get_file_serialnum(const struct dircache_file *dcfilep)
1227{ 2504{
1228 if (!size || !buf || index < 0) 2505 int idx = dcfilep->idx;
2506
2507 if (idx == 0 || idx < -NUM_VOLUMES)
1229 return 0; 2508 return 0;
1230 2509
1231 buf[0] = '/'; 2510 dc_serial_t serialnum;
1232 size_t res = copy_path_helper(&dircache_root[index], buf, size - 1); 2511
1233 /* fixup trailing '/' */ 2512 if (idx > 0)
1234 buf[res] = '\0'; 2513 {
1235 return res; 2514 struct dircache_entry *ce = get_entry(idx);
2515 serialnum = ce ? ce->serialnum : 0;
2516 }
2517 else
2518 {
2519 serialnum = get_idx_dcvolp(idx)->serialnum;
2520 }
2521
2522 return serialnum == dcfilep->serialnum ? serialnum : 0;
1236} 2523}
1237 2524
1238/* --- Directory cache live updating functions --- */ 2525/**
1239static int block_until_ready(void) 2526 * usermode function to construct a full absolute path from dircache into the
2527 * given buffer given the dircache file info
2528 *
2529 * returns:
2530 * success - the length of the string, not including the trailing null
2531 * failure - a negative value
2532 *
2533 * successful return value is as strlcpy()
2534 *
2535 * errors:
2536 * ENOENT - the file or directory does not exist
2537 */
2538ssize_t dircache_get_path(const struct dircache_file *dcfilep, char *buf,
2539 size_t size)
1240{ 2540{
1241 /* Block until dircache has been built. */ 2541 /* if missing buffer space, still return what's needed a la strlcpy */
1242 while (!dircache_initialized && dircache_is_initializing()) 2542 if (!buf)
1243 sleep(1); 2543 size = 0;
1244 2544 else if (size)
1245 if (!dircache_initialized) 2545 *buf = '\0';
1246 return -1; 2546
1247 2547 ssize_t len = -1;
1248 return 0; 2548
2549 dircache_lock();
2550
2551 /* first and foremost, there must be a cache and the serial number must
2552 check out */
2553 if (dircache_runinfo.handle && get_file_serialnum(dcfilep))
2554 len = get_path_sub(dcfilep->idx, buf, size);
2555
2556 if (len < 0)
2557 errno = ENOENT;
2558
2559 dircache_unlock();
2560 return len;
1249} 2561}
1250 2562
1251static struct dircache_entry* dircache_new_entry(const char *path, int attribute) 2563/**
1252{ 2564 * searches the sublist starting at 'idx' for the named component
1253 struct dircache_entry *entry; 2565 */
1254 char basedir[MAX_PATH*2];
1255 char *new;
1256 long last_cache_size = dircache_size;
1257 2566
1258 strlcpy(basedir, path, sizeof(basedir)); 2567/* helper for get_file_sub() */
1259 new = strrchr(basedir, '/'); 2568static struct dircache_entry *
1260 if (new == NULL) 2569get_file_sub_scan(int idx, const char *name, size_t length, int *idxp)
2570{
2571 struct dircache_entry *ce = get_entry(idx);
2572 if (ce)
1261 { 2573 {
1262 logf("error occurred"); 2574 char entname[MAX_NAME+1];
1263 dircache_initialized = false; 2575 name = strmemdupa(name, length);
1264 return NULL; 2576
2577 do
2578 {
2579 entry_name_copy(entname, ce);
2580 if (!strcasecmp(entname, name))
2581 {
2582 *idxp = idx;
2583 break;
2584 }
2585
2586 idx = ce->next;
2587 }
2588 while ((ce = get_entry(idx)));
1265 } 2589 }
1266 2590
1267 *new = '\0'; 2591 return ce;
1268 new++; 2592}
2593
2594/**
2595 * searches for the subcomponent of *pathp
2596 */
2597
2598/* helper for dircache_get_file() */
2599static int get_file_sub(const char **pathp, int *downp, int *idxp)
2600{
2601 int rc;
2602 const char *name;
2603 rc = parse_path_component(pathp, &name, false);
2604 if (rc <= 0)
2605 return rc;
2606 else if (rc >= MAX_PATH)
2607 return ENAMETOOLONG; /* that's just unpossible, man */
2608
2609 struct dircache_entry *ce = get_file_sub_scan(*downp, name, rc, idxp);
1269 2610
1270 entry = dircache_get_entry(basedir, true); 2611 if (!ce)
1271 if (entry == NULL) 2612 rc = RC_NOT_FOUND; /* not there; tellibry solly */
2613 else if (!*pathp)
2614 rc = RC_PATH_ENDED; /* done */
2615 else if (!(ce->attr & ATTR_DIRECTORY))
2616 rc = ENOTDIR; /* a parent component must be a directory */
2617 else
2618 while ((rc = get_file_sub(pathp, &ce->down, idxp)) == RC_CONTINUE);
2619
2620 switch (rc)
1272 { 2621 {
1273 logf("basedir not found!"); 2622 case RC_GO_UP: /* hit ".."; drop to previous level */
1274 logf("%s", basedir); 2623 return RC_CONTINUE;
1275 dircache_initialized = false; 2624 case RC_PATH_ENDED: /* success! */
1276 return NULL; 2625 return RC_FOUND;
2626 default: /* component not found or error */
2627 return rc;
1277 } 2628 }
2629}
1278 2630
1279 if (reserve_used + 2*sizeof(struct dircache_entry) + strlen(new)+1 2631/**
1280 >= DIRCACHE_RESERVE) 2632 * usermode function to return dircache file info for the given path
2633 *
2634 * returns:
2635 * success: the volume number that is specified for the file
2636 * failure: a negative value
2637 *
2638 * errors:
2639 * ENOENT - the file or directory does not exist or path is empty
2640 * ENAMETOOLONG - a component of the path is too long
2641 * ENOTDIR - a component of the path is not a directory
2642 */
2643int dircache_get_file(const char *path, struct dircache_file *dcfilep)
2644{
2645 if (!path_is_absolute(path) || !dcfilep)
1281 { 2646 {
1282 logf("not enough space"); 2647 errno = ENOENT;
1283 dircache_initialized = false; 2648 return -1;
1284 return NULL;
1285 } 2649 }
1286
1287 while (entry->next != NULL)
1288 entry = entry->next;
1289 2650
1290 if (entry->d_name != NULL) 2651 dircache_lock();
2652
2653 if (!dircache_runinfo.handle)
1291 { 2654 {
1292 entry = dircache_gen_next(entry); 2655 dircache_unlock();
1293 if (entry == NULL) 2656 errno = ENOENT;
2657 return -2;
2658 }
2659
2660 int volume = 0;
2661 int idx = 0;
2662 dc_serial_t serialnum = 0;
2663 struct dircache_volume *dcvolp = NULL;
2664 struct dircache_entry *ce = NULL;
2665
2666 int rc = RC_GO_UP;
2667
2668 while (rc == RC_CONTINUE || rc == RC_GO_UP)
2669 {
2670 #ifdef HAVE_MULTIVOLUME
2671 if (rc == RC_GO_UP)
1294 { 2672 {
1295 dircache_initialized = false; 2673 volume = path_strip_volume(path, &path, false);
1296 return NULL; 2674 if (!CHECK_VOL(volume))
2675 {
2676 rc = ENXIO;
2677 break;
2678 }
1297 } 2679 }
1298 } 2680 #endif /* HAVE_MULTIVOLUME */
2681
2682 dcvolp = DCVOL(volume);
1299 2683
1300 size_t size = strlen(new) + 1; 2684 int *downp = &dcvolp->root_down;
1301 entry->d_name = (d_names_start -= size); 2685 if (*downp <= 0)
1302 entry->startcluster = 0; 2686 {
1303 memset(&entry->info, 0, sizeof(entry->info)); 2687 rc = ENXIO;
1304 entry->info.attribute = attribute; 2688 break;
2689 }
1305 2690
1306 strcpy(entry->d_name, new); 2691 rc = get_file_sub(&path, downp, &idx);
1307 dircache_size += size; 2692 }
1308 2693
1309 if (attribute & ATTR_DIRECTORY) 2694 switch (rc)
1310 { 2695 {
1311 logf("gen_down"); 2696 case RC_FOUND: /* hit: component found */
1312 dircache_gen_down(entry); 2697 serialnum = ce->serialnum;
2698 rc = volume;
2699 break;
2700 case RC_PATH_ENDED: /* hit: it's a root (volume or system) */
2701 idx = -volume - 1;
2702 serialnum = dcvolp->serialnum;
2703 rc = volume;
2704 break;
2705 case RC_NOT_FOUND: /* miss */
2706 rc = ENOENT;
2707 default:
2708 idx = 0;
2709 errno = rc;
2710 rc = -3;
2711 break;
1313 } 2712 }
1314
1315 reserve_used += dircache_size - last_cache_size;
1316 2713
1317 return entry; 2714 dcfilep->idx = idx;
2715 dcfilep->serialnum = serialnum;
2716
2717 dircache_unlock();
2718 return rc;
1318} 2719}
2720#endif /* 0 */
2721
1319 2722
1320void dircache_bind(int fd, const char *path) 2723/** Debug screen/info stuff **/
2724
2725/**
2726 * return cache state parameters
2727 */
2728void dircache_get_info(struct dircache_info *info)
1321{ 2729{
1322 struct dircache_entry *entry; 2730 static const char * const status_descriptions[] =
1323 2731 {
1324 /* Queue requests until dircache has been built. */ 2732 [DIRCACHE_IDLE] = "Idle",
1325 if (!dircache_initialized && dircache_is_initializing()) 2733 [DIRCACHE_SCANNING] = "Scanning",
2734 [DIRCACHE_READY] = "Ready",
2735 };
2736
2737 if (!info)
2738 return;
2739
2740 memset(info, 0, sizeof (*info));
2741
2742 dircache_lock();
2743
2744 enum dircache_status status = DIRCACHE_IDLE;
2745
2746 for (unsigned int volume = 0; volume < NUM_VOLUMES; volume++)
1326 { 2747 {
1327 if (fdbind_idx >= MAX_PENDING_BINDINGS) 2748 struct dircache_volume *dcvolp = DCVOL(volume);
1328 return ; 2749 enum dircache_status volstatus = dcvolp->status;
1329 strlcpy(fdbind_cache[fdbind_idx].path, path, 2750
1330 sizeof(fdbind_cache[fdbind_idx].path)); 2751 switch (volstatus)
1331 fdbind_cache[fdbind_idx].fd = fd; 2752 {
1332 fdbind_idx++; 2753 case DIRCACHE_SCANNING:
1333 return ; 2754 /* if any one is scanning then overall status is "scanning" */
2755 status = volstatus;
2756
2757 /* sum the time the scanning has taken so far */
2758 info->build_ticks += current_tick - dcvolp->start_tick;
2759 break;
2760 case DIRCACHE_READY:
2761 /* if all the rest are idle and at least one is ready, then
2762 status is "ready". */
2763 if (status == DIRCACHE_IDLE)
2764 status = DIRCACHE_READY;
2765
2766 /* sum the build ticks of all "ready" volumes */
2767 info->build_ticks += dcvolp->build_ticks;
2768 break;
2769 case DIRCACHE_IDLE:
2770 /* if all are idle; then the whole cache is "idle" */
2771 break;
2772 }
1334 } 2773 }
1335
1336 if (!dircache_initialized)
1337 return ;
1338 2774
1339 logf("bind: %d/%s", fd, path); 2775 info->status = status;
1340 entry = dircache_get_entry(path, false); 2776 info->statusdesc = status_descriptions[status];
1341 if (entry == NULL) 2777 info->last_size = dircache.last_size;
2778 info->size_limit = DIRCACHE_LIMIT;
2779 info->reserve = DIRCACHE_RESERVE;
2780
2781 /* report usage only if there is something ready or being built */
2782 if (status != DIRCACHE_IDLE)
1342 { 2783 {
1343 logf("not found!"); 2784 info->reserve_used = reserve_buf_used();
1344 dircache_initialized = false; 2785 info->size = dircache.size;
1345 return ; 2786 info->sizeused = dircache.sizeused;
2787 info->entry_count = dircache.numentries;
1346 } 2788 }
1347 2789
1348 fd_bindings[fd] = entry; 2790 dircache_unlock();
1349} 2791}
1350 2792
1351void dircache_update_filesize(int fd, long newsize, long startcluster) 2793#ifdef DIRCACHE_DUMPSTER
2794/**
2795 * dump RAW binary of buffer and CSV of all valid paths and volumes to disk
2796 */
2797void dircache_dump(void)
1352{ 2798{
1353 if (!dircache_initialized || fd < 0) 2799 /* open both now so they're in the cache */
1354 return ; 2800 int fdbin = open(DIRCACHE_DUMPSTER_BIN, O_WRONLY|O_CREAT|O_TRUNC, 0666);
1355 2801 int fdcsv = open(DIRCACHE_DUMPSTER_CSV, O_WRONLY|O_CREAT|O_TRUNC, 0666);
1356 if (fd_bindings[fd] == NULL) 2802 if (fdbin < 0 || fdcsv < 0)
1357 { 2803 {
1358 logf("dircache fd(%d) access error", fd); 2804 close(fdbin);
1359 dircache_initialized = false; 2805 return;
1360 return ;
1361 } 2806 }
1362
1363 fd_bindings[fd]->info.size = newsize;
1364 fd_bindings[fd]->startcluster = startcluster;
1365}
1366void dircache_update_filetime(int fd)
1367{
1368#if CONFIG_RTC == 0
1369 (void)fd;
1370#else
1371 short year;
1372 struct tm *now = get_time();
1373 if (!dircache_initialized || fd < 0)
1374 return ;
1375
1376 if (fd_bindings[fd] == NULL)
1377 {
1378 logf("dircache fd access error");
1379 dircache_initialized = false;
1380 return ;
1381 }
1382 year = now->tm_year+1900-1980;
1383 fd_bindings[fd]->info.wrtdate = (((year)&0x7f)<<9) |
1384 (((now->tm_mon+1)&0xf)<<5) |
1385 (((now->tm_mday)&0x1f));
1386 fd_bindings[fd]->info.wrttime = (((now->tm_hour)&0x1f)<<11) |
1387 (((now->tm_min)&0x3f)<<5) |
1388 (((now->tm_sec/2)&0x1f));
1389#endif
1390}
1391 2807
1392void dircache_mkdir(const char *path) 2808 trigger_cpu_boost();
1393{ /* Test ok. */ 2809 dircache_lock();
1394 if (block_until_ready()) 2810
1395 return ; 2811 if (dircache_runinfo.handle)
1396 2812 {
1397 2813 buffer_lock();
1398 logf("mkdir: %s", path); 2814
1399 dircache_new_entry(path, ATTR_DIRECTORY); 2815 /* bin */
1400} 2816 write(fdbin, dircache_runinfo.p + ENTRYSIZE,
1401 2817 dircache_runinfo.bufsize + 1);
1402void dircache_rmdir(const char *path) 2818
1403{ /* Test ok. */ 2819 /* CSV */
1404 struct dircache_entry *entry; 2820 fdprintf(fdcsv, "\"Index\",\"Serialnum\","
1405 2821 "\"Path\",\"Frontier\","
1406 if (block_until_ready()) 2822 "\"Attribute\",\"File Size\","
1407 return ; 2823 "\"Mod Date\",\"Mod Time\"\n");
1408 2824
1409 logf("rmdir: %s", path); 2825 FOR_EACH_VOLUME(-1, volume)
1410 entry = dircache_get_entry(path, false);
1411 if (entry == NULL || entry->down == NULL)
1412 {
1413 logf("not found or not a directory!");
1414 dircache_initialized = false;
1415 return ;
1416 }
1417
1418 entry->down = NULL;
1419 entry->d_name = NULL;
1420}
1421
1422/* Remove a file from cache */
1423void dircache_remove(const char *name)
1424{ /* Test ok. */
1425 struct dircache_entry *entry;
1426
1427 if (block_until_ready())
1428 return ;
1429
1430 logf("remove: %s", name);
1431
1432 entry = dircache_get_entry(name, false);
1433
1434 if (entry == NULL)
1435 {
1436 logf("not found!");
1437 dircache_initialized = false;
1438 return ;
1439 }
1440
1441 entry->d_name = NULL;
1442}
1443
1444void dircache_rename(const char *oldpath, const char *newpath)
1445{ /* Test ok. */
1446 struct dircache_entry *entry, *newentry;
1447 struct dircache_entry oldentry;
1448 char absolute_path[MAX_PATH*2];
1449 char *p;
1450
1451 if (block_until_ready())
1452 return ;
1453
1454 logf("rename: %s->%s", oldpath, newpath);
1455
1456 entry = dircache_get_entry(oldpath, false);
1457 if (entry == NULL)
1458 {
1459 logf("not found!");
1460 dircache_initialized = false;
1461 return ;
1462 }
1463
1464 /* Delete the old entry. */
1465 entry->d_name = NULL;
1466
1467 /** If we rename the same filename twice in a row, we need to
1468 * save the data, because the entry will be re-used. */
1469 oldentry = *entry;
1470
1471 /* Generate the absolute path for destination if necessary. */
1472 if (newpath[0] != '/')
1473 {
1474 strlcpy(absolute_path, oldpath, sizeof(absolute_path));
1475 p = strrchr(absolute_path, '/');
1476 if (!p)
1477 { 2826 {
1478 logf("Invalid path"); 2827 struct dircache_volume *dcvolp = DCVOL(volume);
1479 dircache_initialized = false; 2828 if (dcvolp->status == DIRCACHE_IDLE)
1480 return ; 2829 continue;
2830
2831 #ifdef HAVE_MULTIVOLUME
2832 char name[VOL_MAX_LEN+1];
2833 get_volume_name(volume, name);
2834 #endif
2835 fdprintf(fdcsv,
2836 "%d,%lu,"
2837 "\"%c" IF_MV("%s") "\",%u,"
2838 "0x%08X,0,"
2839 "\"\",\"\"\n",
2840 -volume-1, dcvolp->serialnum,
2841 PATH_SEPCH, IF_MV(name,) dcvolp->frontier,
2842 ATTR_DIRECTORY | ATTR_VOLUME);
1481 } 2843 }
1482 2844
1483 *p = '\0'; 2845 FOR_EACH_CACHE_ENTRY(ce)
1484 strlcpy(p, absolute_path, sizeof(absolute_path)-strlen(p)); 2846 {
1485 newpath = absolute_path; 2847 #ifdef DIRCACHE_NATIVE
1486 } 2848 time_t mtime = fattime_mktime(ce->wrtdate, ce->wrttime);
1487 2849 #else
1488 newentry = dircache_new_entry(newpath, entry->info.attribute); 2850 time_t mtime = ce->mtime;
1489 if (newentry == NULL) 2851 #endif
1490 { 2852 struct tm tm;
1491 dircache_initialized = false; 2853 gmtime_r(&mtime, &tm);
1492 return ; 2854
2855 char buf[DC_MAX_NAME + 2];
2856 *buf = '\0';
2857 int idx = get_index(ce);
2858 get_path_sub(idx, buf, sizeof (buf));
2859
2860 fdprintf(fdcsv,
2861 "%d,%lu,"
2862 "\"%s\",%u,"
2863 "0x%08X,%lu,"
2864 "%04d/%02d/%02d,"
2865 "%02d:%02d:%02d\n",
2866 idx, ce->serialnum,
2867 buf, ce->frontier,
2868 ce->attr, (ce->attr & ATTR_DIRECTORY) ?
2869 0ul : (unsigned long)ce->filesize,
2870 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
2871 tm.tm_hour, tm.tm_min, tm.tm_sec);
2872 }
2873
2874 buffer_unlock();
1493 } 2875 }
1494 2876
1495 newentry->down = oldentry.down; 2877 dircache_unlock();
1496 newentry->startcluster = oldentry.startcluster; 2878 cancel_cpu_boost();
1497 newentry->info.size = oldentry.info.size; 2879
1498 newentry->info.wrtdate = oldentry.info.wrtdate; 2880 close(fdbin);
1499 newentry->info.wrttime = oldentry.info.wrttime; 2881 close(fdcsv);
1500} 2882}
2883#endif /* DIRCACHE_DUMPSTER */
2884
1501 2885
1502void dircache_add_file(const char *path, long startcluster) 2886/** Misc. stuff **/
2887
2888/**
2889 * set the dircache file to initial values
2890 */
2891void dircache_dcfile_init(struct dircache_file *dcfilep)
1503{ 2892{
1504 struct dircache_entry *entry; 2893 dcfilep->idx = 0;
1505 2894 dcfilep->serialnum = 0;
1506 if (block_until_ready())
1507 return ;
1508
1509 logf("add file: %s", path);
1510 entry = dircache_new_entry(path, 0);
1511 if (entry == NULL)
1512 return ;
1513
1514 entry->startcluster = startcluster;
1515} 2895}
1516 2896
1517static bool is_disable_msg_pending(void) 2897#ifdef HAVE_EEPROM_SETTINGS
2898
2899#ifdef HAVE_HOTSWAP
2900/* NOTE: This is hazardous to the filesystem of any sort of removable
2901 storage unless it may be determined that the filesystem from save
2902 to load is identical. If it's not possible to do so in a timely
2903 manner, it's not worth persisting the cache. */
2904 #warning "Don't do this; you'll find the consequences unpleasant."
2905#endif
2906
2907/* dircache persistence file header magic */
2908#define DIRCACHE_MAGIC 0x00d0c0a1
2909
2910/* dircache persistence file header */
2911struct dircache_maindata
1518{ 2912{
1519 return check_event_queue(); 2913 uint32_t magic; /* DIRCACHE_MAGIC */
2914 struct dircache dircache; /* metadata of the cache! */
2915 uint32_t datacrc; /* CRC32 of data */
2916 uint32_t hdrcrc; /* CRC32 of header through datacrc */
2917} __attribute__((packed, aligned (4)));
2918
2919/**
2920 * verify that the clean status is A-ok
2921 */
2922static bool dircache_is_clean(bool saving)
2923{
2924 if (saving)
2925 return dircache.dcvol[0].status == DIRCACHE_READY;
2926 else
2927 {
2928 return dircache.dcvol[0].status == DIRCACHE_IDLE &&
2929 !dircache_runinfo.enabled;
2930 }
1520} 2931}
1521 2932
1522DIR_CACHED* opendir_cached(const char* name) 2933/**
2934 * function to load the internal cache structure from disk to initialize
2935 * the dircache really fast with little disk access.
2936 */
2937int dircache_load(void)
1523{ 2938{
1524 int dd; 2939 logf("Loading directory cache");
1525 DIR_CACHED* pdir = opendirs; 2940 int fd = open_dircache_file(O_RDONLY);
2941 if (fd < 0)
2942 return -1;
2943
2944 int rc = -1;
2945
2946 ssize_t size;
2947 struct dircache_maindata maindata;
2948 uint32_t crc;
2949 int handle = 0;
2950 bool hasbuffer = false;
1526 2951
1527 if ( name[0] != '/' ) 2952 size = sizeof (maindata);
2953 if (read(fd, &maindata, size) != size)
1528 { 2954 {
1529 DEBUGF("Only absolute paths supported right now\n"); 2955 logf("dircache: header read failed");
1530 return NULL; 2956 goto error_nolock;
1531 } 2957 }
1532 2958
1533 /* find a free dir descriptor */ 2959 /* sanity check the header */
1534 for ( dd=0; dd<MAX_OPEN_DIRS; dd++, pdir++) 2960 if (maindata.magic != DIRCACHE_MAGIC)
1535 if ( !pdir->busy ) 2961 {
1536 break; 2962 logf("dircache: invalid header magic");
2963 goto error_nolock;
2964 }
1537 2965
1538 if ( dd == MAX_OPEN_DIRS ) 2966 crc = crc_32(&maindata, offsetof(struct dircache_maindata, hdrcrc),
2967 0xffffffff);
2968 if (crc != maindata.hdrcrc)
1539 { 2969 {
1540 DEBUGF("Too many dirs open\n"); 2970 logf("dircache: invalid header CRC32");
1541 errno = EMFILE; 2971 goto error_nolock;
1542 return NULL;
1543 } 2972 }
1544
1545 pdir->busy = true;
1546 2973
1547 if (!dircache_initialized || is_disable_msg_pending()) 2974 if (maindata.dircache.size !=
2975 maindata.dircache.sizeentries + maindata.dircache.sizenames ||
2976 ALIGN_DOWN(maindata.dircache.size, ENTRYSIZE) != maindata.dircache.size ||
2977 filesize(fd) - sizeof (maindata) != maindata.dircache.size)
1548 { 2978 {
1549 pdir->internal_entry = -1; 2979 logf("dircache: file header error");
1550 pdir->regulardir = opendir_uncached(name); 2980 goto error_nolock;
1551 } 2981 }
1552 else 2982
2983 /* allocate so that exactly the reserve size remains */
2984 size_t bufsize = maindata.dircache.size + DIRCACHE_RESERVE + 1;
2985 handle = alloc_cache(bufsize);
2986 if (handle <= 0)
2987 {
2988 logf("dircache: failed load allocation");
2989 goto error_nolock;
2990 }
2991
2992 dircache_lock();
2993 buffer_lock();
2994
2995 if (!dircache_is_clean(false))
2996 goto error;
2997
2998 /* from this point on, we're actually dealing with the cache in RAM */
2999 dircache = maindata.dircache;
3000
3001 set_buffer(handle, bufsize);
3002 hasbuffer = true;
3003
3004 /* convert back to in-RAM representation */
3005 dircache.numentries = maindata.dircache.sizeentries / ENTRYSIZE;
3006
3007 /* read the dircache file into memory; start with the entries */
3008 size = maindata.dircache.sizeentries;
3009 if (read(fd, dircache_runinfo.pentry + 1, size) != size)
1553 { 3010 {
1554 pdir->regulardir = NULL; 3011 logf("dircache read failed #1");
1555 pdir->internal_entry = dircache_get_entry_id_ex(name, true); 3012 goto error;
1556 pdir->theent.info.attribute = -1; /* used to make readdir_cached aware of the first call */
1557 } 3013 }
1558 3014
1559 if (pdir->internal_entry == -1 && pdir->regulardir == NULL) 3015 crc = crc_32(dircache_runinfo.pentry + 1, size, 0xffffffff);
3016
3017 /* continue with the names; fix up indexes to them if needed */
3018 dircache.names -= maindata.dircache.sizenames;
3019 *get_name(dircache.names - 1) = 0;
3020
3021 size = maindata.dircache.sizenames;
3022 if (read(fd, get_name(dircache.names), size) != size)
1560 { 3023 {
1561 pdir->busy = false; 3024 logf("dircache read failed #2");
1562 return NULL; 3025 goto error;
1563 } 3026 }
1564 3027
1565 return pdir; 3028 crc = crc_32(get_name(dircache.names), size, crc);
1566} 3029 if (crc != maindata.datacrc)
3030 {
3031 logf("dircache: data failed CRC32");
3032 goto error;
3033 }
1567 3034
1568struct dirent_cached* readdir_cached(DIR_CACHED* dir) 3035 /* only names will be changed in relative position so fix up those
1569{ 3036 references */
1570 struct dircache_entry *ce = get_entry(dir->internal_entry); 3037 ssize_t offset = dircache.names - maindata.dircache.names;
1571 struct dirent_uncached *regentry; 3038 if (offset != 0)
1572 3039 {
1573 if (!dir->busy) 3040 /* nothing should be open besides the dircache file itself therefore
1574 return NULL; 3041 no bindings need be resolved; the cache will have its own entry
3042 but that should get cleaned up when removing the file */
3043 FOR_EACH_CACHE_ENTRY(ce)
3044 {
3045 if (!ce->tinyname)
3046 ce->name += offset;
3047 }
3048 }
1575 3049
1576 if (dir->regulardir != NULL) 3050 dircache.reserve_used = 0;
1577 { 3051
1578 regentry = readdir_uncached(dir->regulardir); 3052 /* enable the cache but do not try to build it */
1579 if (regentry == NULL) 3053 dircache_enable_internal(false);
1580 return NULL; 3054
1581 3055 /* cache successfully loaded */
1582 strlcpy(dir->theent.d_name, regentry->d_name, MAX_PATH); 3056 logf("Done, %ld KiB used", dircache.size / 1024);
1583 dir->theent.startcluster = regentry->startcluster; 3057 rc = 0;
1584 dir->theent.info = regentry->info; 3058error:
1585 3059 if (rc < 0 && hasbuffer)
1586 return &dir->theent; 3060 reset_buffer();
1587 }
1588
1589 /* if theent.attribute=-1 then this is the first call */
1590 /* otherwise, this is is not so we first take the entry's ->next */
1591 /* NOTE: normal file can't have attribute=-1 */
1592 if(dir->theent.info.attribute != -1)
1593 ce = ce->next;
1594 /* skip unused entries */
1595 while(ce != NULL && ce->d_name == NULL)
1596 ce = ce->next;
1597
1598 if (ce == NULL)
1599 return NULL;
1600 3061
1601 strlcpy(dir->theent.d_name, ce->d_name, MAX_PATH); 3062 buffer_unlock();
1602 /* Can't do `dir->theent = *ce` 3063 dircache_unlock();
1603 because that modifies the d_name pointer. */
1604 dir->theent.startcluster = ce->startcluster;
1605 dir->theent.info = ce->info;
1606 dir->internal_entry = ce - dircache_root;
1607 3064
1608 //logf("-> %s", ce->d_name); 3065error_nolock:
1609 return &dir->theent; 3066 if (rc < 0 && handle > 0)
3067 core_free(handle);
3068
3069 if (fd >= 0)
3070 close(fd);
3071
3072 remove_dircache_file();
3073 return rc;
1610} 3074}
1611 3075
1612int closedir_cached(DIR_CACHED* dir) 3076/**
3077 * function to save the internal cache stucture to disk for fast loading
3078 * on boot
3079 */
3080int dircache_save(void)
1613{ 3081{
1614 if (!dir->busy) 3082 logf("Saving directory cache");
3083
3084 int fd = open_dircache_file(O_WRONLY|O_CREAT|O_TRUNC|O_APPEND);
3085 if (fd < 0)
1615 return -1; 3086 return -1;
1616
1617 dir->busy=false;
1618 if (dir->regulardir != NULL)
1619 return closedir_uncached(dir->regulardir);
1620
1621 return 0;
1622}
1623 3087
1624int mkdir_cached(const char *name) 3088 dircache_lock();
1625{ 3089 buffer_lock();
1626 int rc=mkdir_uncached(name); 3090
1627 if (rc >= 0) 3091 int rc = -1;
1628 dircache_mkdir(name); 3092
1629 return(rc); 3093 if (!dircache_is_clean(true))
3094 goto error;
3095
3096 /* save the header structure along with the cache metadata */
3097 ssize_t size;
3098 uint32_t crc;
3099 struct dircache_maindata maindata =
3100 {
3101 .magic = DIRCACHE_MAGIC,
3102 .dircache = dircache,
3103 };
3104
3105 /* store the size since it better detects an invalid header */
3106 maindata.dircache.sizeentries = maindata.dircache.numentries * ENTRYSIZE;
3107
3108 /* write the template header */
3109 size = sizeof (maindata);
3110 if (write(fd, &maindata, size) != size)
3111 {
3112 logf("dircache: write failed #1");
3113 goto error;
3114 }
3115
3116 /* write the dircache entries */
3117 size = maindata.dircache.sizeentries;
3118 if (write(fd, dircache_runinfo.pentry + 1, size) != size)
3119 {
3120 logf("dircache: write failed #2");
3121 goto error;
3122 }
3123
3124 crc = crc_32(dircache_runinfo.pentry + 1, size, 0xffffffff);
3125
3126 /* continue with the names */
3127 size = maindata.dircache.sizenames;
3128 if (write(fd, get_name(dircache.names), size) != size)
3129 {
3130 logf("dircache: write failed #3");
3131 goto error;
3132 }
3133
3134 crc = crc_32(get_name(dircache.names), size, crc);
3135 maindata.datacrc = crc;
3136
3137 /* rewrite the header with CRC info */
3138 if (lseek(fd, 0, SEEK_SET) != 0)
3139 {
3140 logf("dircache: seek failed");
3141 goto error;
3142 }
3143
3144 crc = crc_32(&maindata, offsetof(struct dircache_maindata, hdrcrc),
3145 0xffffffff);
3146 maindata.hdrcrc = crc;
3147
3148 if (write(fd, &maindata, sizeof (maindata)) != sizeof (maindata))
3149 {
3150 logf("dircache: write failed #4");
3151 goto error;
3152 }
3153
3154 /* as of now, no changes to the volumes should be allowed at all since
3155 that makes what was saved completely invalid */
3156 rc = 0;
3157error:
3158 buffer_unlock();
3159 dircache_unlock();
3160
3161 if (rc < 0)
3162 remove_dircache_file();
3163
3164 close(fd);
3165 return rc;
1630} 3166}
3167#endif /* HAVE_EEPROM_SETTINGS */
1631 3168
1632int rmdir_cached(const char* name) 3169/**
3170 * main one-time initialization function that must be called before any other
3171 * operations within the dircache
3172 */
3173void dircache_init(size_t last_size)
1633{ 3174{
1634 int rc=rmdir_uncached(name); 3175 queue_init(&dircache_queue, false);
1635 if(rc >= 0) 3176
1636 dircache_rmdir(name); 3177 dircache.last_size = MIN(last_size, DIRCACHE_LIMIT);
1637 return(rc); 3178
3179 struct dircache_runinfo *dcrip = &dircache_runinfo;
3180 dcrip->suspended = 1;
3181 dcrip->thread_done = true;
3182 dcrip->ops.move_callback = move_callback;
1638} 3183}
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 @@
19 * 19 *
20 ****************************************************************************/ 20 ****************************************************************************/
21#include <stdio.h> 21#include <stdio.h>
22#include <string.h>
23#include "config.h"
22#include "kernel.h" 24#include "kernel.h"
23#include "storage.h" 25#include "storage.h"
24#include "debug.h" 26#include "debug.h"
25#include "fat.h" 27#include "disk_cache.h"
26#include "dir.h" /* for release_dirs() */ 28#include "fileobj_mgr.h"
27#include "file.h" /* for release_files() */ 29#include "dir.h"
30#include "dircache_redirect.h"
28#include "disk.h" 31#include "disk.h"
29#include <string.h> 32
33#ifndef CONFIG_DEFAULT_PARTNUM
34#define CONFIG_DEFAULT_PARTNUM 0
35#endif
36
37#define disk_reader_lock() file_internal_lock_READER()
38#define disk_reader_unlock() file_internal_unlock_READER()
39#define disk_writer_lock() file_internal_lock_WRITER()
40#define disk_writer_unlock() file_internal_unlock_WRITER()
30 41
31/* Partition table entry layout: 42/* Partition table entry layout:
32 ----------------------- 43 -----------------------
@@ -42,11 +53,18 @@
42 12-15: nr of sectors in partition 53 12-15: nr of sectors in partition
43*/ 54*/
44 55
45#define BYTES2INT32(array,pos) \ 56#define BYTES2INT32(array, pos) \
46 ((long)array[pos] | ((long)array[pos+1] << 8 ) | \ 57 (((uint32_t)array[pos+0] << 0) | \
47 ((long)array[pos+2] << 16 ) | ((long)array[pos+3] << 24 )) 58 ((uint32_t)array[pos+1] << 8) | \
59 ((uint32_t)array[pos+2] << 16) | \
60 ((uint32_t)array[pos+3] << 24))
61
62#define BYTES2INT16(array, pos) \
63 (((uint32_t)array[pos+0] << 0) | \
64 ((uint32_t)array[pos+1] << 8))
48 65
49static const unsigned char fat_partition_types[] = { 66static const unsigned char fat_partition_types[] =
67{
50 0x0b, 0x1b, /* FAT32 + hidden variant */ 68 0x0b, 0x1b, /* FAT32 + hidden variant */
51 0x0c, 0x1c, /* FAT32 (LBA) + hidden variant */ 69 0x0c, 0x1c, /* FAT32 (LBA) + hidden variant */
52#ifdef HAVE_FAT16SUPPORT 70#ifdef HAVE_FAT16SUPPORT
@@ -56,159 +74,135 @@ static const unsigned char fat_partition_types[] = {
56#endif 74#endif
57}; 75};
58 76
59static struct partinfo part[NUM_DRIVES*4]; /* space for 4 partitions on 2 drives */ 77/* space for 4 partitions on 2 drives */
60static int vol_drive[NUM_VOLUMES]; /* mounted to which drive (-1 if none) */ 78static struct partinfo part[NUM_DRIVES*4];
61static struct mutex disk_mutex; 79/* mounted to which drive (-1 if none) */
80static int vol_drive[NUM_VOLUMES];
81
82static int get_free_volume(void)
83{
84 for (int i = 0; i < NUM_VOLUMES; i++)
85 {
86 if (vol_drive[i] == -1) /* unassigned? */
87 return i;
88 }
89
90 return -1; /* none found */
91}
62 92
63#ifdef MAX_LOG_SECTOR_SIZE 93#ifdef MAX_LOG_SECTOR_SIZE
64static int disk_sector_multiplier[NUM_DRIVES] = {[0 ... NUM_DRIVES-1] = 1}; 94static int disk_sector_multiplier[NUM_DRIVES] =
95 { [0 ... NUM_DRIVES-1] = 1 };
65 96
66int disk_get_sector_multiplier(IF_MD_NONVOID(int drive)) 97int disk_get_sector_multiplier(IF_MD_NONVOID(int drive))
67{ 98{
68 #ifdef HAVE_MULTIDRIVE 99 if (!CHECK_DRV(drive))
69 return disk_sector_multiplier[drive]; 100 return 0;
70 #else 101
71 return disk_sector_multiplier[0]; 102 disk_reader_lock();
72 #endif 103 int multiplier = disk_sector_multiplier[IF_MD_DRV(drive)];
104 disk_reader_unlock();
105 return multiplier;
73} 106}
74#endif 107#endif /* MAX_LOG_SECTOR_SIZE */
75 108
76struct partinfo* disk_init(IF_MD_NONVOID(int drive)) 109bool disk_init(IF_MD_NONVOID(int drive))
77{ 110{
78 int i; 111 if (!CHECK_DRV(drive))
79#ifdef HAVE_MULTIDRIVE 112 return false; /* out of space in table */
80 /* For each drive, start at a different position, in order not to destroy
81 the first entry of drive 0.
82 That one is needed to calculate config sector position. */
83 struct partinfo* pinfo = &part[drive*4];
84 if ((size_t)drive >= sizeof(part)/sizeof(*part)/4)
85 return NULL; /* out of space in table */
86#else
87 struct partinfo* pinfo = part;
88 const int drive = 0;
89 (void)drive;
90#endif
91 113
92 unsigned char* sector = fat_get_sector_buffer(); 114 unsigned char *sector = dc_get_buffer();
93 storage_read_sectors(IF_MD(drive,) 0,1, sector); 115 if (!sector)
94 /* check that the boot sector is initialized */ 116 return false;
95 if ( (sector[510] != 0x55) ||
96 (sector[511] != 0xaa)) {
97 fat_release_sector_buffer();
98 DEBUGF("Bad boot sector signature\n");
99 return NULL;
100 }
101 117
102 /* parse partitions */ 118 memset(sector, 0, SECTOR_SIZE);
103 for ( i=0; i<4; i++ ) { 119 storage_read_sectors(IF_MD(drive,) 0, 1, sector);
104 unsigned char* ptr = sector + 0x1be + 16*i;
105 pinfo[i].type = ptr[4];
106 pinfo[i].start = BYTES2INT32(ptr, 8);
107 pinfo[i].size = BYTES2INT32(ptr, 12);
108 120
109 DEBUGF("Part%d: Type %02x, start: %08lx size: %08lx\n", 121 bool init = false;
110 i,pinfo[i].type,pinfo[i].start,pinfo[i].size);
111 122
112 /* extended? */ 123 /* check that the boot sector is initialized */
113 if ( pinfo[i].type == 5 ) { 124 if (BYTES2INT16(sector, 510) == 0xaa55)
114 /* not handled yet */ 125 {
115 } 126 /* For each drive, start at a different position, in order not to
116 } 127 destroy the first entry of drive 0. That one is needed to calculate
117 fat_release_sector_buffer(); 128 config sector position. */
118 return pinfo; 129 struct partinfo *pinfo = &part[IF_MD_DRV(drive)*4];
119}
120 130
121struct partinfo* disk_partinfo(int partition) 131 disk_writer_lock();
122{
123 return &part[partition];
124}
125 132
126void disk_init_subsystem(void) 133 /* parse partitions */
127{ 134 for (int i = 0; i < 4; i++)
128 mutex_init(&disk_mutex); 135 {
129} 136 unsigned char* ptr = sector + 0x1be + 16*i;
137 pinfo[i].type = ptr[4];
138 pinfo[i].start = BYTES2INT32(ptr, 8);
139 pinfo[i].size = BYTES2INT32(ptr, 12);
130 140
131int disk_mount_all(void) 141 DEBUGF("Part%d: Type %02x, start: %08lx size: %08lx\n",
132{ 142 i,pinfo[i].type,pinfo[i].start,pinfo[i].size);
133 int mounted=0;
134 int i;
135
136#ifdef HAVE_HOTSWAP
137 mutex_lock(&disk_mutex);
138#endif
139 143
140 fat_init(); /* reset all mounted partitions */ 144 /* extended? */
141 for (i=0; i<NUM_VOLUMES; i++) 145 if ( pinfo[i].type == 5 )
142 vol_drive[i] = -1; /* mark all as unassigned */ 146 {
147 /* not handled yet */
148 }
149 }
143 150
144#ifndef HAVE_MULTIDRIVE 151 disk_writer_unlock();
145 mounted = disk_mount(0); 152
146#else 153 init = true;
147 for(i=0;i<NUM_DRIVES;i++) 154 }
155 else
148 { 156 {
149#ifdef HAVE_HOTSWAP 157 DEBUGF("Bad boot sector signature\n");
150 if (storage_present(i))
151#endif
152 mounted += disk_mount(i);
153 } 158 }
154#endif
155 159
156#ifdef HAVE_HOTSWAP 160 dc_release_buffer(sector);
157 mutex_unlock(&disk_mutex); 161 return init;
158#endif
159 return mounted;
160} 162}
161 163
162static int get_free_volume(void) 164bool disk_partinfo(int partition, struct partinfo *info)
163{ 165{
164 int i; 166 if (partition < 0 || partition >= (int)ARRAYLEN(part) || !info)
165 for (i=0; i<NUM_VOLUMES; i++) 167 return false;
166 {
167 if (vol_drive[i] == -1) /* unassigned? */
168 return i;
169 }
170 168
171 return -1; /* none found */ 169 disk_reader_lock();
170 *info = part[partition];
171 disk_reader_unlock();
172 return true;
172} 173}
173 174
174int disk_mount(int drive) 175int disk_mount(int drive)
175{ 176{
176 int mounted = 0; /* reset partition-on-drive flag */ 177 int mounted = 0; /* reset partition-on-drive flag */
177 int volume;
178 struct partinfo* pinfo;
179 178
180#ifdef HAVE_HOTSWAP 179 disk_writer_lock();
181 mutex_lock(&disk_mutex);
182#endif
183 180
184 volume = get_free_volume(); 181 int volume = get_free_volume();
185 pinfo = disk_init(IF_MD(drive));
186#ifdef MAX_LOG_SECTOR_SIZE
187 disk_sector_multiplier[drive] = 1;
188#endif
189 182
190 if (pinfo == NULL) 183 if (!disk_init(IF_MD(drive)))
191 { 184 {
192#ifdef HAVE_HOTSWAP 185 disk_writer_unlock();
193 mutex_unlock(&disk_mutex);
194#endif
195 return 0; 186 return 0;
196 } 187 }
197#if defined(TOSHIBA_GIGABEAT_S) 188
198 int i = 1; /* For the Gigabeat S, we mount the second partition */ 189 struct partinfo *pinfo = &part[IF_MD_DRV(drive)*4];
199#else 190#ifdef MAX_LOG_SECTOR_SIZE
200 int i = 0; 191 disk_sector_multiplier[IF_MD_DRV(drive)] = 1;
201#endif 192#endif
202 for (; volume != -1 && i<4 && mounted<NUM_VOLUMES_PER_DRIVE; i++) 193
194 for (int i = CONFIG_DEFAULT_PARTNUM;
195 volume != -1 && i < 4 && mounted < NUM_VOLUMES_PER_DRIVE;
196 i++)
203 { 197 {
204 if (memchr(fat_partition_types, pinfo[i].type, 198 if (memchr(fat_partition_types, pinfo[i].type,
205 sizeof(fat_partition_types)) == NULL) 199 sizeof(fat_partition_types)) == NULL)
206 continue; /* not an accepted partition type */ 200 continue; /* not an accepted partition type */
207 201
208#ifdef MAX_LOG_SECTOR_SIZE 202 bool success = false;
209 int j; 203
210 204 #ifdef MAX_LOG_SECTOR_SIZE
211 for (j = 1; j <= (MAX_LOG_SECTOR_SIZE/SECTOR_SIZE); j <<= 1) 205 for (int j = 1; j <= (MAX_LOG_SECTOR_SIZE/SECTOR_SIZE); j <<= 1)
212 { 206 {
213 if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start * j)) 207 if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start * j))
214 { 208 {
@@ -218,93 +212,242 @@ int disk_mount(int drive)
218 vol_drive[volume] = drive; /* remember the drive for this volume */ 212 vol_drive[volume] = drive; /* remember the drive for this volume */
219 volume = get_free_volume(); /* prepare next entry */ 213 volume = get_free_volume(); /* prepare next entry */
220 disk_sector_multiplier[drive] = j; 214 disk_sector_multiplier[drive] = j;
215 success = true;
221 break; 216 break;
222 } 217 }
223 } 218 }
224#else 219 #else /* ndef MAX_LOG_SECTOR_SIZE */
225 if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start)) 220 if (!fat_mount(IF_MV(volume,) IF_MD(drive,) pinfo[i].start))
226 { 221 {
227 mounted++; 222 mounted++;
228 vol_drive[volume] = drive; /* remember the drive for this volume */ 223 vol_drive[volume] = drive; /* remember the drive for this volume */
229 volume = get_free_volume(); /* prepare next entry */ 224 volume = get_free_volume(); /* prepare next entry */
225 success = true;
230 } 226 }
231#endif 227 #endif /* MAX_LOG_SECTOR_SIZE */
228
229 if (success)
230 volume_onmount_internal(IF_MV(volume));
232 } 231 }
233 232
234 if (mounted == 0 && volume != -1) /* none of the 4 entries worked? */ 233 if (mounted == 0 && volume != -1) /* none of the 4 entries worked? */
235 { /* try "superfloppy" mode */ 234 { /* try "superfloppy" mode */
236 DEBUGF("No partition found, trying to mount sector 0.\n"); 235 DEBUGF("No partition found, trying to mount sector 0.\n");
236
237 if (!fat_mount(IF_MV(volume,) IF_MD(drive,) 0)) 237 if (!fat_mount(IF_MV(volume,) IF_MD(drive,) 0))
238 { 238 {
239#ifdef MAX_LOG_SECTOR_SIZE 239 #ifdef MAX_LOG_SECTOR_SIZE
240 disk_sector_multiplier[drive] = fat_get_bytes_per_sector(IF_MV(volume))/SECTOR_SIZE; 240 disk_sector_multiplier[drive] =
241#endif 241 fat_get_bytes_per_sector(IF_MV(volume)) / SECTOR_SIZE;
242 #endif
242 mounted = 1; 243 mounted = 1;
243 vol_drive[volume] = drive; /* remember the drive for this volume */ 244 vol_drive[volume] = drive; /* remember the drive for this volume */
245 volume_onmount_internal(IF_MV(volume));
244 } 246 }
245 } 247 }
246#ifdef HAVE_HOTSWAP 248
247 mutex_unlock(&disk_mutex); 249 disk_writer_unlock();
248#endif 250 return mounted;
251}
252
253int disk_mount_all(void)
254{
255 int mounted = 0;
256
257 disk_writer_lock();
258
259 /* reset all mounted partitions */
260 volume_onunmount_internal(IF_MV(-1));
261 fat_init();
262
263 for (int i = 0; i < NUM_VOLUMES; i++)
264 vol_drive[i] = -1; /* mark all as unassigned */
265
266 for (int i = 0; i < NUM_DRIVES; i++)
267 {
268 #ifdef HAVE_HOTSWAP
269 if (storage_present(i))
270 #endif
271 mounted += disk_mount(i);
272 }
273
274 disk_writer_unlock();
249 return mounted; 275 return mounted;
250} 276}
251 277
252int disk_unmount(int drive) 278int disk_unmount(int drive)
253{ 279{
280 if (!CHECK_DRV(drive))
281 return 0;
282
254 int unmounted = 0; 283 int unmounted = 0;
255 int i; 284
256#ifdef HAVE_HOTSWAP 285 disk_writer_lock();
257 mutex_lock(&disk_mutex); 286
258#endif 287 for (int i = 0; i < NUM_VOLUMES; i++)
259 for (i=0; i<NUM_VOLUMES; i++)
260 { 288 {
261 if (vol_drive[i] == drive) 289 if (vol_drive[i] == drive)
262 { /* force releasing resources */ 290 { /* force releasing resources */
263 vol_drive[i] = -1; /* mark unused */ 291 vol_drive[i] = -1; /* mark unused */
292
293 volume_onunmount_internal(IF_MV(i));
294 fat_unmount(IF_MV(i));
295
264 unmounted++; 296 unmounted++;
265 release_files(i);
266 release_dirs(i);
267 fat_unmount(i, false);
268 } 297 }
269 } 298 }
270#ifdef HAVE_HOTSWAP
271 mutex_unlock(&disk_mutex);
272#endif
273 299
300 disk_writer_unlock();
274 return unmounted; 301 return unmounted;
275} 302}
276 303
277int disk_unmount_all(void) 304int disk_unmount_all(void)
278{ 305{
279#ifndef HAVE_MULTIDRIVE
280 return disk_unmount(0);
281#else /* HAVE_MULTIDRIVE */
282 int unmounted = 0; 306 int unmounted = 0;
283 int i; 307
284 for (i = 0; i < NUM_DRIVES; i++) 308 disk_writer_lock();
309
310 volume_onunmount_internal(IF_MV(-1));
311
312 for (int i = 0; i < NUM_DRIVES; i++)
285 { 313 {
286#ifdef HAVE_HOTSWAP 314 #ifdef HAVE_HOTSWAP
287 if (storage_present(i)) 315 if (storage_present(i))
288#endif 316 #endif
289 unmounted += disk_unmount(i); 317 unmounted += disk_unmount(i);
290 } 318 }
291 319
320 disk_writer_unlock();
292 return unmounted; 321 return unmounted;
293#endif /* HAVE_MULTIDRIVE */ 322}
323
324bool disk_present(IF_MD_NONVOID(int drive))
325{
326 int rc = -1;
327
328 if (CHECK_DRV(drive))
329 {
330 void *sector = dc_get_buffer();
331 if (sector)
332 {
333 rc = storage_read_sectors(IF_MD(drive,) 0, 1, sector);
334 dc_release_buffer(sector);
335 }
336 }
337
338 return rc == 0;
339}
340
341
342/** Volume-centric functions **/
343
344void volume_recalc_free(IF_MV_NONVOID(int volume))
345{
346 if (!CHECK_VOL(volume))
347 return;
348
349 /* FIXME: this is crummy but the only way to ensure a correct freecount
350 if other threads are writing and changing the fsinfo; it is possible
351 to get multiple threads calling here and also writing and get correct
352 freespace counts, however a bit complicated to do; if thou desireth I
353 shall implement the concurrent version -- jethead71 */
354 disk_writer_lock();
355 fat_recalc_free(IF_MV(volume));
356 disk_writer_unlock();
357}
358
359unsigned int volume_get_cluster_size(IF_MV_NONVOID(int volume))
360{
361 if (!CHECK_VOL(volume))
362 return 0;
363
364 disk_reader_lock();
365 unsigned int clustersize = fat_get_cluster_size(IF_MV(volume));
366 disk_reader_unlock();
367 return clustersize;
368}
369
370void volume_size(IF_MV(int volume,) unsigned long *sizep, unsigned long *freep)
371{
372 disk_reader_lock();
373
374 if (!CHECK_VOL(volume) || !fat_size(IF_MV(volume,) sizep, freep))
375 {
376 if (freep) *sizep = 0;
377 if (freep) *freep = 0;
378 }
379
380 disk_reader_unlock();
381}
382
383#if defined (HAVE_HOTSWAP) || defined (HAVE_MULTIDRIVE) \
384 || defined (HAVE_DIRCACHE)
385enum volume_info_type
386{
387#ifdef HAVE_HOTSWAP
388 VP_REMOVABLE,
389 VP_PRESENT,
390#endif
391#if defined (HAVE_MULTIDRIVE) || defined (HAVE_DIRCACHE)
392 VP_DRIVE,
393#endif
394};
395
396static int volume_properties(int volume, enum volume_info_type infotype)
397{
398 int res = -1;
399
400 disk_reader_lock();
401
402 if (CHECK_VOL(volume))
403 {
404 int vd = vol_drive[volume];
405 switch (infotype)
406 {
407 #ifdef HAVE_HOTSWAP
408 case VP_REMOVABLE:
409 res = storage_removable(vd) ? 1 : 0;
410 break;
411 case VP_PRESENT:
412 res = storage_present(vd) ? 1 : 0;
413 break;
414 #endif
415 #if defined(HAVE_MULTIDRIVE) || defined(HAVE_DIRCACHE)
416 case VP_DRIVE:
417 res = vd;
418 break;
419 #endif
420 }
421 }
422
423 disk_reader_unlock();
424 return res;
294} 425}
295 426
296#ifdef HAVE_HOTSWAP 427#ifdef HAVE_HOTSWAP
297bool volume_removable(int volume) 428bool volume_removable(int volume)
298{ 429{
299 if(vol_drive[volume] == -1) 430 return volume_properties(volume, VP_REMOVABLE) > 0;
300 return false;
301 return storage_removable(vol_drive[volume]);
302} 431}
303 432
304bool volume_present(int volume) 433bool volume_present(int volume)
305{ 434{
306 if(vol_drive[volume] == -1) 435 return volume_properties(volume, VP_PRESENT) > 0;
307 return false;
308 return storage_present(vol_drive[volume]);
309} 436}
310#endif 437#endif /* HAVE_HOTSWAP */
438
439#ifdef HAVE_MULTIDRIVE
440int volume_drive(int volume)
441{
442 return volume_properties(volume, VP_DRIVE);
443}
444#endif /* HAVE_MULTIDRIVE */
445
446#ifdef HAVE_DIRCACHE
447bool volume_ismounted(IF_MV_NONVOID(int volume))
448{
449 return volume_properties(IF_MV_VOL(volume), VP_DRIVE) >= 0;
450}
451#endif /* HAVE_DIRCACHE */
452
453#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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "config.h"
22#include "debug.h"
23#include "system.h"
24#include "linked_list.h"
25#include "disk_cache.h"
26#include "fat.h" /* for SECTOR_SIZE */
27#include "bitarray.h"
28
29/* Cache: LRU cache with separately-chained hashtable
30 *
31 * Each entry of the map is the mapped location of the hashed sector value
32 * where each bit in each map entry indicates which corresponding cache
33 * entries are occupied by sector values that collide in that map entry.
34 *
35 * Each volume is given its own bit map.
36 *
37 * To probe for a specific key, each bit in the map entry must be examined,
38 * its position used as an index into the cache_entry array and the actual
39 * sector information compared for that cache entry. If the search exhausts
40 * all bits, the sector is not cached.
41 *
42 * To avoid long chains, the map entry count should be much greater than the
43 * number of cache entries. Since the cache is an LRU design, no buffer entry
44 * in the array is intrinsically associated with any particular sector number
45 * or volume.
46 *
47 * Example 6-sector cache with 8-entry map:
48 * cache entry 543210
49 * cache map 100000 <- sector number hashes into map
50 * 000000
51 * 000100
52 * 000000
53 * 010000
54 * 000000
55 * 001001 <- collision
56 * 000000
57 * volume map 111101 <- entry usage by the volume (OR of all map entries)
58 */
59
60enum dce_flags /* flags for each cache entry */
61{
62 DCE_INUSE = 0x01, /* entry in use and valid */
63 DCE_DIRTY = 0x02, /* entry is dirty in need of writeback */
64 DCE_BUF = 0x04, /* entry is being used as a general buffer */
65};
66
67struct disk_cache_entry
68{
69 struct lldc_node node; /* LRU list links */
70 unsigned char flags; /* entry flags */
71#ifdef HAVE_MULTIVOLUME
72 unsigned char volume; /* volume of sector */
73#endif
74 unsigned long sector; /* cached disk sector number */
75};
76
77BITARRAY_TYPE_DECLARE(cache_map_entry_t, cache_map, DC_NUM_ENTRIES)
78
79static inline unsigned int map_sector(unsigned long sector)
80{
81 /* keep sector hash simple for now */
82 return sector % DC_MAP_NUM_ENTRIES;
83}
84
85static struct lldc_head cache_lru; /* LRU cache list (head = LRU item) */
86static struct disk_cache_entry cache_entry[DC_NUM_ENTRIES];
87static cache_map_entry_t cache_map_entry[NUM_VOLUMES][DC_MAP_NUM_ENTRIES];
88static cache_map_entry_t cache_vol_map[NUM_VOLUMES] IBSS_ATTR;
89static uint8_t cache_buffer[DC_NUM_ENTRIES][DC_CACHE_BUFSIZE] CACHEALIGN_ATTR;
90struct mutex disk_cache_mutex SHAREDBSS_ATTR;
91
92#define CACHE_MAP_ENTRY(volume, mapnum) \
93 cache_map_entry[IF_MV_VOL(volume)][mapnum]
94#define CACHE_VOL_MAP(volume) \
95 cache_vol_map[IF_MV_VOL(volume)]
96
97#define DCE_LRU() ((struct disk_cache_entry *)cache_lru.head)
98#define DCE_NEXT(fce) ((struct disk_cache_entry *)(fce)->node.next)
99#define NODE_DCE(node) ((struct disk_cache_entry *)(node))
100
101/* get the cache index from a pointer to a buffer */
102#define DCIDX_FROM_BUF(buf) \
103 ((uint8_t (*)[DC_CACHE_BUFSIZE])(buf) - cache_buffer)
104
105#define DCIDX_FROM_DCE(dce) \
106 ((dce) - cache_entry)
107
108/* set the in-use bit in the map */
109static inline void cache_bitmap_set_bit(int volume, unsigned int mapnum,
110 unsigned int bitnum)
111{
112 cache_map_set_bit(&CACHE_MAP_ENTRY(volume, mapnum), bitnum);
113 cache_map_set_bit(&CACHE_VOL_MAP(volume), bitnum);
114 (void)volume;
115}
116
117/* clear the in-use bit in the map */
118static inline void cache_bitmap_clear_bit(int volume, unsigned int mapnum,
119 unsigned int bitnum)
120{
121 cache_map_clear_bit(&CACHE_MAP_ENTRY(volume, mapnum), bitnum);
122 cache_map_clear_bit(&CACHE_VOL_MAP(volume), bitnum);
123 (void)volume;
124}
125
126/* make entry MRU by moving it to the list tail */
127static inline void touch_cache_entry(struct disk_cache_entry *which)
128{
129 struct lldc_node *lru = cache_lru.head;
130 struct lldc_node *node = &which->node;
131
132 if (node == lru->prev) /* already MRU */
133 ; /**/
134 else if (node == lru) /* is the LRU? just rotate list */
135 cache_lru.head = lru->next;
136 else /* somewhere else; move it */
137 {
138 lldc_remove(&cache_lru, node);
139 lldc_insert_last(&cache_lru, node);
140 }
141}
142
143/* remove LRU entry from the cache list to use as a buffer */
144static struct disk_cache_entry * cache_remove_lru_entry(void)
145{
146 struct lldc_node *lru = cache_lru.head;
147
148 /* at least one is reserved for client */
149 if (lru == lru->next)
150 return NULL;
151
152 /* remove it; next-LRU becomes the LRU */
153 lldc_remove(&cache_lru, lru);
154 return NODE_DCE(lru);
155}
156
157/* return entry to the cache list and set it LRU */
158static void cache_return_lru_entry(struct disk_cache_entry *fce)
159{
160 lldc_insert_first(&cache_lru, &fce->node);
161}
162
163/* discard the entry's data and mark it unused */
164static inline void cache_discard_entry(struct disk_cache_entry *dce,
165 unsigned int index)
166{
167 cache_bitmap_clear_bit(IF_MV_VOL(dce->volume), map_sector(dce->sector),
168 index);
169 dce->flags = 0;
170}
171
172/* search the cache for the specified sector, returning a buffer, either
173 to the specified sector, if it exists, or a new/evicted entry that must
174 be filled */
175void * dc_cache_probe(IF_MV(int volume,) unsigned long sector,
176 unsigned int *flagsp)
177{
178 unsigned int mapnum = map_sector(sector);
179
180 FOR_EACH_BITARRAY_SET_BIT(&CACHE_MAP_ENTRY(volume, mapnum), index)
181 {
182 struct disk_cache_entry *dce = &cache_entry[index];
183
184 if (dce->sector == sector)
185 {
186 *flagsp = DCE_INUSE;
187 touch_cache_entry(dce);
188 return cache_buffer[index];
189 }
190 }
191
192 /* sector not found so the LRU is the victim */
193 struct disk_cache_entry *dce = DCE_LRU();
194 cache_lru.head = dce->node.next;
195
196 unsigned int index = DCIDX_FROM_DCE(dce);
197 void *buf = cache_buffer[index];
198 unsigned int old_flags = dce->flags;
199
200 if (old_flags)
201 {
202 int old_volume = IF_MV_VOL(dce->volume);
203 unsigned long sector = dce->sector;
204 unsigned int old_mapnum = map_sector(sector);
205
206 if (old_flags & DCE_DIRTY)
207 dc_writeback_callback(IF_MV(old_volume,) sector, buf);
208
209 if (mapnum == old_mapnum IF_MV( && volume == old_volume ))
210 goto finish_setup;
211
212 cache_bitmap_clear_bit(old_volume, old_mapnum, index);
213 }
214
215 cache_bitmap_set_bit(IF_MV_VOL(volume), mapnum, index);
216
217finish_setup:
218 dce->flags = DCE_INUSE;
219#ifdef HAVE_MULTIVOLUME
220 dce->volume = volume;
221#endif
222 dce->sector = sector;
223
224 *flagsp = 0;
225 return buf;
226}
227
228/* mark in-use cache entry as dirty by buffer */
229void dc_dirty_buf(void *buf)
230{
231 unsigned int index = DCIDX_FROM_BUF(buf);
232
233 if (index >= DC_NUM_ENTRIES)
234 return;
235
236 /* dirt remains, sticky until flushed */
237 struct disk_cache_entry *fce = &cache_entry[index];
238 if (fce->flags & DCE_INUSE)
239 fce->flags |= DCE_DIRTY;
240}
241
242/* discard in-use cache entry by buffer */
243void dc_discard_buf(void *buf)
244{
245 unsigned int index = DCIDX_FROM_BUF(buf);
246
247 if (index >= DC_NUM_ENTRIES)
248 return;
249
250 struct disk_cache_entry *dce = &cache_entry[index];
251 if (dce->flags & DCE_INUSE)
252 cache_discard_entry(dce, index);
253}
254
255/* commit all dirty cache entries to storage for a specified volume */
256void dc_commit_all(IF_MV_NONVOID(int volume))
257{
258 DEBUGF("dc_commit_all()\n");
259
260 FOR_EACH_BITARRAY_SET_BIT(&CACHE_VOL_MAP(volume), index)
261 {
262 struct disk_cache_entry *dce = &cache_entry[index];
263 unsigned int flags = dce->flags;
264
265 if (flags & DCE_DIRTY)
266 {
267 dc_writeback_callback(IF_MV(volume,) dce->sector,
268 cache_buffer[index]);
269 dce->flags = flags & ~DCE_DIRTY;
270 }
271 }
272}
273
274/* discard all cache entries from the specified volume */
275void dc_discard_all(IF_MV_NONVOID(int volume))
276{
277 DEBUGF("dc_discard_all()\n");
278
279 FOR_EACH_BITARRAY_SET_BIT(&CACHE_VOL_MAP(volume), index)
280 cache_discard_entry(&cache_entry[index], index);
281}
282
283/* expropriate a buffer from the cache */
284void * dc_get_buffer(void)
285{
286 dc_lock_cache();
287
288 void *buf = NULL;
289 struct disk_cache_entry *dce = cache_remove_lru_entry();
290
291 if (dce)
292 {
293 unsigned int index = DCIDX_FROM_DCE(dce);
294 unsigned int flags = dce->flags;
295
296 buf = cache_buffer[index];
297
298 if (flags)
299 {
300 /* must first commit this sector if dirty */
301 if (flags & DCE_DIRTY)
302 dc_writeback_callback(IF_MV(dce->volume,) dce->sector, buf);
303
304 cache_discard_entry(dce, index);
305 }
306
307 dce->flags = DCE_BUF;
308 }
309 /* cache is out of buffers */
310
311 dc_unlock_cache();
312 return buf;
313}
314
315/* return buffer to the cache by buffer */
316void dc_release_buffer(void *buf)
317{
318 unsigned int index = DCIDX_FROM_BUF(buf);
319
320 if (index >= DC_NUM_ENTRIES)
321 return;
322
323 dc_lock_cache();
324
325 struct disk_cache_entry *dce = &cache_entry[index];
326
327 if (dce->flags & DCE_BUF)
328 {
329 dce->flags = 0;
330 cache_return_lru_entry(dce);
331 }
332
333 dc_unlock_cache();
334}
335
336/* one-time init at startup */
337void dc_init(void)
338{
339 mutex_init(&disk_cache_mutex);
340 lldc_init(&cache_lru);
341 for (unsigned int i = 0; i < DC_NUM_ENTRIES; i++)
342 lldc_insert_last(&cache_lru, &cache_entry[i].node);
343}
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 @@
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2002 by Björn Stenberg 10 * Copyright (C) 2002 by Björn Stenberg
11 * Copyright (C) 2014 by Michael Sevakis
11 * 12 *
12 * This program is free software; you can redistribute it and/or 13 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License 14 * modify it under the terms of the GNU General Public License
@@ -18,819 +19,1201 @@
18 * KIND, either express or implied. 19 * KIND, either express or implied.
19 * 20 *
20 ****************************************************************************/ 21 ****************************************************************************/
22#define RB_FILESYSTEM_OS
23#include "config.h"
24#include "system.h"
21#include <string.h> 25#include <string.h>
22#include <errno.h> 26#include <errno.h>
23#include <stdbool.h>
24#include "file.h"
25#include "fat.h"
26#include "dir_uncached.h"
27#include "debug.h" 27#include "debug.h"
28#include "dircache.h" 28#include "file.h"
29#include "filefuncs.h" 29#include "fileobj_mgr.h"
30#include "system.h" 30#include "disk_cache.h"
31#include "dircache_redirect.h"
32#include "string-extra.h"
33
34/**
35 * These functions provide a roughly POSIX-compatible file I/O API.
36 */
37
38/* structure used for open file descriptors */
39static struct filestr_desc
40{
41 struct filestr_base stream; /* basic stream info (first!) */
42 file_size_t offset; /* current offset for stream */
43 file_size_t *sizep; /* shortcut to file size in fileobj */
44} open_streams[MAX_OPEN_FILES];
31 45
32/* 46/* check and return a struct filestr_desc* from a file descriptor number */
33 These functions provide a roughly POSIX-compatible file IO API. 47static struct filestr_desc * get_filestr(int fildes)
48{
49 struct filestr_desc *file = &open_streams[fildes];
34 50
35 Since the fat32 driver only manages sectors, we maintain a one-sector 51 if ((unsigned int)fildes >= MAX_OPEN_FILES)
36 cache for each open file. This way we can provide byte access without 52 file = NULL;
37 having to re-read the sector each time. 53 else if (file->stream.flags & FDO_BUSY)
38 The penalty is the RAM used for the cache and slightly more complex code. 54 return file;
39*/
40 55
41struct filedesc { 56 DEBUGF("fildes %d: bad file number\n", fildes);
42 unsigned char cache[SECTOR_SIZE] CACHEALIGN_ATTR; 57 errno = (file && file->stream.flags == FV_NONEXIST) ? ENXIO : EBADF;
43 int cacheoffset; /* invariant: 0 <= cacheoffset <= SECTOR_SIZE */ 58 return NULL;
44 long fileoffset; 59}
45 long size;
46 int attr;
47 struct fat_file fatfile;
48 bool busy;
49 bool write;
50 bool dirty;
51 bool trunc;
52} CACHEALIGN_ATTR;
53 60
54static struct filedesc openfiles[MAX_OPEN_FILES] CACHEALIGN_ATTR; 61#define GET_FILESTR(type, fildes) \
62 ({ \
63 file_internal_lock_##type(); \
64 struct filestr_desc * _file = get_filestr(fildes); \
65 if (_file) \
66 FILESTR_LOCK(type, &_file->stream); \
67 else \
68 file_internal_unlock_##type(); \
69 _file; \
70 })
71
72/* release the lock on the filestr_desc* */
73#define RELEASE_FILESTR(type, file) \
74 ({ \
75 FILESTR_UNLOCK(type, &(file)->stream); \
76 file_internal_unlock_##type(); \
77 })
78
79/* find a free file descriptor */
80static int alloc_filestr(struct filestr_desc **filep)
81{
82 for (int fildes = 0; fildes < MAX_OPEN_FILES; fildes++)
83 {
84 struct filestr_desc *file = &open_streams[fildes];
85 if (!file->stream.flags)
86 {
87 *filep = file;
88 return fildes;
89 }
90 }
55 91
56static int flush_cache(int fd); 92 DEBUGF("Too many files open\n");
93 return -1;
94}
57 95
58int file_creat(const char *pathname) 96/* return the file size in sectors */
97static inline unsigned long filesize_sectors(file_size_t size)
59{ 98{
60 return open(pathname, O_WRONLY|O_CREAT|O_TRUNC, 0666); 99 /* overflow proof whereas "(x + y - 1) / y" is not */
100 unsigned long numsectors = size / SECTOR_SIZE;
101
102 if (size % SECTOR_SIZE)
103 numsectors++;
104
105 return numsectors;
61} 106}
62 107
63static int open_internal(const char* pathname, int flags, bool use_cache) 108/* flush a dirty cache buffer */
109static int flush_cache(struct filestr_desc *file)
64{ 110{
65 DIR_UNCACHED* dir;
66 struct dirent_uncached* entry;
67 int fd;
68 int pathnamesize = strlen(pathname) + 1;
69 char pathnamecopy[pathnamesize];
70 char* name;
71 struct filedesc* file = NULL;
72 int rc; 111 int rc;
73#ifndef HAVE_DIRCACHE 112 struct filestr_cache *cachep = file->stream.cachep;
74 (void)use_cache;
75#endif
76 113
77 LDEBUGF("open(\"%s\",%d)\n",pathname,flags); 114 DEBUGF("Flushing dirty sector cache (%lu)\n", cachep->sector);
78 115
79 if ( pathname[0] != '/' ) { 116 if (fat_query_sectornum(&file->stream.fatstr) != cachep->sector)
80 DEBUGF("'%s' is not an absolute path.\n",pathname); 117 {
81 DEBUGF("Only absolute pathnames supported at the moment\n"); 118 /* get on the correct sector */
82 errno = EINVAL; 119 rc = fat_seek(&file->stream.fatstr, cachep->sector);
83 return -1; 120 if (rc < 0)
121 FILE_ERROR(EIO, rc * 10 - 1);
84 } 122 }
85 123
86 /* find a free file descriptor */ 124 rc = fat_readwrite(&file->stream.fatstr, 1, cachep->buffer, true);
87 for ( fd=0; fd<MAX_OPEN_FILES; fd++ ) 125 if (rc < 0)
88 if ( !openfiles[fd].busy ) 126 {
89 break; 127 if (rc == FAT_RC_ENOSPC)
90 128 FILE_ERROR(ENOSPC, RC);
91 if ( fd == MAX_OPEN_FILES ) { 129 else
92 DEBUGF("Too many files open\n"); 130 FILE_ERROR(EIO, rc * 10 - 2);
93 errno = EMFILE;
94 return -2;
95 } 131 }
96 132
97 file = &openfiles[fd]; 133 cachep->flags = 0;
98 memset(file, 0, sizeof(struct filedesc)); 134 return 1;
135file_error:
136 DEBUGF("Failed flushing cache: %d\n", rc);
137 return rc;
138}
139
140static void discard_cache(struct filestr_desc *file)
141{
142 struct filestr_cache *const cachep = file->stream.cachep;
143 cachep->flags = 0;
144}
99 145
100 if (flags & (O_RDWR | O_WRONLY)) { 146/* set the file pointer */
101 file->write = true; 147static off_t lseek_internal(struct filestr_desc *file, off_t offset,
148 int whence)
149{
150 off_t rc;
151 file_size_t pos;
102 152
103 if (flags & O_TRUNC) 153 file_size_t size = MIN(*file->sizep, FILE_SIZE_MAX);
104 file->trunc = true;
105 }
106 file->busy = true;
107 154
108#ifdef HAVE_DIRCACHE 155 switch (whence)
109 if (dircache_is_enabled() && !file->write && use_cache)
110 { 156 {
111# ifdef HAVE_MULTIVOLUME 157 case SEEK_SET:
112 int volume = strip_volume(pathname, pathnamecopy); 158 if (offset < 0 || (file_size_t)offset > size)
113# endif 159 FILE_ERROR(EINVAL, -1);
114 160
115 int ce = dircache_get_entry_id(pathname); 161 pos = offset;
116 if (ce < 0) 162 break;
117 {
118 errno = ENOENT;
119 file->busy = false;
120 return -7;
121 }
122 163
123 long startcluster = _dircache_get_entry_startcluster(ce); 164 case SEEK_CUR:
124 fat_open(IF_MV(volume,) 165 if ((offset < 0 && (file_size_t)-offset > file->offset) ||
125 startcluster, 166 (offset > 0 && (file_size_t)offset > size - file->offset))
126 &(file->fatfile), 167 FILE_ERROR(EINVAL, -1);
127 NULL);
128 struct dirinfo *info = _dircache_get_entry_dirinfo(ce);
129 file->size = info->size;
130 file->attr = info->attribute;
131 file->cacheoffset = -1;
132 file->fileoffset = 0;
133 168
134 return fd; 169 pos = file->offset + offset;
135 } 170 break;
136#endif
137 171
138 strlcpy(pathnamecopy, pathname, pathnamesize); 172 case SEEK_END:
173 if (offset > 0 || (file_size_t)-offset > size)
174 FILE_ERROR(EINVAL, -1);
139 175
140 /* locate filename */ 176 pos = size + offset;
141 name=strrchr(pathnamecopy+1,'/'); 177 break;
142 if ( name ) { 178
143 *name = 0; 179 default:
144 dir = opendir_uncached(pathnamecopy); 180 FILE_ERROR(EINVAL, -1);
145 *name = '/';
146 name++;
147 }
148 else {
149 dir = opendir_uncached("/");
150 name = pathnamecopy+1;
151 }
152 if (!dir) {
153 DEBUGF("Failed opening dir\n");
154 errno = EIO;
155 file->busy = false;
156 return -4;
157 }
158
159 if(name[0] == 0) {
160 DEBUGF("Empty file name\n");
161 errno = EINVAL;
162 file->busy = false;
163 closedir_uncached(dir);
164 return -5;
165 }
166
167 /* scan dir for name */
168 while ((entry = readdir_uncached(dir))) {
169 if ( !strcasecmp(name, entry->d_name) ) {
170 fat_open(IF_MV(dir->fatdir.file.volume,)
171 entry->startcluster,
172 &(file->fatfile),
173 &(dir->fatdir));
174 file->size = file->trunc ? 0 : entry->info.size;
175 file->attr = entry->info.attribute;
176 break;
177 }
178 } 181 }
179 182
180 if ( !entry ) { 183 file->offset = pos;
181 LDEBUGF("Didn't find file %s\n",name); 184
182 if ( file->write && (flags & O_CREAT) ) { 185 return pos;
183 rc = fat_create_file(name, 186file_error:
184 &(file->fatfile), 187 return rc;
185 &(dir->fatdir)); 188}
186 if (rc < 0) { 189
187 DEBUGF("Couldn't create %s in %s\n",name,pathnamecopy); 190/* callback for each file stream to make sure all data is in sync with new
188 errno = EIO; 191 size */
189 file->busy = false; 192void ftruncate_internal_callback(struct filestr_base *stream,
190 closedir_uncached(dir); 193 struct filestr_base *s)
191 return rc * 10 - 6; 194{
195 struct filestr_desc *file = (struct filestr_desc *)s;
196 file_size_t size = *file->sizep;
197
198 /* caches with data beyond new extents are invalid */
199 unsigned long sector = file->stream.cachep->sector;
200 if (sector != INVALID_SECNUM && sector >= filesize_sectors(size))
201 filestr_discard_cache(&file->stream);
202
203 /* keep all positions within bounds */
204 if (file->offset > size)
205 file->offset = size;
206
207 (void)stream;
208}
209
210/* truncate the file to the specified length */
211static int ftruncate_internal(struct filestr_desc *file, file_size_t size,
212 bool write_now)
213{
214 int rc = 0, rc2 = 1;
215
216 file_size_t cursize = *file->sizep;
217 file_size_t truncsize = MIN(size, cursize);
218
219 if (write_now)
220 {
221 unsigned long sector = filesize_sectors(truncsize);
222 struct filestr_cache *const cachep = file->stream.cachep;
223
224 if (cachep->flags == (FSC_NEW|FSC_DIRTY) &&
225 cachep->sector + 1 == sector)
226 {
227 /* sector created but may have never been added to the cluster
228 chain; flush it now or the subsequent may fail */
229 rc2 = flush_cache(file);
230 if (rc2 == FAT_RC_ENOSPC)
231 {
232 /* no space left on device; further truncation needed */
233 discard_cache(file);
234 truncsize = ALIGN_DOWN(truncsize - 1, SECTOR_SIZE);
235 sector--;
236 rc = rc2;
192 } 237 }
193#ifdef HAVE_DIRCACHE 238 else if (rc2 < 0)
194 dircache_add_file(pathname, file->fatfile.firstcluster); 239 FILE_ERROR(ERRNO, rc2 * 10 - 1);
195#endif
196 file->size = 0;
197 file->attr = 0;
198 }
199 else {
200 DEBUGF("Couldn't find %s in %s\n",name,pathnamecopy);
201 errno = ENOENT;
202 file->busy = false;
203 closedir_uncached(dir);
204 return -7;
205 }
206 } else {
207 if(file->write && (file->attr & FAT_ATTR_DIRECTORY)) {
208 errno = EISDIR;
209 file->busy = false;
210 closedir_uncached(dir);
211 return -8;
212 } 240 }
213 }
214 closedir_uncached(dir);
215 241
216 file->cacheoffset = -1; 242 rc2 = fat_seek(&file->stream.fatstr, sector);
217 file->fileoffset = 0; 243 if (rc2 < 0)
244 FILE_ERROR(EIO, rc2 * 10 - 2);
218 245
219 if (file->write && (flags & O_APPEND)) { 246 rc2 = fat_truncate(&file->stream.fatstr);
220 rc = lseek(fd,0,SEEK_END); 247 if (rc2 < 0)
221 if (rc < 0 ) 248 FILE_ERROR(EIO, rc2 * 10 - 3);
222 return rc * 10 - 9;
223 } 249 }
250 /* else just change the cached file size */
224 251
225#ifdef HAVE_DIRCACHE 252 if (truncsize < cursize)
226 if (file->write) 253 {
227 dircache_bind(fd, pathname); 254 *file->sizep = truncsize;
228#endif 255 fileop_ontruncate_internal(&file->stream);
256 }
229 257
230 return fd; 258 /* if truncation was partially successful, it effectively destroyed
231} 259 everything after the truncation point; still, indicate failure
260 after adjusting size */
261 if (rc2 == 0)
262 FILE_ERROR(EIO, -4);
263 else if (rc2 < 0)
264 FILE_ERROR(ERRNO, rc2);
232 265
233int file_open(const char* pathname, int flags) 266file_error:
234{ 267 return rc;
235 /* By default, use the dircache if available. */
236 return open_internal(pathname, flags, true);
237} 268}
238 269
239int close(int fd) 270/* flush back all outstanding writes to the file */
271static int fsync_internal(struct filestr_desc *file)
240{ 272{
241 struct filedesc* file = &openfiles[fd]; 273 /* call only when holding WRITER lock (updates directory entries) */
242 int rc = 0; 274 int rc = 0;
243 275
244 LDEBUGF("close(%d)\n", fd); 276 file_size_t size = *file->sizep;
277 unsigned int foflags = fileobj_get_flags(&file->stream);
278
279 /* flush sector cache? */
280 struct filestr_cache *const cachep = file->stream.cachep;
281 if (cachep->flags & FSC_DIRTY)
282 {
283 int rc2 = flush_cache(file);
284 if (rc2 == FAT_RC_ENOSPC && (cachep->flags & FSC_NEW))
285 {
286 /* no space left on device so this must be dropped */
287 discard_cache(file);
288 size = ALIGN_DOWN(size - 1, SECTOR_SIZE);
289 foflags |= FO_TRUNC;
290 rc = rc2;
291 }
292 else if (rc2 < 0)
293 FILE_ERROR(ERRNO, rc2 * 10 - 1);
294 }
295
296 /* truncate? */
297 if (foflags & FO_TRUNC)
298 {
299 int rc2 = ftruncate_internal(file, size, rc == 0);
300 if (rc2 < 0)
301 FILE_ERROR(ERRNO, rc2 * 10 - 2);
245 302
246 if (fd < 0 || fd > MAX_OPEN_FILES-1) { 303 /* never needs to be done this way again since any data beyond the
247 errno = EINVAL; 304 cached size is now gone */
248 return -1; 305 fileobj_change_flags(&file->stream, 0, FO_TRUNC);
249 } 306 }
250 if (!file->busy) { 307
251 errno = EBADF; 308file_error:;
252 return -2; 309 /* tie up all loose ends (try to close the file even if failing) */
310 int rc2 = fat_closewrite(&file->stream.fatstr, size,
311 get_dir_fatent_dircache());
312 if (rc2 >= 0)
313 fileop_onsync_internal(&file->stream); /* dir_fatent is implicit arg */
314
315 if (rc2 < 0 && rc >= 0)
316 {
317 errno = EIO;
318 rc = rc2 * 10 - 3;
253 } 319 }
254 if (file->write) { 320
255 rc = fsync(fd); 321 return rc;
322}
323
324/* finish with the file and free resources */
325static int close_internal(struct filestr_desc *file)
326{
327 /* call only when holding WRITER lock (updates directory entries) */
328 int rc;
329
330 if ((file->stream.flags & FD_WRITE) &&
331 !(fileobj_get_flags(&file->stream) & FO_REMOVED))
332 {
333 rc = fsync_internal(file);
256 if (rc < 0) 334 if (rc < 0)
257 return rc * 10 - 3; 335 FILE_ERROR(ERRNO, rc * 10 - 1);
258#ifdef HAVE_DIRCACHE
259 dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
260 dircache_update_filetime(fd);
261#endif
262 } 336 }
263 337
264 file->busy = false; 338 rc = 0;
265 return 0; 339file_error:;
340 int rc2 = close_stream_internal(&file->stream);
341 if (rc2 < 0 && rc >= 0)
342 rc = rc2 * 10 - 2;
343 return rc;
266} 344}
267 345
268int fsync(int fd) 346/* actually do the open gruntwork */
347static int open_internal_inner2(const char *path,
348 struct filestr_desc *file,
349 unsigned int callflags)
269{ 350{
270 struct filedesc* file = &openfiles[fd]; 351 int rc;
271 int rc = 0;
272
273 LDEBUGF("fsync(%d)\n", fd);
274 352
275 if (fd < 0 || fd > MAX_OPEN_FILES-1) { 353 struct path_component_info compinfo;
276 errno = EINVAL; 354 rc = open_stream_internal(path, callflags, &file->stream, &compinfo);
277 return -1; 355 if (rc < 0)
278 } 356 {
279 if (!file->busy) { 357 DEBUGF("Open failed: %d\n", rc);
280 errno = EBADF; 358 FILE_ERROR_RETURN(ERRNO, rc * 10 - 1);
281 return -2;
282 } 359 }
283 if (file->write) { 360
284 /* flush sector cache */ 361 bool created = false;
285 if ( file->dirty ) { 362
286 rc = flush_cache(fd); 363 if (rc > 0)
287 if (rc < 0) 364 {
288 { 365 if (callflags & FF_EXCL)
289 /* when failing, try to close the file anyway */ 366 {
290 fat_closewrite(&(file->fatfile), file->size, file->attr); 367 DEBUGF("File exists\n");
291 return rc * 10 - 3; 368 FILE_ERROR(EEXIST, -2);
292 }
293 } 369 }
294 370
295 /* truncate? */ 371 if (compinfo.attr & ATTR_DIRECTORY)
296 if (file->trunc) { 372 {
297 rc = ftruncate(fd, file->size); 373 if ((callflags & FD_WRITE) || !(callflags & FF_ANYTYPE))
298 if (rc < 0)
299 { 374 {
300 /* when failing, try to close the file anyway */ 375 DEBUGF("File is a directory\n");
301 fat_closewrite(&(file->fatfile), file->size, file->attr); 376 FILE_ERROR(EISDIR, -3);
302 return rc * 10 - 4;
303 } 377 }
378
379 compinfo.filesize = MAX_DIRECTORY_SIZE; /* allow file ops */
380 }
381 }
382 else if (callflags & FF_CREAT)
383 {
384 if (compinfo.attr & ATTR_DIRECTORY)
385 {
386 DEBUGF("File is a directory\n");
387 FILE_ERROR(EISDIR, -5);
304 } 388 }
305 389
306 /* tie up all loose ends */ 390 /* not found; try to create it */
307 rc = fat_closewrite(&(file->fatfile), file->size, file->attr); 391
392 callflags &= ~FO_TRUNC;
393 rc = create_stream_internal(&compinfo.parentinfo, compinfo.name,
394 compinfo.length, ATTR_NEW_FILE, callflags,
395 &file->stream);
308 if (rc < 0) 396 if (rc < 0)
309 return rc * 10 - 5; 397 FILE_ERROR(ERRNO, rc * 10 - 6);
310 }
311 return 0;
312}
313 398
314int remove(const char* name) 399 created = true;
315{
316 int rc;
317 struct filedesc* file;
318 /* Can't use dircache now, because we need to access the fat structures. */
319 int fd = open_internal(name, O_WRONLY, false);
320 if ( fd < 0 )
321 return fd * 10 - 1;
322
323 file = &openfiles[fd];
324#ifdef HAVE_DIRCACHE
325 dircache_remove(name);
326#endif
327 rc = fat_remove(&(file->fatfile));
328 if ( rc < 0 ) {
329 DEBUGF("Failed removing file: %d\n", rc);
330 errno = EIO;
331 return rc * 10 - 3;
332 } 400 }
401 else
402 {
403 DEBUGF("File not found\n");
404 FILE_ERROR(ENOENT, -7);
405 }
406
407 fat_rewind(&file->stream.fatstr);
408 file->sizep = fileobj_get_sizep(&file->stream);
409 file->offset = 0;
410
411 if (!created)
412 {
413 /* size from storage applies to first stream only otherwise it's
414 already up to date */
415 const bool first = fileobj_get_flags(&file->stream) & FO_SINGLE;
416 if (first)
417 *file->sizep = compinfo.filesize;
333 418
334 file->size = 0; 419 if (callflags & FO_TRUNC)
420 {
421 /* if the file is kind of "big" then free some space now */
422 rc = ftruncate_internal(file, 0, *file->sizep >= O_TRUNC_THRESH);
423 if (rc < 0)
424 {
425 DEBUGF("O_TRUNC failed: %d\n", rc);
426 FILE_ERROR(ERRNO, rc * 10 - 4);
427 }
428 }
429 }
335 430
336 rc = close(fd); 431 rc = 0;
337 if (rc<0) 432file_error:
338 return rc * 10 - 4; 433 if (rc < 0)
434 close_stream_internal(&file->stream);
339 435
340 return 0; 436 return rc;
341} 437}
342 438
343int rename(const char* path, const char* newpath) 439/* allocate a file descriptor, if needed, assemble stream flags and open
440 a new stream */
441static int open_internal_inner1(const char *path, int oflag,
442 unsigned int callflags)
344{ 443{
345 int rc, fd; 444 DEBUGF("%s(path=\"%s\",oflag=%X,callflags=%X)\n", __func__,
346 DIR_UNCACHED* dir; 445 path, oflag, callflags);
347 char* nameptr;
348 char* dirptr;
349 struct filedesc* file;
350 char newpath2[MAX_PATH];
351 446
352 /* verify new path does not already exist */ 447 int rc;
353 /* If it is a directory, errno == EISDIR if the name exists */
354 fd = open(newpath, O_RDONLY);
355 if ( fd >= 0 || errno == EISDIR) {
356 close(fd);
357 errno = EBUSY;
358 return -1;
359 }
360 close(fd);
361 448
362 fd = open_internal(path, O_RDONLY, false); 449 struct filestr_desc *file;
363 if ( fd < 0 ) { 450 int fildes = alloc_filestr(&file);
364 errno = EIO; 451 if (fildes < 0)
365 return fd * 10 - 2; 452 FILE_ERROR(EMFILE, -1);
366 }
367 453
368 /* extract new file name */ 454 callflags &= ~FDO_MASK;
369 nameptr = strrchr(newpath,'/');
370 if (nameptr)
371 nameptr++;
372 else {
373 close(fd);
374 return - 3;
375 }
376 455
377 /* Extract new path */ 456 if (oflag & O_ACCMODE)
378 strcpy(newpath2, newpath); 457 {
458 callflags |= FD_WRITE;
379 459
380 dirptr = strrchr(newpath2,'/'); 460 if ((oflag & O_ACCMODE) == O_WRONLY)
381 if(dirptr) 461 callflags |= FD_WRONLY;
382 *dirptr = 0;
383 else {
384 close(fd);
385 return - 4;
386 }
387 462
388 dirptr = newpath2; 463 if (oflag & O_APPEND)
464 callflags |= FD_APPEND;
389 465
390 if(strlen(dirptr) == 0) { 466 if (oflag & O_TRUNC)
391 dirptr = "/"; 467 callflags |= FO_TRUNC;
392 } 468 }
393 469 else if (oflag & O_TRUNC)
394 dir = opendir_uncached(dirptr); 470 {
395 if(!dir) { 471 /* O_TRUNC requires write mode */
396 close(fd); 472 DEBUGF("No write mode but have O_TRUNC\n");
397 return - 5; 473 FILE_ERROR(EINVAL, -2);
398 } 474 }
399 475
400 file = &openfiles[fd]; 476 /* O_CREAT and O_APPEND are fine without write mode
477 * for the former, an empty file is created but no data may be written
478 * for the latter, no append will be allowed anyway */
479 if (oflag & O_CREAT)
480 {
481 callflags |= FF_CREAT;
401 482
402 rc = fat_rename(&file->fatfile, &dir->fatdir, nameptr, 483 if (oflag & O_EXCL)
403 file->size, file->attr); 484 callflags |= FF_EXCL;
404#ifdef HAVE_MULTIVOLUME
405 if ( rc == -1) {
406 close(fd);
407 closedir_uncached(dir);
408 DEBUGF("Failed renaming file across volumnes: %d\n", rc);
409 errno = EXDEV;
410 return -6;
411 }
412#endif
413 if ( rc < 0 ) {
414 close(fd);
415 closedir_uncached(dir);
416 DEBUGF("Failed renaming file: %d\n", rc);
417 errno = EIO;
418 return rc * 10 - 7;
419 } 485 }
420 486
421#ifdef HAVE_DIRCACHE 487 rc = open_internal_inner2(path, file, callflags);
422 dircache_rename(path, newpath); 488 if (rc < 0)
423#endif 489 FILE_ERROR(ERRNO, rc * 10 - 3);
424 490
425 rc = close(fd); 491 return fildes;
426 if (rc<0) {
427 closedir_uncached(dir);
428 errno = EIO;
429 return rc * 10 - 8;
430 }
431 492
432 rc = closedir_uncached(dir); 493file_error:
433 if (rc<0) { 494 return rc;
434 errno = EIO; 495}
435 return rc * 10 - 9;
436 }
437 496
438 return 0; 497static int open_internal_locked(const char *path, int oflag,
498 unsigned int callflags)
499{
500 file_internal_lock_WRITER();
501 int rc = open_internal_inner1(path, oflag, callflags);
502 file_internal_unlock_WRITER();
503 return rc;
439} 504}
440 505
441int ftruncate(int fd, off_t size) 506/* fill a cache buffer with a new sector */
507static int readwrite_fill_cache(struct filestr_desc *file, unsigned long sector,
508 unsigned long filesectors, bool write)
442{ 509{
443 int rc, sector; 510 /* sector != cachep->sector should have been checked by now */
444 struct filedesc* file = &openfiles[fd];
445 511
446 sector = size / SECTOR_SIZE; 512 int rc;
447 if (size % SECTOR_SIZE) 513 struct filestr_cache *cachep = filestr_get_cache(&file->stream);
448 sector++;
449 514
450 rc = fat_seek(&(file->fatfile), sector); 515 if (cachep->flags & FSC_DIRTY)
451 if (rc < 0) { 516 {
452 errno = EIO; 517 rc = flush_cache(file);
453 return rc * 10 - 1; 518 if (rc < 0)
519 FILE_ERROR(ERRNO, rc * 10 - 1);
454 } 520 }
455 521
456 rc = fat_truncate(&(file->fatfile)); 522 if (fat_query_sectornum(&file->stream.fatstr) != sector)
457 if (rc < 0) { 523 {
458 errno = EIO; 524 /* get on the correct sector */
459 return rc * 10 - 2; 525 rc = fat_seek(&file->stream.fatstr, sector);
526 if (rc < 0)
527 FILE_ERROR(EIO, rc * 10 - 2);
460 } 528 }
461 529
462 file->size = size; 530 if (!write || sector < filesectors)
463#ifdef HAVE_DIRCACHE 531 {
464 dircache_update_filesize(fd, size, file->fatfile.firstcluster); 532 /* only reading or this sector would have been flushed if the cache
465#endif 533 was previously needed for a different sector */
534 rc = fat_readwrite(&file->stream.fatstr, 1, cachep->buffer, false);
535 if (rc < 0)
536 FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 3);
537 }
538 else
539 {
540 /* create a fresh, shiny, new sector with that new sector smell */
541 cachep->flags = FSC_NEW;
542 }
466 543
467 return 0; 544 cachep->sector = sector;
545 return 1;
546file_error:
547 DEBUGF("Failed caching sector: %d\n", rc);
548 return rc;
468} 549}
469 550
470static int flush_cache(int fd) 551/* read or write to part or all of the cache buffer */
552static inline void readwrite_cache(struct filestr_cache *cachep, void *buf,
553 unsigned long secoffset, size_t nbyte,
554 bool write)
471{ 555{
472 int rc; 556 void *dst, *cbufp = cachep->buffer + secoffset;
473 struct filedesc* file = &openfiles[fd];
474 long sector = file->fileoffset / SECTOR_SIZE;
475
476 DEBUGF("Flushing dirty sector cache\n");
477 557
478 /* make sure we are on correct sector */ 558 if (write)
479 rc = fat_seek(&(file->fatfile), sector); 559 {
480 if ( rc < 0 ) 560 dst = cbufp;
481 return rc * 10 - 3; 561 cachep->flags |= FSC_DIRTY;
482 562 }
483 rc = fat_readwrite(&(file->fatfile), 1, file->cache, true ); 563 else
564 {
565 dst = buf;
566 buf = cbufp;
567 }
484 568
485 if ( rc < 0 ) { 569 memcpy(dst, buf, nbyte);
486 if(file->fatfile.eof) 570}
487 errno = ENOSPC;
488 571
489 return rc * 10 - 2; 572/* read or write a partial sector using the file's cache */
573static inline ssize_t readwrite_partial(struct filestr_desc *file,
574 struct filestr_cache *cachep,
575 unsigned long sector,
576 unsigned long secoffset,
577 void *buf,
578 size_t nbyte,
579 unsigned long filesectors,
580 unsigned int flags)
581{
582 if (sector != cachep->sector)
583 {
584 /* wrong sector in buffer */
585 int rc = readwrite_fill_cache(file, sector, filesectors, flags);
586 if (rc <= 0)
587 return rc;
490 } 588 }
491 589
492 file->dirty = false; 590 readwrite_cache(cachep, buf, secoffset, nbyte, flags);
493 591 return nbyte;
494 return 0;
495} 592}
496 593
497static int readwrite(int fd, void* buf, long count, bool write) 594/* read from or write to the file; back end to read() and write() */
595static ssize_t readwrite(struct filestr_desc *file, void *buf, size_t nbyte,
596 bool write)
498{ 597{
499 long sectors; 598 DEBUGF("readwrite(%p,%lx,%ld,%s)\n",
500 long nread=0; 599 file, (long)buf, nbyte, write ? "write" : "read");
501 struct filedesc* file;
502 int rc;
503#ifdef STORAGE_NEEDS_ALIGN
504 long i;
505 int rc2;
506#endif
507 600
508 if (fd < 0 || fd > MAX_OPEN_FILES-1) { 601 const file_size_t size = *file->sizep;
509 errno = EINVAL; 602 file_size_t filerem;
510 return -1;
511 }
512 603
513 file = &openfiles[fd]; 604 if (write)
605 {
606 /* if opened in append mode, move pointer to end */
607 if (file->stream.flags & FD_APPEND)
608 file->offset = MIN(size, FILE_SIZE_MAX);
514 609
515 if ( !file->busy ) { 610 filerem = FILE_SIZE_MAX - file->offset;
516 errno = EBADF; 611 }
517 return -1; 612 else
613 {
614 /* limit to maximum possible offset (EOF or FILE_SIZE_MAX) */
615 filerem = MIN(size, FILE_SIZE_MAX) - file->offset;
518 } 616 }
519 617
520 if(file->attr & FAT_ATTR_DIRECTORY) { 618 if (nbyte > filerem)
521 errno = EISDIR; 619 {
522 return -1; 620 nbyte = filerem;
621 if (nbyte > 0)
622 {}
623 else if (write)
624 FILE_ERROR_RETURN(EFBIG, -1); /* would get too large */
625 else if (file->offset >= FILE_SIZE_MAX)
626 FILE_ERROR_RETURN(EOVERFLOW, -2); /* can't read here */
523 } 627 }
524 628
525 LDEBUGF( "readwrite(%d,%lx,%ld,%s)\n", 629 if (nbyte == 0)
526 fd,(long)buf,count,write?"write":"read"); 630 return 0;
527 631
528 /* attempt to read past EOF? */ 632 int rc = 0;
529 if (!write && count > file->size - file->fileoffset) 633
530 count = file->size - file->fileoffset; 634 struct filestr_cache * const cachep = file->stream.cachep;
635 void * const bufstart = buf;
636
637 const unsigned long filesectors = filesize_sectors(size);
638 unsigned long sector = file->offset / SECTOR_SIZE;
639 unsigned long sectoroffs = file->offset % SECTOR_SIZE;
531 640
532 /* any head bytes? */ 641 /* any head bytes? */
533 if ( file->cacheoffset != -1 ) { 642 if (sectoroffs)
534 int offs = file->cacheoffset; 643 {
535 int headbytes = MIN(count, SECTOR_SIZE - offs); 644 size_t headbytes = MIN(nbyte, SECTOR_SIZE - sectoroffs);
645 rc = readwrite_partial(file, cachep, sector, sectoroffs, buf, headbytes,
646 filesectors, write);
647 if (rc <= 0)
648 {
649 if (rc < 0)
650 FILE_ERROR(ERRNO, rc * 10 - 3);
536 651
537 if (write) { 652 nbyte = 0; /* eof, skip the rest */
538 memcpy( file->cache + offs, buf, headbytes );
539 file->dirty = true;
540 } 653 }
541 else { 654 else
542 memcpy( buf, file->cache + offs, headbytes ); 655 {
656 buf += rc;
657 nbyte -= rc;
658 sector++; /* if nbyte goes to 0, the rest is skipped anyway */
543 } 659 }
660 }
544 661
545 if (offs + headbytes == SECTOR_SIZE) { 662 /* read/write whole sectors right into/from the supplied buffer */
546 if (file->dirty) { 663 unsigned long sectorcount = nbyte / SECTOR_SIZE;
547 rc = flush_cache(fd);
548 if ( rc < 0 ) {
549 errno = EIO;
550 return rc * 10 - 2;
551 }
552 }
553 file->cacheoffset = -1;
554 }
555 else {
556 file->cacheoffset += headbytes;
557 }
558 664
559 nread = headbytes; 665 while (sectorcount)
560 count -= headbytes; 666 {
561 } 667 unsigned long runlen = sectorcount;
562 668
563 /* If the buffer has been modified, either it has been flushed already 669 /* if a cached sector is inside the transfer range, split the transfer
564 * (if (offs+headbytes == SECTOR_SIZE)...) or does not need to be (no 670 into two parts and use the cache for that sector to keep it coherent
565 * more data to follow in this call). Do NOT flush here. */ 671 without writeback */
672 if (UNLIKELY(cachep->sector >= sector &&
673 cachep->sector < sector + sectorcount))
674 {
675 runlen = cachep->sector - sector;
676 }
566 677
567 /* read/write whole sectors right into/from the supplied buffer */ 678 if (runlen)
568 sectors = count / SECTOR_SIZE; 679 {
569 rc = 0; 680 if (fat_query_sectornum(&file->stream.fatstr) != sector)
570 if ( sectors ) {
571#ifdef STORAGE_NEEDS_ALIGN
572 if (((uint32_t)buf + nread) & (CACHEALIGN_SIZE - 1))
573 for (i = 0; i < sectors; i++)
574 { 681 {
575 if (write) memcpy(file->cache, buf+nread+i*SECTOR_SIZE, SECTOR_SIZE); 682 /* get on the correct sector */
576 rc2 = fat_readwrite(&(file->fatfile), 1, file->cache, write ); 683 rc = 0;
577 if (rc2 < 0) 684
685 /* If the dirty bit isn't set, we're somehow beyond the file
686 size and you can't explain _that_ */
687 if (sector >= filesectors && cachep->flags == (FSC_NEW|FSC_DIRTY))
578 { 688 {
579 rc = rc2; 689 rc = flush_cache(file);
580 break; 690 if (rc < 0)
691 FILE_ERROR(ERRNO, rc * 10 - 4);
692
693 if (cachep->sector + 1 == sector)
694 rc = 1; /* if now ok, don't seek */
695 }
696
697 if (rc == 0)
698 {
699 rc = fat_seek(&file->stream.fatstr, sector);
700 if (rc < 0)
701 FILE_ERROR(EIO, rc * 10 - 5);
581 } 702 }
582 else rc += rc2;
583 if (!write) memcpy(buf+nread+i*SECTOR_SIZE, file->cache, SECTOR_SIZE);
584 }
585 else
586#endif
587 rc = fat_readwrite(&(file->fatfile), sectors, (unsigned char*)buf+nread, write );
588 if ( rc < 0 ) {
589 DEBUGF("Failed read/writing %ld sectors\n",sectors);
590 errno = EIO;
591 if(write && file->fatfile.eof) {
592 DEBUGF("No space left on device\n");
593 errno = ENOSPC;
594 } else {
595 file->fileoffset += nread;
596 } 703 }
597 file->cacheoffset = -1; 704
598 /* adjust file size to length written */ 705 rc = fat_readwrite(&file->stream.fatstr, runlen, buf, write);
599 if ( write && file->fileoffset > file->size ) 706 if (rc < 0)
600 { 707 {
601 file->size = file->fileoffset; 708 DEBUGF("I/O error %sing %ld sectors\n", sectors,
602#ifdef HAVE_DIRCACHE 709 write ? "writ" : "read");
603 dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); 710 FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO,
604#endif 711 rc * 10 - 6);
605 } 712 }
606 return nread ? nread : rc * 10 - 4; 713 else
607 } 714 {
608 else { 715 buf += rc * SECTOR_SIZE;
609 if ( rc > 0 ) { 716 nbyte -= rc * SECTOR_SIZE;
610 nread += rc * SECTOR_SIZE; 717 sector += rc;
611 count -= sectors * SECTOR_SIZE; 718 sectorcount -= rc;
612 719
613 /* if eof, skip tail bytes */ 720 /* if eof, skip tail bytes */
614 if ( rc < sectors ) 721 if ((unsigned long)rc < runlen)
615 count = 0; 722 nbyte = 0;
616 } 723
617 else { 724 if (!nbyte)
618 /* eof */ 725 break;
619 count=0;
620 } 726 }
727 }
621 728
622 file->cacheoffset = -1; 729 if (UNLIKELY(sectorcount && sector == cachep->sector))
730 {
731 /* do this one sector with the cache */
732 readwrite_cache(cachep, buf, 0, SECTOR_SIZE, write);
733 buf += SECTOR_SIZE;
734 nbyte -= SECTOR_SIZE;
735 sector++;
736 sectorcount--;
623 } 737 }
624 } 738 }
625 739
626 /* any tail bytes? */ 740 /* any tail bytes? */
627 if ( count ) { 741 if (nbyte)
628 if (write) { 742 {
629 if ( file->fileoffset + nread < file->size ) { 743 /* tail bytes always start at sector offset 0 */
630 /* sector is only partially filled. copy-back from disk */ 744 rc = readwrite_partial(file, cachep, sector, 0, buf, nbyte,
631 LDEBUGF("Copy-back tail cache\n"); 745 filesectors, write);
632 rc = fat_readwrite(&(file->fatfile), 1, file->cache, false ); 746 if (rc < 0)
633 if ( rc < 0 ) { 747 FILE_ERROR(ERRNO, rc * 10 - 7);
634 DEBUGF("Failed writing\n");
635 errno = EIO;
636 file->fileoffset += nread;
637 file->cacheoffset = -1;
638 /* adjust file size to length written */
639 if ( file->fileoffset > file->size )
640 {
641 file->size = file->fileoffset;
642#ifdef HAVE_DIRCACHE
643 dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
644#endif
645 }
646 return nread ? nread : rc * 10 - 5;
647 }
648 /* seek back one sector to put file position right */
649 rc = fat_seek(&(file->fatfile),
650 (file->fileoffset + nread) /
651 SECTOR_SIZE);
652 if ( rc < 0 ) {
653 DEBUGF("fat_seek() failed\n");
654 errno = EIO;
655 file->fileoffset += nread;
656 file->cacheoffset = -1;
657 /* adjust file size to length written */
658 if ( file->fileoffset > file->size )
659 {
660 file->size = file->fileoffset;
661#ifdef HAVE_DIRCACHE
662 dircache_update_filesize(fd, file->size, file->fatfile.firstcluster);
663#endif
664 }
665 return nread ? nread : rc * 10 - 6;
666 }
667 }
668 memcpy( file->cache, (unsigned char*)buf + nread, count );
669 file->dirty = true;
670 }
671 else {
672 rc = fat_readwrite(&(file->fatfile), 1, file->cache,false);
673 if (rc < 1 ) {
674 DEBUGF("Failed caching sector\n");
675 errno = EIO;
676 file->fileoffset += nread;
677 file->cacheoffset = -1;
678 return nread ? nread : rc * 10 - 7;
679 }
680 memcpy( (unsigned char*)buf + nread, file->cache, count );
681 }
682 748
683 nread += count; 749 buf += rc;
684 file->cacheoffset = count;
685 } 750 }
686 751
687 file->fileoffset += nread; 752file_error:;
688 LDEBUGF("fileoffset: %ld\n", file->fileoffset); 753#ifdef DEBUG
754 if (errno == ENOSPC)
755 DEBUGF("No space left on device\n");
756#endif
689 757
690 /* adjust file size to length written */ 758 size_t done = buf - bufstart;
691 if ( write && file->fileoffset > file->size ) 759 if (done)
692 { 760 {
693 file->size = file->fileoffset; 761 /* error or not, update the file offset and size if anything was
694#ifdef HAVE_DIRCACHE 762 transferred */
695 dircache_update_filesize(fd, file->size, file->fatfile.firstcluster); 763 file->offset += done;
696#endif 764 DEBUGF("file offset: %ld\n", file->offset);
765
766 /* adjust file size to length written */
767 if (write && file->offset > size)
768 *file->sizep = file->offset;
769
770 if (rc > 0)
771 return done;
697 } 772 }
698 773
699 return nread; 774 return rc;
700} 775}
701 776
702ssize_t write(int fd, const void* buf, size_t count) 777
778/** Internal interface **/
779
780/* open a file without codepage conversion during the directory search;
781 required to avoid any reentrancy when opening codepages and when scanning
782 directories internally, which could infinitely recurse and would corrupt
783 the static data */
784int open_noiso_internal(const char *path, int oflag)
703{ 785{
704 if (!openfiles[fd].write) { 786 return open_internal_locked(path, oflag, FF_ANYTYPE | FF_NOISO);
705 errno = EACCES;
706 return -1;
707 }
708 return readwrite(fd, (void *)buf, count, true);
709} 787}
710 788
711ssize_t read(int fd, void* buf, size_t count) 789
790/** POSIX **/
791
792/* open a file */
793int open(const char *path, int oflag)
712{ 794{
713 return readwrite(fd, buf, count, false); 795 DEBUGF("open(path=\"%s\",oflag=%X)\n", path, (unsigned)oflag);
796 return open_internal_locked(path, oflag, FF_ANYTYPE);
714} 797}
715 798
799/* create a new file or rewrite an existing one */
800int creat(const char *path)
801{
802 DEBUGF("creat(path=\"%s\")\n", path);
803 return open_internal_locked(path, O_WRONLY|O_CREAT|O_TRUNC, FF_ANYTYPE);
804}
716 805
717off_t lseek(int fd, off_t offset, int whence) 806/* close a file descriptor */
807int close(int fildes)
718{ 808{
719 off_t pos; 809 DEBUGF("close(fd=%d)\n", fildes);
720 long newsector; 810
721 long oldsector;
722 int sectoroffset;
723 int rc; 811 int rc;
724 struct filedesc* file = &openfiles[fd];
725 812
726 LDEBUGF("lseek(%d,%ld,%d)\n",fd,offset,whence); 813 file_internal_lock_WRITER();
727 814
728 if (fd < 0 || fd > MAX_OPEN_FILES-1) { 815 /* needs to work even if marked "nonexistant" */
729 errno = EINVAL; 816 struct filestr_desc *file = &open_streams[fildes];
730 return -1; 817 if ((unsigned int)fildes >= MAX_OPEN_FILES || !file->stream.flags)
731 } 818 {
732 if ( !file->busy ) { 819 DEBUGF("filedes %d not open\n", fildes);
733 errno = EBADF; 820 FILE_ERROR(EBADF, -2);
734 return -1;
735 } 821 }
736 822
737 switch ( whence ) { 823 rc = close_internal(file);
738 case SEEK_SET: 824 if (rc < 0)
739 pos = offset; 825 FILE_ERROR(ERRNO, rc * 10 - 3);
740 break;
741 826
742 case SEEK_CUR: 827file_error:
743 pos = file->fileoffset + offset; 828 file_internal_unlock_WRITER();
744 break; 829 return rc;
830}
831
832/* truncate a file to a specified length */
833int ftruncate(int fildes, off_t length)
834{
835 DEBUGF("ftruncate(fd=%d,len=%ld)\n", fildes, (long)length);
745 836
746 case SEEK_END: 837 struct filestr_desc * const file = GET_FILESTR(READER, fildes);
747 pos = file->size + offset; 838 if (!file)
748 break; 839 FILE_ERROR_RETURN(ERRNO, -1);
749 840
750 default: 841 int rc;
751 errno = EINVAL; 842
752 return -2; 843 if (!(file->stream.flags & FD_WRITE))
844 {
845 DEBUGF("Descriptor is read-only mode\n");
846 FILE_ERROR(EBADF, -2);
753 } 847 }
754 if ((pos < 0) || (pos > file->size)) { 848
755 errno = EINVAL; 849 if (length < 0)
756 return -3; 850 {
851 DEBUGF("Length %ld is invalid\n", (long)length);
852 FILE_ERROR(EINVAL, -3);
757 } 853 }
758 854
759 /* new sector? */ 855 rc = ftruncate_internal(file, length, true);
760 newsector = pos / SECTOR_SIZE; 856 if (rc < 0)
761 oldsector = file->fileoffset / SECTOR_SIZE; 857 FILE_ERROR(ERRNO, rc * 10 - 4);
762 sectoroffset = pos % SECTOR_SIZE;
763 858
764 if ( (newsector != oldsector) || 859file_error:
765 ((file->cacheoffset==-1) && sectoroffset) ) { 860 RELEASE_FILESTR(READER, file);
861 return rc;
862}
766 863
767 if ( newsector != oldsector ) { 864/* synchronize changes to a file */
768 if (file->dirty) { 865int fsync(int fildes)
769 rc = flush_cache(fd); 866{
770 if (rc < 0) 867 DEBUGF("fsync(fd=%d)\n", fildes);
771 return rc * 10 - 5;
772 }
773 868
774 rc = fat_seek(&(file->fatfile), newsector); 869 struct filestr_desc * const file = GET_FILESTR(WRITER, fildes);
775 if ( rc < 0 ) { 870 if (!file)
776 errno = EIO; 871 FILE_ERROR_RETURN(ERRNO, -1);
777 return rc * 10 - 4; 872
778 } 873 int rc;
779 } 874
780 if ( sectoroffset ) { 875 if (!(file->stream.flags & FD_WRITE))
781 rc = fat_readwrite(&(file->fatfile), 1, file->cache ,false); 876 {
782 if ( rc < 0 ) { 877 DEBUGF("Descriptor is read-only mode\n", fd);
783 errno = EIO; 878 FILE_ERROR(EINVAL, -2);
784 return rc * 10 - 6;
785 }
786 file->cacheoffset = sectoroffset;
787 }
788 else
789 file->cacheoffset = -1;
790 } 879 }
791 else
792 if ( file->cacheoffset != -1 )
793 file->cacheoffset = sectoroffset;
794 880
795 file->fileoffset = pos; 881 rc = fsync_internal(file);
882 if (rc < 0)
883 FILE_ERROR(ERRNO, rc * 10 - 3);
796 884
797 return pos; 885file_error:
886 RELEASE_FILESTR(WRITER, file);
887 return rc;
798} 888}
799 889
800off_t filesize(int fd) 890/* move the read/write file offset */
891off_t lseek(int fildes, off_t offset, int whence)
801{ 892{
802 struct filedesc* file = &openfiles[fd]; 893 DEBUGF("lseek(fd=%d,ofs=%ld,wh=%d)\n", fildes, (long)offset, whence);
803 894
804 if (fd < 0 || fd > MAX_OPEN_FILES-1) { 895 struct filestr_desc * const file = GET_FILESTR(READER, fildes);
805 errno = EINVAL; 896 if (!file)
806 return -1; 897 FILE_ERROR_RETURN(ERRNO, -1);
898
899 off_t rc = lseek_internal(file, offset, whence);
900 if (rc < 0)
901 FILE_ERROR(ERRNO, rc * 10 - 2);
902
903file_error:
904 RELEASE_FILESTR(READER, file);
905 return rc;
906}
907
908/* read from a file */
909ssize_t read(int fildes, void *buf, size_t nbyte)
910{
911 struct filestr_desc * const file = GET_FILESTR(READER, fildes);
912 if (!file)
913 FILE_ERROR_RETURN(ERRNO, -1);
914
915 ssize_t rc;
916
917 if (file->stream.flags & FD_WRONLY)
918 {
919 DEBUGF("read(fd=%d,buf=%p,nb=%lu) - "
920 "descriptor is write-only mode\n", fildes, buf, nbyte);
921 FILE_ERROR(EBADF, -2);
807 } 922 }
808 if ( !file->busy ) { 923
809 errno = EBADF; 924 rc = readwrite(file, buf, nbyte, false);
810 return -1; 925 if (rc < 0)
926 FILE_ERROR(ERRNO, rc * 10 - 3);
927
928file_error:
929 RELEASE_FILESTR(READER, file);
930 return rc;
931}
932
933/* write on a file */
934ssize_t write(int fildes, const void *buf, size_t nbyte)
935{
936 struct filestr_desc * const file = GET_FILESTR(READER, fildes);
937 if (!file)
938 FILE_ERROR_RETURN(ERRNO, -1);
939
940 ssize_t rc;
941
942 if (!(file->stream.flags & FD_WRITE))
943 {
944 DEBUGF("write(fd=%d,buf=%p,nb=%lu) - "
945 "descriptor is read-only mode\n", fildes, buf, nbyte);
946 FILE_ERROR(EBADF, -2);
811 } 947 }
812 948
813 return file->size; 949 rc = readwrite(file, (void *)buf, nbyte, true);
950 if (rc < 0)
951 FILE_ERROR(ERRNO, rc * 10 - 3);
952
953file_error:
954 RELEASE_FILESTR(READER, file);
955 return rc;
814} 956}
815 957
958/* remove a file */
959int remove(const char *path)
960{
961 DEBUGF("remove(path=\"%s\")\n", path);
962
963 file_internal_lock_WRITER();
964 int rc = remove_stream_internal(path, NULL, FF_FILE);
965 file_internal_unlock_WRITER();
966 return rc;
967}
816 968
817/* release all file handles on a given volume "by force", to avoid leaks */ 969/* rename a file */
818int release_files(int volume) 970int rename(const char *old, const char *new)
819{ 971{
820 struct filedesc* pfile = openfiles; 972 DEBUGF("rename(old=\"%s\",new=\"%s\")\n", old, new);
821 int fd; 973
822 int closed = 0; 974 int rc, open1rc = -1, open2rc = -1;
823 for ( fd=0; fd<MAX_OPEN_FILES; fd++, pfile++) 975 struct filestr_base oldstr, newstr;
976 struct path_component_info oldinfo, newinfo;
977
978 file_internal_lock_WRITER();
979
980 /* open 'old'; it must exist */
981 open1rc = open_stream_internal(old, FF_ANYTYPE, &oldstr, &oldinfo);
982 if (open1rc <= 0)
824 { 983 {
984 DEBUGF("Failed opening old: %d\n", rc);
985 if (open1rc == 0)
986 FILE_ERROR(ENOENT, -1);
987 else
988 FILE_ERROR(ERRNO, open1rc * 10 - 1);
989 }
990
991 /* if 'old' is a directory then 'new' is also required to be one if 'new'
992 is to be overwritten */
993 const bool are_dirs = oldinfo.attr & ATTR_DIRECTORY;
994
995 /* open new (may or may not exist) */
996 unsigned int callflags = FF_FILE;
997 if (are_dirs)
998 {
999 /* if 'old' is found while parsing the new directory components then
1000 'new' contains path prefix that names 'old'; if new and old are in
1001 the same directory, this tests positive but that is checked later */
1002 callflags = FF_DIR | FF_CHECKPREFIX;
1003 newinfo.prefixp = oldstr.infop;
1004 }
1005
1006 open2rc = open_stream_internal(new, callflags, &newstr, &newinfo);
1007 if (open2rc < 0)
1008 {
1009 DEBUGF("Failed opening new file: %d\n", rc);
1010 FILE_ERROR(ERRNO, open2rc * 10 - 2);
1011 }
1012
825#ifdef HAVE_MULTIVOLUME 1013#ifdef HAVE_MULTIVOLUME
826 if (pfile->fatfile.volume == volume) 1014 if (oldinfo.parentinfo.volume != newinfo.parentinfo.volume)
827#else 1015 {
828 (void)volume; 1016 DEBUGF("Cross-device link\n");
829#endif 1017 FILE_ERROR(EXDEV, -3);
1018 }
1019#endif /* HAVE_MULTIVOLUME */
1020
1021 /* if the parent is changing then this is a move, not a simple rename */
1022 const bool is_move = !fat_file_is_same(&oldinfo.parentinfo.fatfile,
1023 &newinfo.parentinfo.fatfile);
1024 /* prefix found and moving? */
1025 if (is_move && (newinfo.attr & ATTR_PREFIX))
1026 {
1027 DEBUGF("New contains prefix that names old\n");
1028 FILE_ERROR(EINVAL, -4);
1029 }
1030
1031 const char * const oldname = strmemdupa(oldinfo.name, oldinfo.length);
1032 const char * const newname = strmemdupa(newinfo.name, newinfo.length);
1033 bool is_overwrite = false;
1034
1035 if (open2rc > 0)
1036 {
1037 /* new name exists in parent; check if 'old' is overwriting 'new';
1038 if it's the very same file, then it's just a rename */
1039 is_overwrite = oldstr.bindp != newstr.bindp;
1040
1041 if (is_overwrite)
1042 {
1043 if (are_dirs)
1044 {
1045 /* the directory to be overwritten must be empty */
1046 rc = test_dir_empty_internal(&newstr);
1047 if (rc < 0)
1048 FILE_ERROR(ERRNO, rc * 10 - 5);
1049 }
1050 }
1051 else if (!strcmp(newname, oldname)) /* case-only is ok */
1052 {
1053 DEBUGF("No name change (success)\n");
1054 rc = 0;
1055 FILE_ERROR(ERRNO, RC);
1056 }
1057 }
1058 else if (!are_dirs && (newinfo.attr & ATTR_DIRECTORY))
1059 {
1060 /* even if new doesn't exist, canonical path type must match
1061 (ie. a directory path such as "/foo/bar/" when old names a file) */
1062 DEBUGF("New path is a directory\n");
1063 FILE_ERROR(EISDIR, -6);
1064 }
1065
1066 /* first, create the new entry so that there's never a time that the
1067 victim's data has no reference in the directory tree, that is, until
1068 everything else first succeeds */
1069 struct file_base_info old_fileinfo = *oldstr.infop;
1070 rc = fat_rename(&newinfo.parentinfo.fatfile, &oldstr.infop->fatfile,
1071 newname);
1072 if (rc < 0)
1073 {
1074 DEBUGF("I/O error renaming file: %d\n", rc);
1075 FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 7);
1076 }
1077
1078 if (is_overwrite)
1079 {
1080 /* 'new' would have been assigned its own directory entry and
1081 succeeded so at this point it is treated like a remove() call
1082 on the victim which preserves data until the last reference is
1083 closed */
1084 rc = remove_stream_internal(NULL, &newstr, callflags);
1085 if (rc < 0)
1086 FILE_ERROR(ERRNO, rc * 10 - 8);
1087 }
1088
1089 fileop_onrename_internal(&oldstr, is_move ? &old_fileinfo : NULL,
1090 &newinfo.parentinfo, newname);
1091
1092file_error:
1093 /* for now, there is nothing to fail upon closing the old stream */
1094 if (open1rc >= 0)
1095 close_stream_internal(&oldstr);
1096
1097 /* the 'new' stream could fail to close cleanly because it became
1098 impossible to remove its data if this was an overwrite operation */
1099 if (open2rc >= 0)
1100 {
1101 int rc2 = close_stream_internal(&newstr);
1102 if (rc2 < 0 && rc >= 0)
830 { 1103 {
831 pfile->busy = false; /* mark as available, no further action */ 1104 DEBUGF("Success but failed closing new: %d\n", rc2);
832 closed++; 1105 rc = rc2 * 10 - 9;
833 } 1106 }
834 } 1107 }
835 return closed; /* return how many we did */ 1108
1109 file_internal_unlock_WRITER();
1110 return rc;
1111}
1112
1113
1114/** Extensions **/
1115
1116/* get the binary size of a file (in bytes) */
1117off_t filesize(int fildes)
1118{
1119 struct filestr_desc * const file = GET_FILESTR(READER, fildes);
1120 if (!file)
1121 FILE_ERROR_RETURN(ERRNO, -1);
1122
1123 off_t rc;
1124 file_size_t size = *file->sizep;
1125
1126 if (size > FILE_SIZE_MAX)
1127 FILE_ERROR(EOVERFLOW, -2);
1128
1129 rc = (off_t)size;
1130file_error:
1131 RELEASE_FILESTR(READER, file);
1132 return rc;
1133}
1134
1135/* test if two file descriptors refer to the same file */
1136int fsamefile(int fildes1, int fildes2)
1137{
1138 struct filestr_desc * const file1 = GET_FILESTR(WRITER, fildes1);
1139 if (!file1)
1140 FILE_ERROR_RETURN(ERRNO, -1);
1141
1142 int rc = -2;
1143
1144 struct filestr_desc * const file2 = get_filestr(fildes2);
1145 if (file2)
1146 rc = file1->stream.bindp == file2->stream.bindp ? 1 : 0;
1147
1148 RELEASE_FILESTR(WRITER, file1);
1149 return rc;
1150}
1151
1152/* tell the relationship of path1 to path2 */
1153int relate(const char *path1, const char *path2)
1154{
1155 /* this is basically what rename() does but reduced to the relationship
1156 determination */
1157 DEBUGF("relate(path1=\"%s\",path2=\"%s\")\n", path1, path2);
1158
1159 int rc, open1rc = -1, open2rc = -1;
1160 struct filestr_base str1, str2;
1161 struct path_component_info info1, info2;
1162
1163 file_internal_lock_WRITER();
1164
1165 open1rc = open_stream_internal(path1, FF_ANYTYPE, &str1, &info1);
1166 if (open1rc <= 0)
1167 {
1168 DEBUGF("Failed opening path1: %d\n", rc);
1169 if (open1rc < 0)
1170 FILE_ERROR(ERRNO, open1rc * 10 - 1);
1171 else
1172 FILE_ERROR(ENOENT, -1);
1173 }
1174
1175 info2.prefixp = str1.infop;
1176 open2rc = open_stream_internal(path2, FF_ANYTYPE | FF_CHECKPREFIX,
1177 &str2, &info2);
1178 if (open2rc < 0)
1179 {
1180 DEBUGF("Failed opening path2: %d\n", rc);
1181 FILE_ERROR(ERRNO, open2rc * 10 - 2);
1182 }
1183
1184 rc = RELATE_DIFFERENT;
1185
1186 if (open2rc > 0)
1187 {
1188 if (str1.bindp == str2.bindp)
1189 rc = RELATE_SAME;
1190 else if (info2.attr & ATTR_PREFIX)
1191 rc = RELATE_PREFIX;
1192 }
1193 else /* open2rc == 0 */
1194 {
1195 /* path1 existing and path2's final part not can only be a prefix or
1196 different */
1197 if (info2.attr & ATTR_PREFIX)
1198 rc = RELATE_PREFIX;
1199 }
1200
1201file_error:
1202 if (open1rc >= 0)
1203 close_stream_internal(&str1);
1204
1205 if (open2rc >= 0)
1206 close_stream_internal(&str2);
1207
1208 file_internal_unlock_WRITER();
1209 return rc;
1210}
1211
1212/* test file or directory existence */
1213bool file_exists(const char *path)
1214{
1215 file_internal_lock_WRITER();
1216 bool rc = test_stream_exists_internal(path, FF_ANYTYPE) > 0;
1217 file_internal_unlock_WRITER();
1218 return rc;
836} 1219}
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "config.h"
22#include <errno.h>
23#include "system.h"
24#include "debug.h"
25#include "panic.h"
26#include "pathfuncs.h"
27#include "disk_cache.h"
28#include "fileobj_mgr.h"
29#include "dir.h"
30#include "dircache_redirect.h"
31#include "dircache.h"
32#include "string-extra.h"
33#include "rbunicode.h"
34
35/** Internal common filesystem service functions **/
36
37/* for internal functions' scanning use to save quite a bit of stack space -
38 access must be serialized by the writer lock */
39#if defined(CPU_SH) || defined(IAUDIO_M5)
40/* otherwise, out of IRAM */
41struct fat_direntry dir_fatent;
42#else
43struct fat_direntry dir_fatent IBSS_ATTR;
44#endif
45
46struct mrsw_lock file_internal_mrsw SHAREDBSS_ATTR;
47
48
49/** File stream sector caching **/
50
51/* initialize a new cache structure */
52void file_cache_init(struct filestr_cache *cachep)
53{
54 cachep->buffer = NULL;
55 cachep->sector = INVALID_SECNUM;
56 cachep->flags = 0;
57}
58
59/* discard and mark the cache buffer as unused */
60void file_cache_reset(struct filestr_cache *cachep)
61{
62 cachep->sector = INVALID_SECNUM;
63 cachep->flags = 0;
64}
65
66/* allocate resources attached to the cache */
67void file_cache_alloc(struct filestr_cache *cachep)
68{
69 /* if this fails, it is a bug; check for leaks and that the cache has
70 enough buffers for the worst case */
71 if (!cachep->buffer && !(cachep->buffer = dc_get_buffer()))
72 panicf("file_cache_alloc - OOM");
73}
74
75/* free resources attached to the cache */
76void file_cache_free(struct filestr_cache *cachep)
77{
78 if (cachep && cachep->buffer)
79 {
80 dc_release_buffer(cachep->buffer);
81 cachep->buffer = NULL;
82 }
83
84 file_cache_reset(cachep);
85}
86
87
88/** Stream base APIs **/
89
90static inline void filestr_clear(struct filestr_base *stream)
91{
92 stream->flags = 0;
93 stream->bindp = NULL;
94#if 0
95 stream->mtx = NULL;
96#endif
97}
98
99/* actually late-allocate the assigned cache */
100void filestr_alloc_cache(struct filestr_base *stream)
101{
102 file_cache_alloc(stream->cachep);
103}
104
105/* free the stream's cache buffer if it's its own */
106void filestr_free_cache(struct filestr_base *stream)
107{
108 if (stream->cachep == &stream->cache)
109 file_cache_free(stream->cachep);
110}
111
112/* assign a cache to the stream */
113void filestr_assign_cache(struct filestr_base *stream,
114 struct filestr_cache *cachep)
115{
116 if (cachep)
117 {
118 filestr_free_cache(stream);
119 stream->cachep = cachep;
120 }
121 else /* assign own cache */
122 {
123 file_cache_reset(&stream->cache);
124 stream->cachep = &stream->cache;
125 }
126}
127
128/* duplicate a cache into a stream's local cache */
129void filestr_copy_cache(struct filestr_base *stream,
130 struct filestr_cache *cachep)
131{
132 stream->cachep = &stream->cache;
133 stream->cache.sector = cachep->sector;
134 stream->cache.flags = cachep->flags;
135
136 if (cachep->buffer)
137 {
138 file_cache_alloc(&stream->cache);
139 memcpy(stream->cache.buffer, cachep->buffer, DC_CACHE_BUFSIZE);
140 }
141 else
142 {
143 file_cache_free(&stream->cache);
144 }
145}
146
147/* discard cache contents and invalidate it */
148void filestr_discard_cache(struct filestr_base *stream)
149{
150 file_cache_reset(stream->cachep);
151}
152
153/* Initialize the base descriptor */
154void filestr_base_init(struct filestr_base *stream)
155{
156 filestr_clear(stream);
157 file_cache_init(&stream->cache);
158 stream->cachep = &stream->cache;
159}
160
161/* free base descriptor resources */
162void filestr_base_destroy(struct filestr_base *stream)
163{
164 filestr_clear(stream);
165 filestr_free_cache(stream);
166}
167
168
169/** Internal directory service functions **/
170
171/* read the next directory entry and return its FS info */
172int uncached_readdir_internal(struct filestr_base *stream,
173 struct file_base_info *infop,
174 struct fat_direntry *fatent)
175{
176 return fat_readdir(&stream->fatstr, &infop->fatfile.e,
177 filestr_get_cache(stream), fatent);
178}
179
180/* rewind the FS directory to the beginning */
181void uncached_rewinddir_internal(struct file_base_info *infop)
182{
183 fat_rewinddir(&infop->fatfile.e);
184}
185
186/* check if the directory is empty (ie. only "." and/or ".." entries
187 exist at most) */
188int test_dir_empty_internal(struct filestr_base *stream)
189{
190 int rc;
191
192 struct file_base_info info;
193 fat_rewind(&stream->fatstr);
194 rewinddir_internal(&info);
195
196 while ((rc = readdir_internal(stream, &info, &dir_fatent)) > 0)
197 {
198 /* no OEM decoding is recessary for this simple check */
199 if (!is_dotdir_name(dir_fatent.name))
200 {
201 DEBUGF("Directory not empty\n");
202 FILE_ERROR_RETURN(ENOTEMPTY, -1);
203 }
204 }
205
206 if (rc < 0)
207 {
208 DEBUGF("I/O error checking directory: %d\n", rc);
209 FILE_ERROR_RETURN(EIO, rc * 10 - 2);
210 }
211
212 return 0;
213}
214
215/* iso decode the name to UTF-8 */
216void iso_decode_d_name(char *d_name)
217{
218 if (is_dotdir_name(d_name))
219 return;
220
221 char shortname[13];
222 size_t len = strlcpy(shortname, d_name, sizeof (shortname));
223 /* This MUST be the default codepage thus not something that could be
224 loaded on call */
225 iso_decode(shortname, d_name, -1, len + 1);
226}
227
228#ifdef HAVE_DIRCACHE
229/* nullify all the fields of the struct dirent */
230void empty_dirent(struct dirent *entry)
231{
232 entry->d_name[0] = '\0';
233 entry->info.attr = 0;
234 entry->info.size = 0;
235 entry->info.wrtdate = 0;
236 entry->info.wrttime = 0;
237}
238
239/* fill the native dirinfo from the static dir_fatent */
240void fill_dirinfo_native(struct dirinfo_native *dinp)
241{
242 struct fat_direntry *fatent = get_dir_fatent();
243 dinp->attr = fatent->attr;
244 dinp->size = fatent->filesize;
245 dinp->wrtdate = fatent->wrtdate;
246 dinp->wrttime = fatent->wrttime;
247}
248#endif /* HAVE_DIRCACHE */
249
250int uncached_readdir_dirent(struct filestr_base *stream,
251 struct dirscan_info *scanp,
252 struct dirent *entry)
253{
254 struct fat_direntry fatent;
255 int rc = fat_readdir(&stream->fatstr, &scanp->fatscan,
256 filestr_get_cache(stream), &fatent);
257
258 /* FAT driver clears the struct fat_dirent if nothing is returned */
259 strcpy(entry->d_name, fatent.name);
260 entry->info.attr = fatent.attr;
261 entry->info.size = fatent.filesize;
262 entry->info.wrtdate = fatent.wrtdate;
263 entry->info.wrttime = fatent.wrttime;
264
265 return rc;
266}
267
268/* rewind the FS directory pointer */
269void uncached_rewinddir_dirent(struct dirscan_info *scanp)
270{
271 fat_rewinddir(&scanp->fatscan);
272}
273
274
275/** open_stream_internal() helpers and types **/
276
277struct pathwalk
278{
279 const char *path; /* current location in input path */
280 unsigned int callflags; /* callflags parameter */
281 struct path_component_info *compinfo; /* compinfo parameter */
282 file_size_t filesize; /* size of the file */
283};
284
285struct pathwalk_component
286{
287 struct file_base_info info; /* basic file information */
288 const char *name; /* component name location in path */
289 uint16_t length; /* length of name of component */
290 uint16_t attr; /* attributes of this component */
291 struct pathwalk_component *nextp; /* parent if in use else next free */
292};
293
294#define WALK_RC_NOT_FOUND 0 /* successfully not found */
295#define WALK_RC_FOUND 1 /* found and opened */
296#define WALK_RC_FOUND_ROOT 2 /* found and opened sys/volume root */
297#define WALK_RC_CONT_AT_ROOT 3 /* continue at root level */
298
299/* return another struct pathwalk_component from the pool, or NULL if the
300 pool is completely used */
301static void * pathwalk_comp_alloc_(struct pathwalk_component *parentp)
302{
303 /* static pool that goes to a depth of STATIC_COMP_NUM before allocating
304 elements from the stack */
305 static struct pathwalk_component aux_pathwalk[STATIC_PATHCOMP_NUM];
306 struct pathwalk_component *compp = NULL;
307
308 if (!parentp)
309 compp = &aux_pathwalk[0]; /* root */
310 else if (PTR_IN_ARRAY(aux_pathwalk, parentp, STATIC_PATHCOMP_NUM-1))
311 compp = parentp + 1;
312
313 return compp;
314}
315
316/* allocates components from the pool or stack depending upon the depth */
317#define pathwalk_comp_alloc(parentp) \
318 ({ \
319 void *__c = pathwalk_comp_alloc_(parentp); \
320 if (!__builtin_constant_p(parentp) && !__c) \
321 __c = alloca(sizeof (struct pathwalk_component)); \
322 (struct pathwalk_component *)__c; \
323 })
324
325/* fill in the details of the struct path_component_info for caller */
326static int fill_path_compinfo(struct pathwalk *walkp,
327 struct pathwalk_component *compp,
328 int rc)
329{
330 if (rc == -ENOENT)
331 {
332 /* this component wasn't found; see if more of them exist or path
333 has trailing separators; if it does, this component should be
334 interpreted as a directory even if it doesn't exist and it's the
335 final one; also, this has to be the last part or it's an error*/
336 const char *p = GOBBLE_PATH_SEPCH(walkp->path);
337 if (!*p)
338 {
339 if (p > walkp->path)
340 compp->attr |= ATTR_DIRECTORY;
341
342 rc = WALK_RC_NOT_FOUND; /* successfully not found */
343 }
344 }
345
346 if (rc >= 0)
347 {
348 struct path_component_info *compinfo = walkp->compinfo;
349 compinfo->name = compp->name;
350 compinfo->length = compp->length;
351 compinfo->attr = compp->attr;
352 compinfo->filesize = walkp->filesize;
353 compinfo->parentinfo = (compp->nextp ?: compp)->info;
354 }
355
356 return rc;
357}
358
359/* open the final stream itself, if found */
360static int walk_open_info(struct pathwalk *walkp,
361 struct pathwalk_component *compp,
362 int rc,
363 struct filestr_base *stream)
364{
365 /* this may make adjustments to things; do it first */
366 if (walkp->compinfo)
367 rc = fill_path_compinfo(walkp, compp, rc);
368
369 if (rc < 0 || rc == WALK_RC_NOT_FOUND)
370 return rc;
371
372 unsigned int callflags = walkp->callflags;
373 bool isdir = compp->attr & ATTR_DIRECTORY;
374
375 /* type must match what is called for */
376 switch (callflags & FF_TYPEMASK)
377 {
378 case FF_FILE:
379 if (!isdir) break;
380 DEBUGF("File is a directory\n");
381 return -EISDIR;
382 case FF_DIR:
383 if (isdir) break;
384 DEBUGF("File is not a directory\n");
385 return -ENOTDIR;
386 /* FF_ANYTYPE: basically, ignore FF_FILE/FF_DIR */
387 }
388
389 /* FO_DIRECTORY must match type */
390 if (isdir)
391 callflags |= FO_DIRECTORY;
392 else
393 callflags &= ~FO_DIRECTORY;
394
395 fileop_onopen_internal(stream, &compp->info, callflags);
396 return compp->nextp ? WALK_RC_FOUND : WALK_RC_FOUND_ROOT;
397}
398
399/* check the component against the prefix test info */
400static void walk_check_prefix(struct pathwalk *walkp,
401 struct pathwalk_component *compp)
402{
403 if (compp->attr & ATTR_PREFIX)
404 return;
405
406 if (!fat_file_is_same(&compp->info.fatfile,
407 &walkp->compinfo->prefixp->fatfile))
408 return;
409
410 compp->attr |= ATTR_PREFIX;
411}
412
413/* opens the component named by 'comp' in the directory 'parent' */
414static NO_INLINE int open_path_component(struct pathwalk *walkp,
415 struct pathwalk_component *compp,
416 struct filestr_base *stream)
417{
418 int rc;
419
420 /* create a null-terminated copy of the component name */
421 char *compname = strmemdupa(compp->name, compp->length);
422
423 unsigned int callflags = walkp->callflags;
424 struct pathwalk_component *parentp = compp->nextp;
425
426 /* children inherit the prefix coloring from the parent */
427 compp->attr = parentp->attr & ATTR_PREFIX;
428
429 /* most of the next would be abstracted elsewhere if doing other
430 filesystems */
431
432 /* scan parent for name; stream is converted to this parent */
433 file_cache_reset(stream->cachep);
434 stream->infop = &parentp->info;
435 fat_filestr_init(&stream->fatstr, &parentp->info.fatfile);
436 rewinddir_internal(&compp->info);
437
438 while ((rc = readdir_internal(stream, &compp->info, &dir_fatent)) > 0)
439 {
440 if (rc > 1 && !(callflags & FF_NOISO))
441 iso_decode_d_name(dir_fatent.name);
442
443 if (!strcasecmp(compname, dir_fatent.name))
444 break;
445 }
446
447 if (rc == 0)
448 {
449 DEBUGF("File/directory not found\n");
450 return -ENOENT;
451 }
452 else if (rc < 0)
453 {
454 DEBUGF("I/O error reading directory %d\n", rc);
455 return -EIO;
456 }
457
458 rc = fat_open(stream->fatstr.fatfilep, dir_fatent.firstcluster,
459 &compp->info.fatfile);
460 if (rc < 0)
461 {
462 DEBUGF("I/O error opening file/directory %s (%d)\n",
463 compname, rc);
464 return -EIO;
465 }
466
467 walkp->filesize = dir_fatent.filesize;
468 compp->attr |= dir_fatent.attr;
469
470 if (callflags & FF_CHECKPREFIX)
471 walk_check_prefix(walkp, compp);
472
473 return WALK_RC_FOUND;
474}
475
476/* parse a path component, open it and process the next */
477static NO_INLINE int
478walk_path(struct pathwalk *walkp, struct pathwalk_component *compp,
479 struct filestr_base *stream)
480{
481 int rc = WALK_RC_FOUND;
482
483 if (walkp->callflags & FF_CHECKPREFIX)
484 walk_check_prefix(walkp, compp);
485
486 /* alloca is used in a loop, but we reuse any blocks previously allocated
487 if we went up then back down; if the path takes us back to the root, then
488 everything is cleaned automatically */
489 struct pathwalk_component *freep = NULL;
490
491 const char *name;
492 ssize_t len;
493
494 while ((len = parse_path_component(&walkp->path, &name)))
495 {
496 /* whatever is to be a parent must be a directory */
497 if (!(compp->attr & ATTR_DIRECTORY))
498 return -ENOTDIR;
499
500 switch (len)
501 {
502 case 1:
503 case 2:
504 /* check for "." and ".." */
505 if (name[0] == '.')
506 {
507 if (len == 2 && name[1] == '.')
508 {
509 struct pathwalk_component *parentp = compp->nextp;
510 if (!parentp)
511 return WALK_RC_CONT_AT_ROOT;
512
513 compp->nextp = freep;
514 freep = compp;
515 compp = parentp;
516 }
517
518 break;
519 }
520
521 /* fallthrough */
522 default:
523 if (len >= MAX_NAME)
524 return -ENAMETOOLONG;
525
526 struct pathwalk_component *newp = freep;
527 if (!newp)
528 newp = pathwalk_comp_alloc(compp);
529 else
530 freep = freep->nextp;
531
532 newp->nextp = compp;
533 compp = newp;
534
535 compp->name = name;
536 compp->length = len;
537
538 rc = open_path_component(walkp, compp, stream);
539 if (rc < 0)
540 break;
541 }
542 }
543
544 return walk_open_info(walkp, compp, rc, stream);
545}
546
547/* open a stream given a path to the resource */
548int open_stream_internal(const char *path, unsigned int callflags,
549 struct filestr_base *stream,
550 struct path_component_info *compinfo)
551{
552 DEBUGF("%s(path=\"%s\",flg=%X,str=%p,compinfo=%p)\n", path, callflags,
553 stream, compinfo);
554 int rc;
555
556 filestr_base_init(stream);
557
558 if (!path_is_absolute(path))
559 {
560 /* while this supports relative components, there is currently no
561 current working directory concept at this level by which to
562 fully qualify the path (though that would not be excessively
563 difficult to add) */
564 DEBUGF("\"%s\" is not an absolute path\n"
565 "Only absolute paths currently supported.\n", path);
566 FILE_ERROR(path ? ENOENT : EFAULT, -1);
567 }
568
569 /* if !compinfo, then the result of this check is not visible anyway */
570 if (!compinfo)
571 callflags &= ~FF_CHECKPREFIX;
572
573 struct pathwalk walk;
574 walk.path = path;
575 walk.callflags = callflags;
576 walk.compinfo = compinfo;
577 walk.filesize = 0;
578
579 struct pathwalk_component *rootp = pathwalk_comp_alloc(NULL);
580 rootp->nextp = NULL;
581 rootp->attr = ATTR_DIRECTORY;
582
583#ifdef HAVE_MULTIVOLUME
584 int volume = 0, rootrc = WALK_RC_FOUND;
585#endif /* HAVE_MULTIVOLUME */
586
587 while (1)
588 {
589 const char *pathptr = walk.path;
590
591 #ifdef HAVE_MULTIVOLUME
592 /* this seamlessly integrates secondary filesystems into the
593 root namespace (e.g. "/<0>/../../<1>/../foo/." :<=> "/foo") */
594 const char *p;
595 volume = path_strip_volume(pathptr, &p, false);
596 if (!CHECK_VOL(volume))
597 {
598 DEBUGF("No such device or address: %d\n", volume);
599 FILE_ERROR(ENXIO, -2);
600 }
601
602 /* the root of this subpath is the system root? */
603 rootrc = p == pathptr ? WALK_RC_FOUND_ROOT : WALK_RC_FOUND;
604 walk.path = p;
605 #endif /* HAVE_MULTIVOLUME */
606
607 /* set name to start at last leading separator; names of volume
608 specifiers will be returned as "/<fooN>" */
609 rootp->name = GOBBLE_PATH_SEPCH(pathptr) - 1;
610 rootp->length =
611 IF_MV( rootrc == WALK_RC_FOUND ? p - rootp->name : ) 1;
612
613 rc = fat_open_rootdir(IF_MV(volume,) &rootp->info.fatfile);
614 if (rc < 0)
615 {
616 /* not mounted */
617 DEBUGF("No such device or address: %d\n", IF_MV_VOL(volume));
618 rc = -ENXIO;
619 break;
620 }
621
622 get_rootinfo_internal(&rootp->info);
623 rc = walk_path(&walk, rootp, stream);
624 if (rc != WALK_RC_CONT_AT_ROOT)
625 break;
626 }
627
628 switch (rc)
629 {
630 case WALK_RC_FOUND_ROOT:
631 IF_MV( rc = rootrc; )
632 case WALK_RC_NOT_FOUND:
633 case WALK_RC_FOUND:
634 break;
635
636 default: /* utter, abject failure :`( */
637 DEBUGF("Open failed: rc=%d, errno=%d\n", rc, errno);
638 filestr_base_destroy(stream);
639 FILE_ERROR(-rc, -2);
640 }
641
642 file_cache_reset(stream->cachep);
643
644file_error:
645 return rc;
646}
647
648/* close the stream referenced by 'stream' */
649int close_stream_internal(struct filestr_base *stream)
650{
651 int rc;
652 unsigned int foflags = fileobj_get_flags(stream);
653
654 if ((foflags & (FO_SINGLE|FO_REMOVED)) == (FO_SINGLE|FO_REMOVED))
655 {
656 /* nothing is referencing it so now remove the file's data */
657 rc = fat_remove(&stream->infop->fatfile, FAT_RM_DATA);
658 if (rc < 0)
659 {
660 DEBUGF("I/O error removing file data: %d\n", rc);
661 FILE_ERROR(EIO, rc * 10 - 1);
662 }
663 }
664
665 rc = 0;
666file_error:
667 /* close no matter what */
668 fileop_onclose_internal(stream);
669 return rc;
670}
671
672/* create a new stream in the parent directory */
673int create_stream_internal(struct file_base_info *parentinfop,
674 const char *basename, size_t length,
675 unsigned int attr, unsigned int callflags,
676 struct filestr_base *stream)
677{
678 /* assumes an attempt was made beforehand to open *stream with
679 open_stream_internal() which returned zero (successfully not found),
680 so does not initialize it here */
681 const char * const name = strmemdupa(basename, length);
682 DEBUGF("Creating \"%s\"\n", name);
683
684 struct file_base_info info;
685 int rc = fat_create_file(&parentinfop->fatfile, name, attr,
686 &info.fatfile, get_dir_fatent_dircache());
687 if (rc < 0)
688 {
689 DEBUGF("Create failed: %d\n", rc);
690 FILE_ERROR(rc == FAT_RC_ENOSPC ? ENOSPC : EIO, rc * 10 - 1);
691 }
692
693 /* dir_fatent is implicit arg */
694 fileop_oncreate_internal(stream, &info, callflags, parentinfop, name);
695 rc = 0;
696file_error:
697 return rc;
698}
699
700/* removes files and directories - back-end to remove() and rmdir() */
701int remove_stream_internal(const char *path, struct filestr_base *stream,
702 unsigned int callflags)
703{
704 /* Only FF_* flags should be in callflags */
705 int rc;
706
707 struct filestr_base opened_stream;
708 if (!stream)
709 stream = &opened_stream;
710
711 if (stream == &opened_stream)
712 {
713 /* no stream provided so open local one */
714 rc = open_stream_internal(path, callflags, stream, NULL);
715 if (rc < 0)
716 {
717 DEBUGF("Failed opening path: %d\n", rc);
718 FILE_ERROR(ERRNO, rc * 10 - 1);
719 }
720 }
721 /* else ignore the 'path' argument */
722
723 if (callflags & FF_DIR)
724 {
725 /* directory to be removed must be empty */
726 rc = test_dir_empty_internal(stream);
727 if (rc < 0)
728 FILE_ERROR(ERRNO, rc * 10 - 2);
729 }
730
731 /* save old info since fat_remove() will destroy the dir info */
732 struct file_base_info oldinfo = *stream->infop;
733 rc = fat_remove(&stream->infop->fatfile, FAT_RM_DIRENTRIES);
734 if (rc < 0)
735 {
736 DEBUGF("I/O error removing dir entries: %d\n", rc);
737 FILE_ERROR(EIO, rc * 10 - 3);
738 }
739
740 fileop_onremove_internal(stream, &oldinfo);
741
742 rc = 0;
743file_error:
744 if (stream == &opened_stream)
745 {
746 /* will do removal of data below if this is the only reference */
747 int rc2 = close_stream_internal(stream);
748 if (rc2 < 0 && rc >= 0)
749 {
750 rc = rc2 * 10 - 4;
751 DEBUGF("Success but failed closing stream: %d\n", rc);
752 }
753 }
754
755 return rc;
756}
757
758/* test file/directory existence with constraints */
759int test_stream_exists_internal(const char *path, unsigned int callflags)
760{
761 /* only FF_* flags should be in callflags */
762 struct filestr_base stream;
763 int rc = open_stream_internal(path, callflags, &stream, NULL);
764 if (rc > 0)
765 close_stream_internal(&stream);
766
767 return rc;
768}
769
770/* one-time init at startup */
771void filesystem_init(void)
772{
773 mrsw_init(&file_internal_mrsw);
774 dc_init();
775 fileobj_mgr_init();
776}
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 by Björn Stenberg
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "config.h"
22#include "dir.h"
23#include "stdlib.h"
24#include "string.h"
25#include "debug.h"
26#include "file.h"
27#include "filefuncs.h"
28#include "string-extra.h"
29
30#ifndef __PCTOOL__
31#ifdef HAVE_MULTIVOLUME
32
33/* returns on which volume this is, and copies the reduced name
34 (sortof a preprocessor for volume-decorated pathnames) */
35int strip_volume(const char* name, char* namecopy)
36{
37 int volume = 0;
38 const char *temp = name;
39
40 while (*temp == '/') /* skip all leading slashes */
41 ++temp;
42
43 if (*temp && !strncmp(temp, VOL_NAMES, VOL_ENUM_POS))
44 {
45 temp += VOL_ENUM_POS; /* behind special name */
46 volume = atoi(temp); /* number is following */
47 temp = strchr(temp, '/'); /* search for slash behind */
48 if (temp != NULL)
49 name = temp; /* use the part behind the volume */
50 else
51 name = "/"; /* else this must be the root dir */
52 }
53
54 strlcpy(namecopy, name, MAX_PATH);
55
56 return volume;
57}
58#endif /* #ifdef HAVE_MULTIVOLUME */
59
60#endif /* __PCTOOL__ */
61/* Test file existence, using dircache of possible */
62bool file_exists(const char *file)
63{
64 int fd;
65
66#ifdef DEBUG
67 if (!file || !*file)
68 {
69 DEBUGF("%s(%p): Invalid parameter!\n", __func__, file);
70 return false;
71 }
72#endif
73
74#ifdef HAVE_DIRCACHE
75 if (dircache_is_enabled())
76 return (dircache_get_entry_id(file) >= 0);
77#endif
78
79 fd = open(file, O_RDONLY);
80 if (fd < 0)
81 return false;
82 close(fd);
83 return true;
84}
85
86bool dir_exists(const char *path)
87{
88 DIR* d = opendir(path);
89 if (!d)
90 return false;
91 closedir(d);
92 return true;
93}
94
95
96#if (CONFIG_PLATFORM & PLATFORM_NATIVE) || defined(SIMULATOR)
97struct dirinfo dir_get_info(DIR* parent, struct dirent *entry)
98{
99 (void)parent;
100 return entry->info;
101}
102#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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "config.h"
22#include "system.h"
23#include "debug.h"
24#include "file.h"
25#include "dir.h"
26#include "disk_cache.h"
27#include "fileobj_mgr.h"
28#include "dircache_redirect.h"
29
30/**
31 * Manages file and directory streams on all volumes
32 *
33 * Intended for internal use by disk, file and directory code
34 */
35
36
37/* there will always be enough of these for all user handles, thus these
38 functions don't return failure codes */
39#define MAX_FILEOBJS (MAX_OPEN_HANDLES + AUX_FILEOBJS)
40
41/* describes the file as an image on the storage medium */
42static struct fileobj_binding
43{
44 struct file_base_binding bind; /* base info list item (first!) */
45 uint16_t flags; /* F(D)(O)_* bits of this file/dir */
46 uint16_t writers; /* number of writer streams */
47 struct filestr_cache cache; /* write mode shared cache */
48 file_size_t size; /* size of this file */
49 struct ll_head list; /* open streams for this file/dir */
50} fobindings[MAX_FILEOBJS];
51static struct mutex stream_mutexes[MAX_FILEOBJS] SHAREDBSS_ATTR;
52static struct ll_head free_bindings;
53static struct ll_head busy_bindings[NUM_VOLUMES];
54
55#define BUSY_BINDINGS(volume) \
56 (&busy_bindings[IF_MV_VOL(volume)])
57
58#define BASEBINDING_LIST(bindp) \
59 (BUSY_BINDINGS(BASEBINDING_VOL(bindp)))
60
61#define FREE_BINDINGS() \
62 (&free_bindings)
63
64#define BINDING_FIRST(type, volume...) \
65 ((struct fileobj_binding *)type##_BINDINGS(volume)->head)
66
67#define BINDING_NEXT(fobp) \
68 ((struct fileobj_binding *)(fobp)->bind.node.next)
69
70#define FOR_EACH_BINDING(volume, fobp) \
71 for (struct fileobj_binding *fobp = BINDING_FIRST(BUSY, volume); \
72 fobp; fobp = BINDING_NEXT(fobp))
73
74#define STREAM_FIRST(fobp) \
75 ((struct filestr_base *)(fobp)->list.head)
76
77#define STREAM_NEXT(s) \
78 ((struct filestr_base *)(s)->node.next)
79
80#define STREAM_THIS(s) \
81 (s)
82
83#define FOR_EACH_STREAM(what, start, s) \
84 for (struct filestr_base *s = STREAM_##what(start); \
85 s; s = STREAM_NEXT(s))
86
87
88/* syncs information for the stream's old and new parent directory if any are
89 currently opened */
90static void fileobj_sync_parent(const struct file_base_info *infop[],
91 int count)
92{
93 FOR_EACH_BINDING(infop[0]->volume, fobp)
94 {
95 if ((fobp->flags & (FO_DIRECTORY|FO_REMOVED)) != FO_DIRECTORY)
96 continue; /* not directory or removed can't be parent of anything */
97
98 struct filestr_base *parentstrp = STREAM_FIRST(fobp);
99 struct fat_file *parentfilep = &parentstrp->infop->fatfile;
100
101 for (int i = 0; i < count; i++)
102 {
103 if (!fat_dir_is_parent(parentfilep, &infop[i]->fatfile))
104 continue;
105
106 /* discard scan/read caches' parent dir info */
107 FOR_EACH_STREAM(THIS, parentstrp, s)
108 filestr_discard_cache(s);
109 }
110 }
111}
112
113/* see if this file has open streams and return that fileobj_binding if so,
114 else grab a new one from the free list; returns true if this stream is
115 the only open one */
116static bool binding_assign(const struct file_base_info *srcinfop,
117 struct fileobj_binding **fobpp)
118{
119 FOR_EACH_BINDING(srcinfop->fatfile.volume, fobp)
120 {
121 if (fobp->flags & FO_REMOVED)
122 continue;
123
124 if (fat_file_is_same(&srcinfop->fatfile, &fobp->bind.info.fatfile))
125 {
126 /* already has open streams */
127 *fobpp = fobp;
128 return false;
129 }
130 }
131
132 /* not found - allocate anew */
133 *fobpp = BINDING_FIRST(FREE);
134 ll_remove_first(FREE_BINDINGS());
135 ll_init(&(*fobpp)->list);
136 return true;
137}
138
139/* mark descriptor as unused and return to the free list */
140static void binding_add_to_free_list(struct fileobj_binding *fobp)
141{
142 fobp->flags = 0;
143 ll_insert_last(FREE_BINDINGS(), &fobp->bind.node);
144}
145
146/** File and directory internal interface **/
147
148void file_binding_insert_last(struct file_base_binding *bindp)
149{
150 ll_insert_last(BASEBINDING_LIST(bindp), &bindp->node);
151}
152
153void file_binding_remove(struct file_base_binding *bindp)
154{
155 ll_remove(BASEBINDING_LIST(bindp), &bindp->node);
156}
157
158#ifdef HAVE_DIRCACHE
159void file_binding_insert_first(struct file_base_binding *bindp)
160{
161 ll_insert_first(BASEBINDING_LIST(bindp), &bindp->node);
162}
163
164void file_binding_remove_next(struct file_base_binding *prevp,
165 struct file_base_binding *bindp)
166{
167 ll_remove_next(BASEBINDING_LIST(bindp), &prevp->node);
168 (void)bindp;
169}
170#endif /* HAVE_DIRCACHE */
171
172/* opens the file object for a new stream and sets up the caches;
173 * the stream must already be opened at the FS driver level and *stream
174 * initialized.
175 *
176 * NOTE: switches stream->infop to the one kept in common for all streams of
177 * the same file, making a copy for only the first stream
178 */
179void fileobj_fileop_open(struct filestr_base *stream,
180 const struct file_base_info *srcinfop,
181 unsigned int callflags)
182{
183 struct fileobj_binding *fobp;
184 bool first = binding_assign(srcinfop, &fobp);
185
186 /* add stream to this file's list */
187 ll_insert_last(&fobp->list, &stream->node);
188
189 /* initiate the new stream into the enclave */
190 stream->flags = FDO_BUSY | (callflags & (FD_WRITE|FD_WRONLY|FD_APPEND));
191 stream->infop = &fobp->bind.info;
192 stream->fatstr.fatfilep = &fobp->bind.info.fatfile;
193 stream->bindp = &fobp->bind;
194 stream->mtx = &stream_mutexes[fobp - fobindings];
195
196 if (first)
197 {
198 /* first stream for file */
199 fobp->bind.info = *srcinfop;
200 fobp->flags = FDO_BUSY | FO_SINGLE |
201 (callflags & (FO_DIRECTORY|FO_TRUNC));
202 fobp->writers = 0;
203 fobp->size = 0;
204
205 if (callflags & FD_WRITE)
206 {
207 /* first one is a writer */
208 fobp->writers = 1;
209 file_cache_init(&fobp->cache);
210 filestr_assign_cache(stream, &fobp->cache);
211 }
212
213 fileobj_bind_file(&fobp->bind);
214 }
215 else
216 {
217 /* additional stream for file */
218 fobp->flags &= ~FO_SINGLE;
219 fobp->flags |= callflags & FO_TRUNC;
220
221 /* once a file/directory, always a file/directory; such a change
222 is a bug */
223 if ((callflags ^ fobp->flags) & FO_DIRECTORY)
224 {
225 DEBUGF("%s - FO_DIRECTORY flag does not match: %p %u\n",
226 __func__, stream, callflags);
227 }
228
229 if (fobp->writers)
230 {
231 /* already writers present */
232 fobp->writers++;
233 filestr_assign_cache(stream, &fobp->cache);
234 }
235 else if (callflags & FD_WRITE)
236 {
237 /* first writer */
238 fobp->writers = 1;
239 file_cache_init(&fobp->cache);
240 FOR_EACH_STREAM(FIRST, fobp, s)
241 filestr_assign_cache(s, &fobp->cache);
242 }
243 /* else another reader */
244 }
245}
246
247/* close the stream and free associated resources */
248void fileobj_fileop_close(struct filestr_base *stream)
249{
250 switch (stream->flags)
251 {
252 case 0: /* not added to manager */
253 case FV_NONEXIST: /* forced-closed by unmounting */
254 filestr_base_destroy(stream);
255 return;
256 }
257
258 struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp;
259 unsigned int foflags = fobp->flags;
260
261 ll_remove(&fobp->list, &stream->node);
262
263 if ((foflags & FO_SINGLE) || fobp->writers == 0)
264 {
265 if (foflags & FO_SINGLE)
266 {
267 /* last stream for file; close everything */
268 fileobj_unbind_file(&fobp->bind);
269
270 if (fobp->writers)
271 file_cache_free(&fobp->cache);
272
273 binding_add_to_free_list(fobp);
274 }
275 }
276 else if ((stream->flags & FD_WRITE) && --fobp->writers == 0)
277 {
278 /* only readers remain; switch back to stream-local caching */
279 FOR_EACH_STREAM(FIRST, fobp, s)
280 filestr_copy_cache(s, &fobp->cache);
281
282 file_cache_free(&fobp->cache);
283 }
284
285 if (!(foflags & FO_SINGLE) && fobp->list.head == fobp->list.tail)
286 fobp->flags |= FO_SINGLE; /* only one open stream remaining */
287
288 filestr_base_destroy(stream);
289}
290
291/* informs manager that file has been created */
292void fileobj_fileop_create(struct filestr_base *stream,
293 const struct file_base_info *srcinfop,
294 unsigned int callflags)
295{
296 fileobj_fileop_open(stream, srcinfop, callflags);
297 fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1);
298}
299
300/* informs manager that file has been removed */
301void fileobj_fileop_remove(struct filestr_base *stream,
302 const struct file_base_info *oldinfop)
303{
304 ((struct fileobj_binding *)stream->bindp)->flags |= FO_REMOVED;
305 fileobj_sync_parent((const struct file_base_info *[]){ oldinfop }, 1);
306}
307
308/* informs manager that file has been renamed */
309void fileobj_fileop_rename(struct filestr_base *stream,
310 const struct file_base_info *oldinfop)
311{
312 /* if there is old info then this was a move and the old parent has to be
313 informed */
314 int count = oldinfop ? 2 : 1;
315 fileobj_sync_parent(&(const struct file_base_info *[])
316 { oldinfop, stream->infop }[2 - count],
317 count);
318}
319
320/* informs manager than directory entries have been updated */
321void fileobj_fileop_sync(struct filestr_base *stream)
322{
323 fileobj_sync_parent((const struct file_base_info *[]){ stream->infop }, 1);
324}
325
326/* inform manager that file has been truncated */
327void fileobj_fileop_truncate(struct filestr_base *stream)
328{
329 /* let caller update internal info */
330 FOR_EACH_STREAM(FIRST, (struct fileobj_binding *)stream->bindp, s)
331 ftruncate_internal_callback(stream, s);
332}
333
334/* query for the pointer to the size storage for the file object */
335file_size_t * fileobj_get_sizep(const struct filestr_base *stream)
336{
337 if (!stream->bindp)
338 return NULL;
339
340 return &((struct fileobj_binding *)stream->bindp)->size;
341}
342
343/* query manager bitflags for the file object */
344unsigned int fileobj_get_flags(const struct filestr_base *stream)
345{
346 if (!stream->bindp)
347 return 0;
348
349 return ((struct fileobj_binding *)stream->bindp)->flags;
350}
351
352/* change manager bitflags for the file object */
353void fileobj_change_flags(struct filestr_base *stream,
354 unsigned int flags, unsigned int mask)
355{
356 struct fileobj_binding *fobp = (struct fileobj_binding *)stream->bindp;
357 if (fobp)
358 fobp->flags = (fobp->flags & ~mask) | (flags & mask);
359}
360
361/* mark all open streams on a device as "nonexistant" */
362void fileobj_mgr_unmount(IF_MV_NONVOID(int volume))
363{
364 /* right now, there is nothing else to be freed when marking a descriptor
365 as "nonexistant" but a callback could be added if that changes */
366 FOR_EACH_VOLUME(volume, v)
367 {
368 struct fileobj_binding *fobp;
369 while ((fobp = BINDING_FIRST(BUSY, v)))
370 {
371 struct filestr_base *s;
372 while ((s = STREAM_FIRST(fobp)))
373 {
374 /* keep it "busy" to avoid races; any valid file/directory
375 descriptor returned by an open call should always be
376 closed by whomever opened it (of course!) */
377 fileop_onclose_internal(s);
378 s->flags = FV_NONEXIST;
379 }
380 }
381 }
382}
383
384/* one-time init at startup */
385void fileobj_mgr_init(void)
386{
387 for (unsigned int i = 0; i < NUM_VOLUMES; i++)
388 ll_init(BUSY_BINDINGS(i));
389
390 ll_init(FREE_BINDINGS());
391 for (unsigned int i = 0; i < MAX_FILEOBJS; i++)
392 {
393 mutex_init(&stream_mutexes[i]);
394 binding_add_to_free_list(&fobindings[i]);
395 }
396}
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include <string.h>
22#include <ctype.h>
23#include "system.h"
24#include "pathfuncs.h"
25#include "string-extra.h"
26
27#ifdef HAVE_MULTIVOLUME
28#include <stdio.h>
29#include "storage.h"
30
31enum storage_name_dec_indexes
32{
33#if (CONFIG_STORAGE & STORAGE_ATA)
34 STORAGE_DEC_IDX_ATA,
35#endif
36#if (CONFIG_STORAGE & STORAGE_MMC)
37 STORAGE_DEC_IDX_MMC,
38#endif
39#if (CONFIG_STORAGE & STORAGE_SD)
40 STORAGE_DEC_IDX_SD,
41#endif
42#if (CONFIG_STORAGE & STORAGE_NAND)
43 STORAGE_DEC_IDX_NAND,
44#endif
45#if (CONFIG_STORAGE & STORAGE_RAMDISK)
46 STORAGE_DEC_IDX_RAMDISK,
47#endif
48#if (CONFIG_STORAGE & STORAGE_HOSTFS)
49 STORAGE_DEC_IDX_HOSTFS,
50#endif
51 STORAGE_NUM_DEC_IDX,
52};
53
54static const char * const storage_dec_names[STORAGE_NUM_DEC_IDX+1] =
55{
56#if (CONFIG_STORAGE & STORAGE_ATA)
57 [STORAGE_DEC_IDX_ATA] = ATA_VOL_DEC,
58#endif
59#if (CONFIG_STORAGE & STORAGE_MMC)
60 [STORAGE_DEC_IDX_MMC] = MMC_VOL_DEC,
61#endif
62#if (CONFIG_STORAGE & STORAGE_SD)
63 [STORAGE_DEC_IDX_SD] = SD_VOL_DEC,
64#endif
65#if (CONFIG_STORAGE & STORAGE_NAND)
66 [STORAGE_DEC_IDX_NAND] = NAND_VOL_DEC,
67#endif
68#if (CONFIG_STORAGE & STORAGE_RAMDISK)
69 [STORAGE_DEC_IDX_RAMDISK] = RAMDISK_VOL_DEC,
70#endif
71#if (CONFIG_STORAGE & STORAGE_HOSTFS)
72 [STORAGE_DEC_IDX_HOSTFS] = HOSTFS_VOL_DEC,
73#endif
74 [STORAGE_NUM_DEC_IDX] = DEFAULT_VOL_DEC,
75};
76
77static const unsigned char storage_dec_indexes[STORAGE_NUM_TYPES+1] =
78{
79 [0 ... STORAGE_NUM_TYPES] = STORAGE_NUM_DEC_IDX,
80#if (CONFIG_STORAGE & STORAGE_ATA)
81 [STORAGE_ATA_NUM] = STORAGE_DEC_IDX_ATA,
82#endif
83#if (CONFIG_STORAGE & STORAGE_MMC)
84 [STORAGE_MMC_NUM] = STORAGE_DEC_IDX_MMC,
85#endif
86#if (CONFIG_STORAGE & STORAGE_SD)
87 [STORAGE_SD_NUM] = STORAGE_DEC_IDX_SD,
88#endif
89#if (CONFIG_STORAGE & STORAGE_NAND)
90 [STORAGE_NAND_NUM] = STORAGE_DEC_IDX_NAND,
91#endif
92#if (CONFIG_STORAGE & STORAGE_RAMDISK)
93 [STORAGE_RAMDISK_NUM] = STORAGE_DEC_IDX_RAMDISK,
94#endif
95#if (CONFIG_STORAGE & STORAGE_HOSTFS)
96 [STORAGE_HOSTFS_NUM] = STORAGE_DEC_IDX_HOSTFS,
97#endif
98};
99
100/* Returns on which volume this is and sets *nameptr to the portion of the
101 * path after the volume specifier, which could be the null if the path is
102 * just a volume root. If *nameptr > name, then a volume specifier was
103 * found. If 'greedy' is 'true', then it all separators after the volume
104 * specifier are consumed, if one was found.
105 */
106int path_strip_volume(const char *name, const char **nameptr, bool greedy)
107{
108 int volume = 0;
109 const char *t = name;
110 int c, v = 0;
111
112 /* format: "/<xxx##>/foo/bar"
113 * the "xxx" is pure decoration; only an unbroken trailing string of
114 * digits within the brackets is parsed as the volume number and of
115 * those, only the last ones VOL_MUM_MAX allows.
116 */
117 c = *(t = GOBBLE_PATH_SEPCH(t)); /* skip all leading slashes */
118 if (c != VOL_START_TOK) /* missing start token? no volume */
119 goto volume0;
120
121 do
122 {
123 switch (c)
124 {
125 case '0' ... '9': /* digit; parse volume number */
126 v = (v * 10 + c - '0') % VOL_NUM_MAX;
127 break;
128 case '\0':
129 case PATH_SEPCH: /* no closing bracket; no volume */
130 goto volume0;
131 default: /* something else; reset volume */
132 v = 0;
133 }
134 }
135 while ((c = *++t) != VOL_END_TOK); /* found end token? */
136
137 if (!(c = *++t)) /* no more path and no '/' is ok */
138 ;
139 else if (c != PATH_SEPCH) /* more path and no separator after end */
140 goto volume0;
141 else if (greedy)
142 t = GOBBLE_PATH_SEPCH(++t); /* strip remaining separators */
143
144 /* if 'greedy' is true and **nameptr == '\0' then it's only a volume
145 root whether or not it has trailing separators */
146
147 volume = v;
148 name = t;
149volume0:
150 if (nameptr)
151 *nameptr = name;
152 return volume;
153}
154
155/* Returns the volume specifier decorated with the storage type name.
156 * Assumes the supplied buffer size is at least {VOL_MAX_LEN}+1.
157 */
158int get_volume_name(int volume, char *buffer)
159{
160 if (volume < 0)
161 {
162 *buffer = '\0';
163 return 0;
164 }
165
166 volume %= VOL_NUM_MAX; /* as path parser would have it */
167
168 int type = storage_driver_type(volume_drive(volume));
169 if (type < 0 || type > STORAGE_NUM_TYPES)
170 type = STORAGE_NUM_TYPES;
171
172 const char *voldec = storage_dec_names[storage_dec_indexes[type]];
173 return snprintf(buffer, VOL_MAX_LEN + 1, "%c%s%d%c",
174 VOL_START_TOK, voldec, volume, VOL_END_TOK);
175}
176#endif /* HAVE_MULTIVOLUME */
177
178/* Just like path_strip_volume() but strips a leading drive specifier and
179 * returns the drive number (A=0, B=1, etc.). -1 means no drive was found.
180 * If 'greedy' is 'true', all separators after the volume are consumed.
181 */
182int path_strip_drive(const char *name, const char **nameptr, bool greedy)
183{
184 int c = toupper(*name);
185
186 if (c >= 'A' && c <= 'Z' && name[1] == PATH_DRVSEPCH)
187 {
188 name = &name[2];
189 if (greedy)
190 name = GOBBLE_PATH_SEPCH(name);
191
192 *nameptr = name;
193 return c - 'A';
194 }
195
196 *nameptr = name;
197 return -1;
198}
199
200/* Strips leading and trailing whitespace from a path
201 * " a/b \txyz" *nameptr->a, len=3: "a/b"
202 */
203size_t path_trim_whitespace(const char *name, const char **nameptr)
204{
205 int c;
206 while ((c = *name) <= ' ' && c)
207 ++name;
208
209 const char *first = name;
210 const char *last = name;
211
212 while (1)
213 {
214 if (c < ' ')
215 {
216 *nameptr = first;
217 return last - first;
218 }
219
220 while ((c = *++name) > ' ');
221 last = name;
222 while (c == ' ') c = *++name;
223 }
224}
225
226/* Strips directory components from the path
227 * "" *nameptr->NUL, len=0: ""
228 * "/" *nameptr->/, len=1: "/"
229 * "//" *nameptr->2nd /, len=1: "/"
230 * "/a" *nameptr->a, len=1: "a"
231 * "/a/bc" *nameptr->b, len=2: "bc"
232 * "d" *nameptr->d, len=1: "d"
233 * "ef/gh" *nameptr->g, len=2: "gh"
234 */
235size_t path_basename(const char *name, const char **nameptr)
236{
237 const char *p = name;
238 const char *q = p;
239 const char *r = q;
240
241 while (*(p = GOBBLE_PATH_SEPCH(p)))
242 {
243 q = p;
244 p = GOBBLE_PATH_COMP(++p);
245 r = p;
246 }
247
248 if (r == name && p > name)
249 q = p, r = q--; /* root - return last slash */
250 /* else path is an empty string */
251
252 *nameptr = q;
253 return r - q;
254}
255
256/* Strips the trailing component from the path
257 * "" *nameptr->NUL, len=0: ""
258 * "/" *nameptr->/, len=1: "/"
259 * "//" *nameptr->2nd /, len=1: "/"
260 * "/a" *nameptr->/, len=1: "/"
261 * "/a/bc" *nameptr->/, len=2: "/a"
262 * "d" *nameptr->d, len=0: ""
263 * "ef/gh" *nameptr->e, len=2: "ef"
264 */
265size_t path_dirname(const char *name, const char **nameptr)
266{
267 const char *p = GOBBLE_PATH_SEPCH(name);
268 const char *q = name;
269 const char *r = p;
270
271 while (*(p = GOBBLE_PATH_COMP(p)))
272 {
273 const char *s = p;
274
275 if (!*(p = GOBBLE_PATH_SEPCH(p)))
276 break;
277
278 q = s;
279 }
280
281 if (q == name && r > name)
282 name = r, q = name--; /* root - return last slash */
283
284 *nameptr = name;
285 return q - name;
286}
287
288/* Removes trailing separators from a path
289 * "" *nameptr->NUL, len=0: ""
290 * "/" *nameptr->/, len=1: "/"
291 * "//" *nameptr->2nd /, len=1: "/"
292 * "/a/" *nameptr->/, len=2: "/a"
293 * "//b/" *nameptr->1st /, len=3: "//b"
294 * "/c/" *nameptr->/, len=2: "/c"
295 */
296size_t path_strip_trailing_separators(const char *name, const char **nameptr)
297{
298 const char *p;
299 size_t len = path_basename(name, &p);
300
301 if (len == 1 && *p == '/' && p > name)
302 {
303 *nameptr = p;
304 name = p - 1; /* root with multiple separators */
305 }
306 else
307 {
308 *nameptr = name;
309 p += len; /* length to end of basename */
310 }
311
312 return p - name;
313}
314
315/* Transforms "wrong" separators into the correct ones
316 * "c:\windows\system32" -> "c:/windows/system32"
317 *
318 * 'path' and 'dstpath' may either be the same buffer or non-overlapping
319 */
320void path_correct_separators(char *dstpath, const char *path)
321{
322 char *dstp = dstpath;
323 const char *p = path;
324
325 while (1)
326 {
327 const char *next = strchr(p, PATH_BADSEPCH);
328 if (!next)
329 break;
330
331 size_t size = next - p;
332
333 if (dstpath != path)
334 memcpy(dstp, p, size); /* not in-place */
335
336 dstp += size;
337 *dstp++ = PATH_SEPCH;
338 p = next + 1;
339 }
340
341 if (dstpath != path)
342 strcpy(dstp, p);
343}
344
345/* Appends one path to another, adding separators between components if needed.
346 * Return value and behavior is otherwise as strlcpy so that truncation may be
347 * detected.
348 *
349 * For basepath and component:
350 * PA_SEP_HARD adds a separator even if the base path is empty
351 * PA_SEP_SOFT adds a separator only if the base path is not empty
352 */
353size_t path_append(char *buf, const char *basepath,
354 const char *component, size_t bufsize)
355{
356 const char *base = basepath && basepath[0] ? basepath : buf;
357 if (!base)
358 return bufsize; /* won't work to get lengths from buf */
359
360 if (!buf)
361 bufsize = 0;
362
363 if (path_is_absolute(component))
364 {
365 /* 'component' is absolute; replace all */
366 basepath = component;
367 component = "";
368 }
369
370 /* if basepath is not null or empty, buffer contents are replaced,
371 otherwise buf contains the base path */
372 size_t len = base == buf ? strlen(buf) : strlcpy(buf, basepath, bufsize);
373
374 bool separate = false;
375
376 if (!basepath || !component)
377 separate = !len || base[len-1] != PATH_SEPCH;
378 else if (component[0])
379 separate = len && base[len-1] != PATH_SEPCH;
380
381 /* caller might lie about size of buf yet use buf as the base */
382 if (base == buf && bufsize && len >= bufsize)
383 buf[bufsize - 1] = '\0';
384
385 buf += len;
386 bufsize -= MIN(len, bufsize);
387
388 if (separate && (len++, bufsize > 0) && --bufsize > 0)
389 *buf++ = PATH_SEPCH;
390
391 return len + strlcpy(buf, component ?: "", bufsize);
392}
393
394/* Returns the location and length of the next path component, consuming the
395 * input in the process.
396 *
397 * "/a/bc/d" breaks into:
398 * start: *namep->1st /
399 * call 1: *namep->a, *pathp->2nd / len=1: "a"
400 * call 2: *namep->b, *pathp->3rd / len=2: "bc"
401 * call 3: *namep->d, *pathp->NUL, len=1: "d"
402 * call 4: *namep->NUL, *pathp->NUL, len=0: ""
403 *
404 * Returns: 0 if the input has been consumed
405 * The length of the component otherwise
406 */
407ssize_t parse_path_component(const char **pathp, const char **namep)
408{
409 /* a component starts at a non-separator and continues until the next
410 separator or null */
411 const char *p = GOBBLE_PATH_SEPCH(*pathp);
412 const char *name = p;
413
414 if (*p)
415 p = GOBBLE_PATH_COMP(++p);
416
417 *pathp = p;
418 *namep = name;
419
420 return p - name;
421}
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 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2010 Thomas Martitz
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22
23#include <stdio.h> /* snprintf */
24#include <stdlib.h>
25#include <stdarg.h>
26#include <sys/stat.h>
27#include <time.h>
28#include <unistd.h>
29#include "config.h"
30#include "rbpaths.h"
31#include "crc32.h"
32#include "file.h" /* MAX_PATH */
33#include "logf.h"
34#include "gcc_extensions.h"
35#include "string-extra.h"
36#include "filefuncs.h"
37
38/* In this file we need the actual OS library functions, not the shadowed
39 * wrapper used within Rockbox' application code (except SDL adds
40 * another layer) */
41#undef open
42#undef creat
43#undef remove
44#undef rename
45#undef opendir
46#undef closedir
47#undef readdir
48#undef mkdir
49#undef rmdir
50#undef dirent
51#undef DIR
52#undef readlink
53
54#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
55static const char rbhome[] = "/sdcard";
56#elif (CONFIG_PLATFORM & (PLATFORM_SDL|PLATFORM_MAEMO|PLATFORM_PANDORA)) && !defined(__PCTOOL__)
57const char *rbhome;
58#else
59/* YPR0, YPR1 */
60static const char rbhome[] = HOME_DIR;
61#endif
62
63#if !(defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1)) && !defined(__PCTOOL__)
64/* Special dirs are user-accessible (and user-writable) dirs which take priority
65 * over the ones where Rockbox is installed to. Classic example would be
66 * $HOME/.config/rockbox.org vs /usr/share/rockbox */
67#define HAVE_SPECIAL_DIRS
68#endif
69
70/* flags for get_user_file_path() */
71/* whether you need write access to that file/dir, especially true
72 * for runtime generated files (config.cfg) */
73#define NEED_WRITE (1<<0)
74/* file or directory? */
75#define IS_FILE (1<<1)
76
77#ifdef HAVE_MULTIDRIVE
78static uint32_t rbhome_hash;
79/* A special link is created under e.g. HOME_DIR/<microSD1>, e.g. to access
80 * external storage in a convinient location, much similar to the mount
81 * point on our native targets. Here they are treated as symlink (one which
82 * doesn't actually exist in the filesystem and therefore we have to override
83 * readlink() */
84static const char *handle_special_links(const char* link, unsigned flags,
85 char *buf, const size_t bufsize)
86{
87 (void) flags;
88 char vol_string[VOL_ENUM_POS + 8];
89 int len = sprintf(vol_string, VOL_NAMES, 1);
90
91 /* link might be passed with or without HOME_DIR expanded. To handle
92 * both perform substring matching (VOL_NAMES is unique enough) */
93 const char *begin = strstr(link, vol_string);
94 if (begin)
95 {
96 /* begin now points to the start of vol_string within link,
97 * we want to copy the remainder of the paths, prefixed by
98 * the actual mount point (the remainder might be "") */
99 snprintf(buf, bufsize, MULTIDRIVE_DIR"%s", begin + len);
100 return buf;
101 }
102
103 return link;
104}
105#endif
106
107void paths_init(void)
108{
109#ifdef HAVE_SPECIAL_DIRS
110 /* make sure $HOME/.config/rockbox.org exists, it's needed for config.cfg */
111#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
112 mkdir("/sdcard/rockbox", 0777);
113 mkdir("/sdcard/rockbox/rocks.data", 0777);
114#else
115 char config_dir[MAX_PATH];
116
117 const char *home = getenv("RBROOT");
118 if (!home)
119 {
120 home = getenv("HOME");
121 }
122 if (!home)
123 {
124 logf("HOME environment var not set. Can't write config");
125 return;
126 }
127
128 rbhome = home;
129 snprintf(config_dir, sizeof(config_dir), "%s/.config", home);
130 mkdir(config_dir, 0777);
131 snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org", home);
132 mkdir(config_dir, 0777);
133 /* Plugin data directory */
134 snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org/rocks.data", home);
135 mkdir(config_dir, 0777);
136#endif
137#endif /* HAVE_SPECIAL_DIRS */
138
139#ifdef HAVE_MULTIDRIVE
140 rbhome_hash = crc_32((const void *) rbhome, strlen(rbhome), 0xffffffff);
141#endif /* HAVE_MULTIDRIVE */
142}
143
144#ifdef HAVE_SPECIAL_DIRS
145static bool try_path(const char* filename, unsigned flags)
146{
147 if (flags & IS_FILE)
148 {
149 if (file_exists(filename))
150 return true;
151 }
152 else
153 {
154 if (dir_exists(filename))
155 return true;
156 }
157 return false;
158}
159
160static const char* _get_user_file_path(const char *path,
161 unsigned flags,
162 char* buf,
163 const size_t bufsize)
164{
165 const char *ret = path;
166 const char *pos = path;
167 /* replace ROCKBOX_DIR in path with $HOME/.config/rockbox.org */
168 pos += ROCKBOX_DIR_LEN;
169 if (*pos == '/') pos += 1;
170
171#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
172 if (snprintf(buf, bufsize, "/sdcard/rockbox/%s", pos)
173#else
174 if (snprintf(buf, bufsize, "%s/.config/rockbox.org/%s", rbhome, pos)
175#endif
176 >= (int)bufsize)
177 return NULL;
178
179 /* always return the replacement buffer (pointing to $HOME) if
180 * write access is needed */
181 if (flags & NEED_WRITE)
182 ret = buf;
183 else if (try_path(buf, flags))
184 ret = buf;
185
186 if (ret != buf) /* not found in $HOME, try ROCKBOX_BASE_DIR, !NEED_WRITE only */
187 {
188 if (snprintf(buf, bufsize, ROCKBOX_SHARE_PATH "/%s", pos) >= (int)bufsize)
189 return NULL;
190
191 if (try_path(buf, flags))
192 ret = buf;
193 }
194
195 return ret;
196}
197
198#endif
199
200static const char* handle_special_dirs(const char* dir, unsigned flags,
201 char *buf, const size_t bufsize)
202{
203 (void) flags; (void) buf; (void) bufsize;
204#ifdef HAVE_SPECIAL_DIRS
205 if (!strncmp(HOME_DIR, dir, HOME_DIR_LEN))
206 {
207 const char *p = dir + HOME_DIR_LEN;
208 while (*p == '/') p++;
209 snprintf(buf, bufsize, "%s/%s", rbhome, p);
210 dir = buf;
211 }
212 else if (!strncmp(ROCKBOX_DIR, dir, ROCKBOX_DIR_LEN))
213 dir = _get_user_file_path(dir, flags, buf, bufsize);
214#endif
215#ifdef HAVE_MULTIDRIVE
216 dir = handle_special_links(dir, flags, buf, bufsize);
217#endif
218 return dir;
219}
220
221int app_open(const char *name, int o, ...)
222{
223 char realpath[MAX_PATH];
224 va_list ap;
225 int fd;
226 int flags = IS_FILE;
227 if (o & (O_CREAT|O_RDWR|O_TRUNC|O_WRONLY))
228 flags |= NEED_WRITE;
229
230 name = handle_special_dirs(name, flags, realpath, sizeof(realpath));
231
232 va_start(ap, o);
233 fd = open(name, o, va_arg(ap, unsigned int));
234 va_end(ap);
235
236 return fd;
237}
238
239int app_creat(const char* name, mode_t mode)
240{
241 return app_open(name, O_CREAT|O_WRONLY|O_TRUNC, mode);
242}
243
244int app_remove(const char *name)
245{
246 char realpath[MAX_PATH];
247 const char *fname = handle_special_dirs(name, NEED_WRITE, realpath, sizeof(realpath));
248
249 return remove(fname);
250}
251
252int app_rename(const char *old, const char *new)
253{
254 char realpath_old[MAX_PATH], realpath_new[MAX_PATH];
255 const char *final_old, *final_new;
256
257 final_old = handle_special_dirs(old, NEED_WRITE, realpath_old, sizeof(realpath_old));
258 final_new = handle_special_dirs(new, NEED_WRITE, realpath_new, sizeof(realpath_new));
259
260 return rename(final_old, final_new);
261}
262
263/* need to wrap around DIR* because we need to save the parent's
264 * directory path in order to determine dirinfo, required to implement
265 * get_dir_info() */
266struct __dir {
267 DIR *dir;
268#ifdef HAVE_MULTIDRIVE
269 int volumes_returned;
270 /* A crc of rbhome is used to speed op the common case where
271 * readdir()/get_dir_info() is called on non-rbhome paths, because
272 * each call needs to check against rbhome for the virtual
273 * mount point of the external storage */
274 uint32_t path_hash;
275#endif
276 char path[];
277};
278
279struct dirinfo dir_get_info(DIR* _parent, struct dirent *dir)
280{
281 struct __dir *parent = (struct __dir*)_parent;
282 struct stat s;
283 struct tm *tm = NULL;
284 struct dirinfo ret;
285 char path[MAX_PATH];
286
287 memset(&ret, 0, sizeof(ret));
288
289#ifdef HAVE_MULTIDRIVE
290 char vol_string[VOL_ENUM_POS + 8];
291 sprintf(vol_string, VOL_NAMES, 1);
292 if (UNLIKELY(rbhome_hash == parent->path_hash) &&
293 /* compare path anyway because of possible hash collision */
294 !strcmp(vol_string, dir->d_name))
295 {
296 ret.attribute = ATTR_LINK;
297 strcpy(path, MULTIDRIVE_DIR);
298 }
299 else
300#endif
301 snprintf(path, sizeof(path), "%s/%s", parent->path, dir->d_name);
302
303
304 if (!lstat(path, &s))
305 {
306 int err = 0;
307 if (S_ISLNK(s.st_mode))
308 {
309 ret.attribute |= ATTR_LINK;
310 err = stat(path, &s);
311 }
312 if (!err)
313 {
314 if (S_ISDIR(s.st_mode))
315 ret.attribute |= ATTR_DIRECTORY;
316
317 ret.size = s.st_size;
318 if ((tm = localtime(&(s.st_mtime))))
319 {
320 ret.wrtdate = ((tm->tm_year - 80) << 9) |
321 ((tm->tm_mon + 1) << 5) |
322 tm->tm_mday;
323 ret.wrttime = (tm->tm_hour << 11) |
324 (tm->tm_min << 5) |
325 (tm->tm_sec >> 1);
326 }
327 }
328 }
329
330 return ret;
331}
332
333DIR* app_opendir(const char *_name)
334{
335 size_t name_len;
336 char realpath[MAX_PATH];
337 const char *name = handle_special_dirs(_name, 0, realpath, sizeof(realpath));
338 name_len = strlen(name);
339 char *buf = malloc(sizeof(struct __dir) + name_len+1);
340 if (!buf)
341 return NULL;
342
343 struct __dir *this = (struct __dir*)buf;
344 /* carefully remove any trailing slash from the input, so that
345 * hash/path matching in readdir() works properly */
346 while (name[name_len-1] == '/' && name_len > 1)
347 name_len -= 1;
348 /* strcpy cannot be used because of trailing slashes */
349 memcpy(this->path, name, name_len);
350 this->path[name_len] = 0;
351 this->dir = opendir(this->path);
352
353 if (!this->dir)
354 {
355 free(buf);
356 return NULL;
357 }
358#ifdef HAVE_MULTIDRIVE
359 this->volumes_returned = 0;
360 this->path_hash = crc_32((const void *)this->path, name_len, 0xffffffff);
361#endif
362
363 return (DIR*)this;
364}
365
366int app_closedir(DIR *dir)
367{
368 struct __dir *this = (struct __dir*)dir;
369 int ret = closedir(this->dir);
370 free(this);
371 return ret;
372}
373
374
375struct dirent* app_readdir(DIR* dir)
376{
377 struct __dir *d = (struct __dir*)dir;
378#ifdef HAVE_MULTIDRIVE
379 /* this is not MT-safe but OK according to man readdir */
380 static struct dirent voldir;
381 if (UNLIKELY(rbhome_hash == d->path_hash)
382 && d->volumes_returned < (NUM_VOLUMES-1)
383 && volume_present(d->volumes_returned+1)
384 /* compare path anyway because of possible hash collision */
385 && !strcmp(d->path, rbhome))
386 {
387 d->volumes_returned += 1;
388 sprintf(voldir.d_name, VOL_NAMES, d->volumes_returned);
389 return &voldir;
390 }
391#endif
392 return readdir(d->dir);
393}
394
395
396int app_mkdir(const char* name)
397{
398 char realpath[MAX_PATH];
399 const char *fname = handle_special_dirs(name, NEED_WRITE, realpath, sizeof(realpath));
400 return mkdir(fname, 0777);
401}
402
403
404int app_rmdir(const char* name)
405{
406 char realpath[MAX_PATH];
407 const char *fname = handle_special_dirs(name, NEED_WRITE, realpath, sizeof(realpath));
408 return rmdir(fname);
409}
410
411
412/* On MD we create a virtual symlink for the external drive,
413 * for this we need to override readlink(). */
414ssize_t app_readlink(const char *path, char *buf, size_t bufsiz)
415{
416 char _buf[MAX_PATH];
417 (void) path; (void) buf; (void) bufsiz;
418 path = handle_special_dirs(path, 0, _buf, sizeof(_buf));
419#ifdef HAVE_MULTIDRIVE
420 /* if path == _buf then we can be sure handle_special_dir() did something
421 * and path is not an ordinary directory */
422 if (path == _buf && !strncmp(path, MULTIDRIVE_DIR, sizeof(MULTIDRIVE_DIR)-1))
423 {
424 /* copying NUL is not required as per readlink specification */
425 ssize_t len = strlen(path);
426 memcpy(buf, path, len);
427 return len;
428 }
429#endif
430 /* does not append NUL !! */
431 return readlink(path, buf, bufsiz);
432}
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 @@
28 28
29#include <stdio.h> 29#include <stdio.h>
30#include "config.h" 30#include "config.h"
31#include "system.h"
32#include "thread.h"
31#include "file.h" 33#include "file.h"
32#include "debug.h" 34#include "debug.h"
33#include "rbunicode.h" 35#include "rbunicode.h"
34#include "rbpaths.h" 36#include "rbpaths.h"
37#include "pathfuncs.h"
38#include "core_alloc.h"
35 39
36#ifndef O_BINARY 40#ifndef O_BINARY
37#define O_BINARY 0 41#define O_BINARY 0
38#endif 42#endif
43#ifndef O_NOISODECODE
44#define O_NOISODECODE 0
45#endif
39 46
40static int default_codepage = 0; 47#define getle16(p) (p[0] | (p[1] >> 8))
41static int loaded_cp_table = 0; 48#define getbe16(p) ((p[1] << 8) | p[0])
42
43#ifdef HAVE_LCD_BITMAP
44 49
45#define MAX_CP_TABLE_SIZE 32768 50#if !defined (__PCTOOL__) && (CONFIG_PLATFORM & PLATFORM_NATIVE)
46#define NUM_TABLES 5 51/* Because file scanning uses the default CP table when matching entries,
52 on-demand loading is not feasible; we also must use the filesystem lock */
53#include "file_internal.h"
54#else /* APPLICATION */
55#ifdef __PCTOOL__
56#define yield()
57#endif
58#define open_noiso_internal open
59#endif /* !APPLICATION */
60
61#if 0 /* not needed just now (will probably end up a spinlock) */
62#include "mutex.h"
63static struct mutex cp_mutex SHAREDBSS_ATTR;
64#define cp_lock_init() mutex_init(&cp_mutex)
65#define cp_lock_enter() mutex_lock(&cp_mutex)
66#define cp_lock_leave() mutex_unlock(&cp_mutex)
67#else
68#define cp_lock_init() do {} while (0)
69#define cp_lock_enter() asm volatile ("")
70#define cp_lock_leave() asm volatile ("")
71#endif
47 72
48static const char * const filename[NUM_TABLES] = 73enum cp_tid
49{ 74{
50 CODEPAGE_DIR"/iso.cp", 75 CP_TID_NONE = -1,
51 CODEPAGE_DIR"/932.cp", /* SJIS */ 76 CP_TID_ISO,
52 CODEPAGE_DIR"/936.cp", /* GB2312 */ 77 CP_TID_932,
53 CODEPAGE_DIR"/949.cp", /* KSX1001 */ 78 CP_TID_936,
54 CODEPAGE_DIR"/950.cp" /* BIG5 */ 79 CP_TID_949,
80 CP_TID_950,
55}; 81};
56 82
57static const char cp_2_table[NUM_CODEPAGES] = 83struct cp_info
58{ 84{
59 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 4, 5, 0 85 int8_t tid;
86 const char *filename;
87 const char *name;
60}; 88};
61 89
62static const char * const name_codepages[NUM_CODEPAGES+1] = 90#ifdef HAVE_LCD_BITMAP
63{
64 "ISO-8859-1",
65 "ISO-8859-7",
66 "ISO-8859-8",
67 "CP1251",
68 "ISO-8859-11",
69 "CP1256",
70 "ISO-8859-9",
71 "ISO-8859-2",
72 "CP1250",
73 "CP1252",
74 "SJIS",
75 "GB-2312",
76 "KSX-1001",
77 "BIG5",
78 "UTF-8",
79 "unknown"
80};
81 91
82#if defined(APPLICATION) && defined(__linux__) 92#define MAX_CP_TABLE_SIZE 32768
83static const char * const name_codepages_linux[NUM_CODEPAGES+1] =
84{
85 /* "ISO-8859-1" */ "iso8859-1",
86 /* "ISO-8859-7" */ "iso8859-7",
87 /* "ISO-8859-8" */ "iso8859-8",
88 /* "CP1251" */ "cp1251",
89 /* "ISO-8859-11"*/ "iso8859-11",
90 /* "CP1256" */ "cp1256",
91 /* "ISO-8859-9" */ "iso8859-9",
92 /* "ISO-8859-2" */ "iso8859-2",
93 /* "CP1250" */ "cp1250",
94 /* "CP1252" */ "iso8859-15", /* closest, linux doesnt have a codepage named cp1252 */
95 /* "SJIS" */ "cp932",
96 /* "GB-2312" */ "cp936",
97 /* "KSX-1001" */ "cp949",
98 /* "BIG5" */ "cp950",
99 /* "UTF-8" */ "utf8",
100 /* "unknown" */ "cp437"
101};
102 93
103const char *get_current_codepage_name_linux(void) 94#define CPF_ISO "iso.cp"
95#define CPF_932 "932.cp" /* SJIS */
96#define CPF_936 "936.cp" /* GB2312 */
97#define CPF_949 "949.cp" /* KSX1001 */
98#define CPF_950 "950.cp" /* BIG5 */
99
100static const struct cp_info cp_info[NUM_CODEPAGES+1] =
104{ 101{
105 if (default_codepage < 0 || default_codepage >= NUM_CODEPAGES) 102 [0 ... NUM_CODEPAGES] = { CP_TID_NONE, NULL , "unknown" },
106 return name_codepages_linux[NUM_CODEPAGES]; 103 [ISO_8859_1] = { CP_TID_NONE, NULL , "ISO-8859-1" },
107 return name_codepages_linux[default_codepage]; 104 [ISO_8859_7] = { CP_TID_ISO , CPF_ISO, "ISO-8859-7" },
108} 105 [ISO_8859_8] = { CP_TID_ISO , CPF_ISO, "ISO-8859-8" },
109#endif 106 [WIN_1251] = { CP_TID_ISO , CPF_ISO, "CP1251" },
107 [ISO_8859_11] = { CP_TID_ISO , CPF_ISO, "ISO-8859-11" },
108 [WIN_1256] = { CP_TID_ISO , CPF_ISO, "CP1256" },
109 [ISO_8859_9] = { CP_TID_ISO , CPF_ISO, "ISO-8859-9" },
110 [ISO_8859_2] = { CP_TID_ISO , CPF_ISO, "ISO-8859-2" },
111 [WIN_1250] = { CP_TID_ISO , CPF_ISO, "CP1250" },
112 [WIN_1252] = { CP_TID_ISO , CPF_ISO, "CP1252" },
113 [SJIS] = { CP_TID_932 , CPF_932, "SJIS" },
114 [GB_2312] = { CP_TID_936 , CPF_936, "GB-2312" },
115 [KSX_1001] = { CP_TID_949 , CPF_949, "KSX-1001" },
116 [BIG_5] = { CP_TID_950 , CPF_950, "BIG5" },
117 [UTF_8] = { CP_TID_NONE, NULL , "UTF-8" },
118};
110 119
111#else /* !HAVE_LCD_BITMAP, reduced support */ 120#else /* !HAVE_LCD_BITMAP, reduced support */
112 121
113#define MAX_CP_TABLE_SIZE 768 122#define MAX_CP_TABLE_SIZE 768
114#define NUM_TABLES 1
115 123
116static const char * const filename[NUM_TABLES] = { 124#define CPF_ISOMINI "isomini.cp"
117 CODEPAGE_DIR"/isomini.cp"
118};
119 125
120static const char cp_2_table[NUM_CODEPAGES] = 126static const struct cp_info cp_info[NUM_CODEPAGES+1] =
121{ 127{
122 0, 1, 1, 1, 1, 1, 1, 0 128 [0 ... NUM_CODEPAGES] = { CP_TID_NONE, NULL , "unknown" },
129 [ISO_8859_1] = { CP_TID_NONE, NULL , "ISO-8859-1" },
130 [ISO_8859_7] = { CP_TID_ISO , CPF_ISOMINI, "ISO-8859-7" },
131 [WIN_1251] = { CP_TID_ISO , CPF_ISOMINI, "CP1251" },
132 [ISO_8859_9] = { CP_TID_ISO , CPF_ISOMINI, "ISO-8859-9" },
133 [ISO_8859_2] = { CP_TID_ISO , CPF_ISOMINI, "ISO-8859-2" },
134 [WIN_1250] = { CP_TID_ISO , CPF_ISOMINI, "CP1250" },
135 [WIN_1252] = { CP_TID_ISO , CPF_ISOMINI, "CP1252" },
136 [UTF_8] = { CP_TID_ISO , NULL , "UTF-8" },
123}; 137};
124 138
125static const char * const name_codepages[NUM_CODEPAGES+1] = 139#endif /* HAVE_LCD_BITMAP */
140
141static int default_cp = INIT_CODEPAGE;
142static int default_cp_tid = CP_TID_NONE;
143static int default_cp_handle = 0;
144static int volatile default_cp_table_ref = 0;
145
146static int loaded_cp_tid = CP_TID_NONE;
147static int volatile cp_table_ref = 0;
148#define CP_LOADING BIT_N(sizeof(int)*8-1) /* guard against multi loaders */
149
150/* non-default codepage table buffer (cannot be bufalloced! playback itself
151 may be making the load request) */
152static unsigned short codepage_table[MAX_CP_TABLE_SIZE+1];
153
154#if defined(APPLICATION) && defined(__linux__)
155static const char * const name_codepages_linux[NUM_CODEPAGES+1] =
126{ 156{
127 "ISO-8859-1", 157 [0 ... NUM_CODEPAGES] = "unknown",
128 "ISO-8859-7", 158 [ISO_8859_1] = "iso8859-1",
129 "CP1251", 159 [ISO_8859_7] = "iso8859-7",
130 "ISO-8859-9", 160 [ISO_8859_8] = "iso8859-8",
131 "ISO-8859-2", 161 [WIN_1251] = "cp1251",
132 "CP1250", 162 [ISO_8859_11] = "iso8859-11",
133 "CP1252", 163 [WIN_1256] = "cp1256",
134 "UTF-8", 164 [ISO_8859_9] = "iso8859-9",
135 "unknown" 165 [ISO_8859_2] = "iso8859-2",
166 [WIN_1250] = "cp1250",
167 /* iso8859-15 is closest, linux doesnt have a codepage named cp1252 */
168 [WIN_1252] = "iso8859-15",
169 [SJIS] = "cp932",
170 [GB_2312] = "cp936",
171 [KSX_1001] = "cp949",
172 [BIG_5] = "cp950",
173 [UTF_8] = "utf8",
136}; 174};
137 175
138#endif 176const char *get_current_codepage_name_linux(void)
139 177{
140static unsigned short codepage_table[MAX_CP_TABLE_SIZE]; 178 int cp = default_cp;
179 if (cp < 0 || cp>= NUM_CODEPAGES)
180 cp = NUM_CODEPAGES;
181 return name_codepages_linux[cp];
182}
183#endif /* defined(APPLICATION) && defined(__linux__) */
141 184
142static const unsigned char utf8comp[6] = 185static const unsigned char utf8comp[6] =
143{ 186{
144 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC 187 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC
145}; 188};
146 189
147/* Load codepage file into memory */ 190static inline void cptable_tohw16(uint16_t *buf, unsigned int count)
148static int load_cp_table(int cp)
149{ 191{
150 int i = 0; 192#ifdef ROCKBOX_BIG_ENDIAN
151 int table = cp_2_table[cp]; 193 for (unsigned int i = 0; i < count; i++)
152 int file, tablesize; 194 buf[i] = letoh16(buf[i]);
153 unsigned char tmp[2]; 195#endif
196 (void)buf; (void)count;
197}
154 198
155 if (table == 0 || table == loaded_cp_table) 199static int move_callback(int handle, void *current, void *new)
156 return 1; 200{
201 /* we don't keep a pointer but we have to stop it if this applies to a
202 buffer not yet swapped-in since it will likely be in use in an I/O
203 call */
204 return (handle != default_cp_handle || default_cp_table_ref != 0) ?
205 BUFLIB_CB_CANNOT_MOVE : BUFLIB_CB_OK;
206 (void)current; (void)new;
207}
157 208
158 file = open(filename[table-1], O_RDONLY|O_BINARY); 209static int alloc_and_load_cp_table(int cp, void *buf)
210{
211 static struct buflib_callbacks ops =
212 { .move_callback = move_callback };
159 213
160 if (file < 0) { 214 /* alloc and read only if there is an associated file */
161 DEBUGF("Can't open codepage file: %s.cp\n", filename[table-1]); 215 const char *filename = cp_info[cp].filename;
216 if (!filename)
162 return 0; 217 return 0;
218
219 char path[MAX_PATH];
220 if (path_append(path, CODEPAGE_DIR, filename, sizeof (path))
221 >= sizeof (path)) {
222 return -1;
163 } 223 }
164 224
165 tablesize = filesize(file) / 2; 225 /* must be opened without a chance of reentering from FS code */
226 int fd = open_noiso_internal(path, O_RDONLY);
227 if (fd < 0)
228 return -1;
166 229
167 if (tablesize > MAX_CP_TABLE_SIZE) { 230 off_t size = filesize(fd);
168 DEBUGF("Invalid codepage file: %s.cp\n", filename[table-1]);
169 close(file);
170 return 0;
171 }
172 231
173 while (i < tablesize) { 232 if (size > 0 && size <= MAX_CP_TABLE_SIZE*2 &&
174 if (!read(file, tmp, 2)) { 233 !(size % (off_t)sizeof (uint16_t))) {
175 DEBUGF("Can't read from codepage file: %s.cp\n", 234
176 filename[table-1]); 235 /* if the buffer is provided, use that but don't alloc */
177 loaded_cp_table = 0; 236 int handle = buf ? 0 : core_alloc_ex(filename, size, &ops);
178 return 0; 237 if (handle > 0)
238 buf = core_get_data(handle);
239
240 if (buf && read(fd, buf, size) == size) {
241 close(fd);
242 cptable_tohw16(buf, size / sizeof (uint16_t));
243 return handle;
179 } 244 }
180 codepage_table[i++] = (tmp[1] << 8) | tmp[0]; 245
246 if (handle > 0)
247 core_free(handle);
181 } 248 }
182 249
183 loaded_cp_table = table; 250 close(fd);
184 close(file); 251 return -1;
185 return 1;
186} 252}
187 253
188/* Encode a UCS value as UTF-8 and return a pointer after this UTF-8 char. */ 254/* 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)
205unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8, 271unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8,
206 int cp, int count) 272 int cp, int count)
207{ 273{
208 unsigned short ucs, tmp; 274 uint16_t *table = NULL;
275
276 cp_lock_enter();
277
278 if (cp < 0 || cp >= NUM_CODEPAGES)
279 cp = default_cp;
209 280
210 if (cp == -1) /* use default codepage */ 281 int tid = cp_info[cp].tid;
211 cp = default_codepage;
212 282
213 if (!load_cp_table(cp)) cp = 0; 283 while (1) {
284 if (tid == default_cp_tid) {
285 /* use default table */
286 if (default_cp_handle > 0) {
287 table = core_get_data(default_cp_handle);
288 default_cp_table_ref++;
289 }
290
291 break;
292 }
293
294 bool load = false;
295
296 if (tid == loaded_cp_tid) {
297 /* use loaded table */
298 if (!(cp_table_ref & CP_LOADING)) {
299 if (tid != CP_TID_NONE) {
300 table = codepage_table;
301 cp_table_ref++;
302 }
303
304 break;
305 }
306 } else if (cp_table_ref == 0) {
307 load = true;
308 cp_table_ref |= CP_LOADING;
309 }
310
311 /* alloc and load must be done outside the lock */
312 cp_lock_leave();
313
314 if (!load) {
315 yield();
316 } else if (alloc_and_load_cp_table(cp, codepage_table) < 0) {
317 cp = INIT_CODEPAGE; /* table may be clobbered now */
318 tid = cp_info[cp].tid;
319 }
320
321 cp_lock_enter();
322
323 if (load) {
324 loaded_cp_tid = tid;
325 cp_table_ref &= ~CP_LOADING;
326 }
327 }
328
329 cp_lock_leave();
214 330
215 while (count--) { 331 while (count--) {
332 unsigned short ucs, tmp;
333
216 if (*iso < 128 || cp == UTF_8) /* Already UTF-8 */ 334 if (*iso < 128 || cp == UTF_8) /* Already UTF-8 */
217 *utf8++ = *iso++; 335 *utf8++ = *iso++;
218 336
219 else { 337 else {
220 338 /* tid tells us which table to use and how */
221 /* cp tells us which codepage to convert from */ 339 switch (tid) {
222 switch (cp) { 340 case CP_TID_ISO: /* Greek */
223 case ISO_8859_7: /* Greek */ 341 /* Hebrew */
224 case WIN_1252: /* Western European */ 342 /* Cyrillic */
225 case WIN_1251: /* Cyrillic */ 343 /* Thai */
226 case ISO_8859_9: /* Turkish */ 344 /* Arabic */
227 case ISO_8859_2: /* Latin Extended */ 345 /* Turkish */
228 case WIN_1250: /* Central European */ 346 /* Latin Extended */
229#ifdef HAVE_LCD_BITMAP 347 /* Central European */
230 case ISO_8859_8: /* Hebrew */ 348 /* Western European */
231 case ISO_8859_11: /* Thai */
232 case WIN_1256: /* Arabic */
233#endif
234 tmp = ((cp-1)*128) + (*iso++ - 128); 349 tmp = ((cp-1)*128) + (*iso++ - 128);
235 ucs = codepage_table[tmp]; 350 ucs = table[tmp];
236 break; 351 break;
237 352
238#ifdef HAVE_LCD_BITMAP 353#ifdef HAVE_LCD_BITMAP
239 case SJIS: /* Japanese */ 354 case CP_TID_932: /* Japanese */
240 if (*iso > 0xA0 && *iso < 0xE0) { 355 if (*iso > 0xA0 && *iso < 0xE0) {
241 tmp = *iso++ | (0xA100 - 0x8000); 356 tmp = *iso++ | (0xA100 - 0x8000);
242 ucs = codepage_table[tmp]; 357 ucs = table[tmp];
243 break; 358 break;
244 } 359 }
245 360
246 case GB_2312: /* Simplified Chinese */ 361 case CP_TID_936: /* Simplified Chinese */
247 case KSX_1001: /* Korean */ 362 case CP_TID_949: /* Korean */
248 case BIG_5: /* Traditional Chinese */ 363 case CP_TID_950: /* Traditional Chinese */
249 if (count < 1 || !iso[1]) { 364 if (count < 1 || !iso[1]) {
250 ucs = *iso++; 365 ucs = *iso++;
251 break; 366 break;
@@ -256,7 +371,7 @@ unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8,
256 tmp = *iso++ << 8; 371 tmp = *iso++ << 8;
257 tmp |= *iso++; 372 tmp |= *iso++;
258 tmp -= 0x8000; 373 tmp -= 0x8000;
259 ucs = codepage_table[tmp]; 374 ucs = table[tmp];
260 count--; 375 count--;
261 break; 376 break;
262#endif /* HAVE_LCD_BITMAP */ 377#endif /* HAVE_LCD_BITMAP */
@@ -271,6 +386,17 @@ unsigned char* iso_decode(const unsigned char *iso, unsigned char *utf8,
271 utf8 = utf8encode(ucs, utf8); 386 utf8 = utf8encode(ucs, utf8);
272 } 387 }
273 } 388 }
389
390 if (table) {
391 cp_lock_enter();
392 if (table == codepage_table) {
393 cp_table_ref--;
394 } else {
395 default_cp_table_ref--;
396 }
397 cp_lock_leave();
398 }
399
274 return utf8; 400 return utf8;
275} 401}
276 402
@@ -288,7 +414,7 @@ unsigned char* utf16LEdecode(const unsigned char *utf16, unsigned char *utf8,
288 utf16 += 4; 414 utf16 += 4;
289 count -= 2; 415 count -= 2;
290 } else { 416 } else {
291 ucs = (utf16[0] | (utf16[1] << 8)); 417 ucs = getle16(utf16);
292 utf16 += 2; 418 utf16 += 2;
293 count -= 1; 419 count -= 1;
294 } 420 }
@@ -310,7 +436,7 @@ unsigned char* utf16BEdecode(const unsigned char *utf16, unsigned char *utf8,
310 utf16 += 4; 436 utf16 += 4;
311 count -= 2; 437 count -= 2;
312 } else { 438 } else {
313 ucs = (utf16[0] << 8) | utf16[1]; 439 ucs = getbe16(utf16);
314 utf16 += 2; 440 utf16 += 2;
315 count -= 1; 441 count -= 1;
316 } 442 }
@@ -400,8 +526,50 @@ const unsigned char* utf8decode(const unsigned char *utf8, unsigned short *ucs)
400 526
401void set_codepage(int cp) 527void set_codepage(int cp)
402{ 528{
403 default_codepage = cp; 529 if (cp < 0 || cp >= NUM_CODEPAGES)
404 return; 530 cp = NUM_CODEPAGES;
531
532 /* load first then swap if load is successful, else just leave it; if
533 handle is 0 then we just free the current one; this won't happen often
534 thus we don't worry about reusing it and consequently avoid possible
535 clobbering of the existing one */
536
537 int handle = -1;
538 int tid = cp_info[cp].tid;
539
540 while (1) {
541 cp_lock_enter();
542
543 if (default_cp_tid == tid)
544 break;
545
546 if (handle >= 0 && default_cp_table_ref == 0) {
547 int hold = default_cp_handle;
548 default_cp_handle = handle;
549 handle = hold;
550 default_cp_tid = tid;
551 break;
552 }
553
554 /* alloc and load must be done outside the lock */
555 cp_lock_leave();
556
557 if (handle < 0 && (handle = alloc_and_load_cp_table(cp, NULL)) < 0)
558 return; /* OOM; change nothing */
559
560 yield();
561 }
562
563 default_cp = cp;
564 cp_lock_leave();
565
566 if (handle > 0)
567 core_free(handle);
568}
569
570int get_codepage(void)
571{
572 return default_cp;
405} 573}
406 574
407/* seek to a given char in a utf8 string and 575/* seek to a given char in a utf8 string and
@@ -418,9 +586,16 @@ int utf8seek(const unsigned char* utf8, int offset)
418 return pos; 586 return pos;
419} 587}
420 588
421const char* get_codepage_name(int cp) 589const char * get_codepage_name(int cp)
422{ 590{
423 if (cp < 0 || cp>= NUM_CODEPAGES) 591 if (cp < 0 || cp >= NUM_CODEPAGES)
424 return name_codepages[NUM_CODEPAGES]; 592 cp = NUM_CODEPAGES;
425 return name_codepages[cp]; 593 return cp_info[cp].name;
426} 594}
595
596#if 0 /* not needed just now */
597void unicode_init(void)
598{
599 cp_lock_init();
600}
601#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 @@
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2002 by Linus Nielsen Feltzing 10 * Copyright (C) 2002 by Linus Nielsen Feltzing
11 * Copyright (C) 2014 by Michael Sevakis
11 * 12 *
12 * This program is free software; you can redistribute it and/or 13 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License 14 * modify it under the terms of the GNU General Public License
@@ -18,27 +19,44 @@
18 * KIND, either express or implied. 19 * KIND, either express or implied.
19 * 20 *
20 ****************************************************************************/ 21 ****************************************************************************/
21#include <stdio.h> 22#include "config.h"
23#include "system.h"
24#include "sys/types.h"
22#include <string.h> 25#include <string.h>
23#include <stdlib.h>
24#include <ctype.h> 26#include <ctype.h>
25#include <stdbool.h> 27#include <stdlib.h>
26#include "fat.h" 28#include <stdio.h>
29#include "fs_attr.h"
30#include "pathfuncs.h"
31#include "disk_cache.h"
32#include "file_internal.h" /* for struct filestr_cache */
27#include "storage.h" 33#include "storage.h"
28#include "debug.h"
29#include "panic.h"
30#include "system.h"
31#include "timefuncs.h" 34#include "timefuncs.h"
32#include "kernel.h"
33#include "rbunicode.h" 35#include "rbunicode.h"
36#include "debug.h"
37#include "panic.h"
34/*#define LOGF_ENABLE*/ 38/*#define LOGF_ENABLE*/
35#include "logf.h" 39#include "logf.h"
36 40
37#define BYTES2INT16(array,pos) \ 41#define BYTES2INT32(array, pos) \
38 (array[pos] | (array[pos+1] << 8 )) 42 (((uint32_t)array[pos+0] << 0) | \
39#define BYTES2INT32(array,pos) \ 43 ((uint32_t)array[pos+1] << 8) | \
40 ((long)array[pos] | ((long)array[pos+1] << 8 ) | \ 44 ((uint32_t)array[pos+2] << 16) | \
41 ((long)array[pos+2] << 16 ) | ((long)array[pos+3] << 24 )) 45 ((uint32_t)array[pos+3] << 24))
46
47#define INT322BYTES(array, pos, val) \
48 ((array[pos+0] = (uint32_t)(val) >> 0), \
49 (array[pos+1] = (uint32_t)(val) >> 8), \
50 (array[pos+2] = (uint32_t)(val) >> 16), \
51 (array[pos+3] = (uint32_t)(val) >> 24))
52
53#define BYTES2INT16(array, pos) \
54 (((uint32_t)array[pos+0] << 0) | \
55 ((uint32_t)array[pos+1] << 8))
56
57#define INT162BYTES(array, pos, val) \
58 ((array[pos+0] = (uint16_t)(val) >> 0), \
59 (array[pos+1] = (uint16_t)(val) >> 8))
42 60
43#define FATTYPE_FAT12 0 61#define FATTYPE_FAT12 0
44#define FATTYPE_FAT16 1 62#define FATTYPE_FAT16 1
@@ -83,82 +101,129 @@
83 101
84#define BPB_LAST_WORD 510 102#define BPB_LAST_WORD 510
85 103
104/* Short and long name directory entry template */
105union raw_dirent
106{
107 struct /* short entry */
108 {
109 uint8_t name[8+3]; /* 0 */
110 uint8_t attr; /* 11 */
111 uint8_t ntres; /* 12 */
112 uint8_t crttimetenth; /* 13 */
113 uint16_t crttime; /* 14 */
114 uint16_t crtdate; /* 16 */
115 uint16_t lstaccdate; /* 18 */
116 uint16_t fstclushi; /* 20 */
117 uint16_t wrttime; /* 22 */
118 uint16_t wrtdate; /* 24 */
119 uint16_t fstcluslo; /* 26 */
120 uint32_t filesize; /* 28 */
121 /* 32 */
122 };
123 struct /* long entry */
124 {
125 uint8_t ldir_ord; /* 0 */
126 uint8_t ldir_name1[10]; /* 1 */
127 uint8_t ldir_attr; /* 11 */
128 uint8_t ldir_type; /* 12 */
129 uint8_t ldir_chksum; /* 13 */
130 uint8_t ldir_name2[12]; /* 14 */
131 uint16_t ldir_fstcluslo; /* 26 */
132 uint8_t ldir_name3[4]; /* 28 */
133 /* 32 */
134 };
135 struct /* raw byte array */
136 {
137 uint8_t data[32]; /* 0 */
138 /* 32 */
139 };
140};
141
142
143/* at most 20 LFN entries */
144#define FATLONG_MAX_ORDER 20
145#define FATLONG_NAME_CHARS 13
146#define FATLONG_ORD_F_LAST 0x40
86 147
87/* attributes */ 148/* attributes */
88#define FAT_ATTR_LONG_NAME (FAT_ATTR_READ_ONLY | FAT_ATTR_HIDDEN | \ 149#define ATTR_LONG_NAME (ATTR_READ_ONLY | ATTR_HIDDEN | \
89 FAT_ATTR_SYSTEM | FAT_ATTR_VOLUME_ID) 150 ATTR_SYSTEM | ATTR_VOLUME_ID)
90#define FAT_ATTR_LONG_NAME_MASK (FAT_ATTR_READ_ONLY | FAT_ATTR_HIDDEN | \ 151#define ATTR_LONG_NAME_MASK (ATTR_READ_ONLY | ATTR_HIDDEN | \
91 FAT_ATTR_SYSTEM | FAT_ATTR_VOLUME_ID | \ 152 ATTR_SYSTEM | ATTR_VOLUME_ID | \
92 FAT_ATTR_DIRECTORY | FAT_ATTR_ARCHIVE ) 153 ATTR_DIRECTORY | ATTR_ARCHIVE )
154
155#define IS_LDIR_ATTR(attr) \
156 (((attr) & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME)
157
158#define IS_VOL_ID_ATTR(attr) \
159 (((attr) & (ATTR_VOLUME_ID | ATTR_DIRECTORY)) == ATTR_VOLUME_ID)
93 160
94/* NTRES flags */ 161/* NTRES flags */
95#define FAT_NTRES_LC_NAME 0x08 162#define FAT_NTRES_LC_NAME 0x08
96#define FAT_NTRES_LC_EXT 0x10 163#define FAT_NTRES_LC_EXT 0x10
97 164
98#define FATDIR_NAME 0 165#define CLUSTERS_PER_FAT_SECTOR (SECTOR_SIZE / 4)
99#define FATDIR_ATTR 11 166#define CLUSTERS_PER_FAT16_SECTOR (SECTOR_SIZE / 2)
100#define FATDIR_NTRES 12 167#define DIR_ENTRIES_PER_SECTOR (SECTOR_SIZE / DIR_ENTRY_SIZE)
101#define FATDIR_CRTTIMETENTH 13 168#define DIR_ENTRY_SIZE 32
102#define FATDIR_CRTTIME 14 169#define FAT_BAD_MARK 0x0ffffff7
103#define FATDIR_CRTDATE 16 170#define FAT_EOF_MARK 0x0ffffff8
104#define FATDIR_LSTACCDATE 18 171#define FAT16_BAD_MARK 0xfff7
105#define FATDIR_FSTCLUSHI 20 172#define FAT16_EOF_MARK 0xfff8
106#define FATDIR_WRTTIME 22 173
107#define FATDIR_WRTDATE 24 174struct fsinfo
108#define FATDIR_FSTCLUSLO 26 175{
109#define FATDIR_FILESIZE 28 176 unsigned long freecount; /* last known free cluster count */
110 177 unsigned long nextfree; /* first cluster to start looking for free
111#define FATLONG_ORDER 0 178 clusters, or 0xffffffff for no hint */
112#define FATLONG_TYPE 12
113#define FATLONG_CHKSUM 13
114#define FATLONG_LAST_LONG_ENTRY 0x40
115#define FATLONG_NAME_BYTES_PER_ENTRY 26
116/* at most 20 LFN entries, keep coherent with fat_dir->longname size ! */
117#define FATLONG_MAX_ORDER 20
118
119#define FATLONG_NAME_CHUNKS 3
120static unsigned char FATLONG_NAME_POS[FATLONG_NAME_CHUNKS] = {1, 14, 28};
121static unsigned char FATLONG_NAME_SIZE[FATLONG_NAME_CHUNKS] = {10, 12, 4};
122
123#define CLUSTERS_PER_FAT_SECTOR (SECTOR_SIZE / 4)
124#define CLUSTERS_PER_FAT16_SECTOR (SECTOR_SIZE / 2)
125#define DIR_ENTRIES_PER_SECTOR (SECTOR_SIZE / DIR_ENTRY_SIZE)
126#define DIR_ENTRY_SIZE 32
127#define NAME_BYTES_PER_ENTRY 13
128#define FAT_BAD_MARK 0x0ffffff7
129#define FAT_EOF_MARK 0x0ffffff8
130#define FAT_LONGNAME_PAD_BYTE 0xff
131#define FAT_LONGNAME_PAD_UCS 0xffff
132
133struct fsinfo {
134 uint32_t freecount; /* last known free cluster count */
135 uint32_t nextfree; /* first cluster to start looking for free
136 clusters, or 0xffffffff for no hint */
137}; 179};
138/* fsinfo offsets */ 180/* fsinfo offsets */
139#define FSINFO_FREECOUNT 488 181#define FSINFO_FREECOUNT 488
140#define FSINFO_NEXTFREE 492 182#define FSINFO_NEXTFREE 492
141 183
184#ifdef HAVE_FAT16SUPPORT
185#define BPB_FN_SET16(bpb, fn) (bpb)->fn##__ = fn##16
186#define BPB_FN_SET32(bpb, fn) (bpb)->fn##__ = fn##32
187#define BPB_FN_DECL(fn, args...) (*fn##__)(struct bpb *bpb , ##args)
188#define BPB_CALL(fn, bpb, args...) ((bpb)->fn##__(bpb , ##args))
189
190#define get_next_cluster(bpb, cluster) \
191 BPB_CALL(get_next_cluster, (bpb), (cluster))
192#define find_free_cluster(bpb, startcluster) \
193 BPB_CALL(find_free_cluster, (bpb), (startcluster))
194#define update_fat_entry(bpb, entry, value) \
195 BPB_CALL(update_fat_entry, (bpb), (entry), (value))
196#define fat_recalc_free_internal(bpb) \
197 BPB_CALL(fat_recalc_free_internal, (bpb))
198#else /* !HAVE_FAT16SUPPORT */
199#define get_next_cluster get_next_cluster32
200#define find_free_cluster find_free_cluster32
201#define update_fat_entry update_fat_entry32
202#define fat_recalc_free_internal fat_recalc_free_internal32
203#endif /* HAVE_FAT16SUPPORT */
204struct bpb;
205static void update_fsinfo32(struct bpb *fat_bpb);
206
142/* Note: This struct doesn't hold the raw values after mounting if 207/* Note: This struct doesn't hold the raw values after mounting if
143 * bpb_bytspersec isn't 512. All sector counts are normalized to 512 byte 208 * bpb_bytspersec isn't 512. All sector counts are normalized to 512 byte
144 * physical sectors. */ 209 * physical sectors. */
145struct bpb 210static struct bpb
146{ 211{
147 int bpb_bytspersec; /* Bytes per sector, typically 512 */ 212 unsigned long bpb_bytspersec; /* Bytes per sector, typically 512 */
148 unsigned int bpb_secperclus; /* Sectors per cluster */ 213 unsigned long bpb_secperclus; /* Sectors per cluster */
149 int bpb_rsvdseccnt; /* Number of reserved sectors */ 214 unsigned long bpb_rsvdseccnt; /* Number of reserved sectors */
150 int bpb_numfats; /* Number of FAT structures, typically 2 */ 215 unsigned long bpb_totsec16; /* Number of sectors on volume (old 16-bit) */
151 int bpb_totsec16; /* Number of sectors on the volume (old 16-bit) */ 216 uint8_t bpb_numfats; /* Number of FAT structures, typically 2 */
152 int bpb_media; /* Media type (typically 0xf0 or 0xf8) */ 217 uint8_t bpb_media; /* Media type (typically 0xf0 or 0xf8) */
153 int bpb_fatsz16; /* Number of used sectors per FAT structure */ 218 uint16_t bpb_fatsz16; /* Number of used sectors per FAT structure */
154 unsigned long bpb_totsec32; /* Number of sectors on the volume 219 unsigned long bpb_totsec32; /* Number of sectors on the volume
155 (new 32-bit) */ 220 (new 32-bit) */
156 unsigned int last_word; /* 0xAA55 */ 221 uint16_t last_word; /* 0xAA55 */
222 long bpb_rootclus;
157 223
158 /**** FAT32 specific *****/ 224 /**** FAT32 specific *****/
159 long bpb_fatsz32; 225 unsigned long bpb_fatsz32;
160 long bpb_rootclus; 226 unsigned long bpb_fsinfo;
161 long bpb_fsinfo;
162 227
163 /* variables for internal use */ 228 /* variables for internal use */
164 unsigned long fatsize; 229 unsigned long fatsize;
@@ -167,936 +232,1246 @@ struct bpb
167 unsigned long firstdatasector; 232 unsigned long firstdatasector;
168 unsigned long startsector; 233 unsigned long startsector;
169 unsigned long dataclusters; 234 unsigned long dataclusters;
235 unsigned long fatrgnstart;
236 unsigned long fatrgnend;
170 struct fsinfo fsinfo; 237 struct fsinfo fsinfo;
171#ifdef HAVE_FAT16SUPPORT 238#ifdef HAVE_FAT16SUPPORT
172 int bpb_rootentcnt; /* Number of dir entries in the root */ 239 unsigned int bpb_rootentcnt; /* Number of dir entries in the root */
173 /* internals for FAT16 support */ 240 /* internals for FAT16 support */
174 bool is_fat16; /* true if we mounted a FAT16 partition, false if FAT32 */ 241 unsigned long rootdirsectornum; /* sector offset of root dir relative to start
175 unsigned int rootdiroffset; /* sector offset of root dir relative to start 242 * of first pseudo cluster */
176 * of first pseudo cluster */ 243#endif /* HAVE_FAT16SUPPORT */
177#endif /* #ifdef HAVE_FAT16SUPPORT */ 244
178#ifdef HAVE_MULTIVOLUME 245 /** Additional information kept for each volume **/
179#ifdef HAVE_MULTIDRIVE 246#ifdef HAVE_FAT16SUPPORT
180 int drive; /* on which physical device is this located */ 247 uint8_t is_fat16; /* true if we mounted a FAT16 partition, false if FAT32 */
181#endif 248#endif
182 bool mounted; /* flag if this volume is mounted */ 249#ifdef HAVE_MULTIDRIVE
250 uint8_t drive; /* on which physical device is this located */
183#endif 251#endif
184};
185
186static struct bpb fat_bpbs[NUM_VOLUMES]; /* mounted partition info */
187static bool initialized = false;
188
189static int update_fsinfo(IF_MV_NONVOID(struct bpb* fat_bpb));
190static int flush_fat(IF_MV_NONVOID(struct bpb* fat_bpb));
191static int bpb_is_sane(IF_MV_NONVOID(struct bpb* fat_bpb));
192static void *cache_fat_sector(IF_MV(struct bpb* fat_bpb,)
193 long secnum, bool dirty);
194static void create_dos_name(const unsigned char *name, unsigned char *newname);
195static void randomize_dos_name(unsigned char *name);
196static unsigned long find_free_cluster(IF_MV(struct bpb* fat_bpb,)
197 unsigned long start);
198static int transfer(IF_MV(struct bpb* fat_bpb,) unsigned long start,
199 long count, char* buf, bool write );
200
201#define FAT_CACHE_SIZE 0x20
202#define FAT_CACHE_MASK (FAT_CACHE_SIZE-1)
203
204struct fat_cache_entry
205{
206 long secnum;
207 bool inuse;
208 bool dirty;
209#ifdef HAVE_MULTIVOLUME 252#ifdef HAVE_MULTIVOLUME
210 struct bpb* fat_vol ; /* shared cache for all volumes */ 253 uint8_t volume; /* on which volume is this located (shortcut) */
211#endif 254#endif
255 uint8_t mounted; /* true if volume is mounted, false otherwise */
256#ifdef HAVE_FAT16SUPPORT
257 /* some functions are different for different FAT types */
258 long BPB_FN_DECL(get_next_cluster, long);
259 long BPB_FN_DECL(find_free_cluster, long);
260 int BPB_FN_DECL(update_fat_entry, unsigned long, unsigned long);
261 void BPB_FN_DECL(fat_recalc_free_internal);
262#endif /* HAVE_FAT16SUPPORT */
263
264} fat_bpbs[NUM_VOLUMES]; /* mounted partition info */
265
266#define IS_FAT_SECTOR(bpb, sector) \
267 (!((sector) >= (bpb)->fatrgnend || (sector) < (bpb)->fatrgnstart))
268
269/* set error code and jump to routine exit */
270#define FAT_ERROR(_rc) \
271 ({ __builtin_constant_p(_rc) ? \
272 ({ if (_rc != RC) rc = (_rc); }) : \
273 ({ rc = (_rc); }); \
274 goto fat_error; })
275
276#define FAT_BPB(volume) \
277 ({ struct bpb * _bpb = &fat_bpbs[IF_MV_VOL(volume)]; \
278 if (!_bpb->mounted) \
279 { \
280 DEBUGF("%s() - volume %d not mounted\n", \
281 __func__, IF_MV_VOL(volume)); \
282 _bpb = NULL; \
283 } \
284 _bpb; })
285
286enum add_dir_entry_flags
287{
288 DIRENT_RETURN = 0x01, /* return the new short entry */
289 DIRENT_TEMPL = 0x0e, /* all TEMPL flags */
290 DIRENT_TEMPL_CRT = 0x02, /* use template crttime */
291 DIRENT_TEMPL_WRT = 0x04, /* use template wrttime */
292 DIRENT_TEMPL_ACC = 0x08, /* use template lstacc time */
293 DIRENT_TEMPL_TIMES = 0x0e, /* keep all time fields */
212}; 294};
213 295
214static char fat_cache_sectors[FAT_CACHE_SIZE][SECTOR_SIZE] CACHEALIGN_ATTR; 296struct fatlong_parse_state
215static struct fat_cache_entry fat_cache[FAT_CACHE_SIZE]; 297{
216static struct mutex cache_mutex SHAREDBSS_ATTR; 298 int ord_max;
217static struct mutex tempbuf_mutex; 299 int ord;
218static char fat_tempbuf[SECTOR_SIZE] CACHEALIGN_ATTR; 300 uint8_t chksum;
219static bool tempbuf_locked; 301};
220 302
221#if defined(HAVE_HOTSWAP) 303static void cache_commit(struct bpb *fat_bpb)
222void fat_lock(void)
223{ 304{
224 mutex_lock(&cache_mutex); 305 dc_lock_cache();
306#ifdef HAVE_FAT16SUPPORT
307 if (!fat_bpb->is_fat16)
308#endif
309 update_fsinfo32(fat_bpb);
310 dc_commit_all(IF_MV(fat_bpb->volume));
311 dc_unlock_cache();
225} 312}
226 313
227void fat_unlock(void) 314static void cache_discard(IF_MV_NONVOID(struct bpb *fat_bpb))
228{ 315{
229 mutex_unlock(&cache_mutex); 316 dc_lock_cache();
317 dc_discard_all(IF_MV(fat_bpb->volume));
318 dc_unlock_cache();
230} 319}
231#endif
232 320
233static long cluster2sec(IF_MV(struct bpb* fat_bpb,) long cluster) 321/* caches a FAT or data area sector */
322static void * cache_sector(struct bpb *fat_bpb, unsigned long secnum)
234{ 323{
235#ifndef HAVE_MULTIVOLUME 324 unsigned int flags;
236 struct bpb* fat_bpb = &fat_bpbs[0]; 325 void *buf = dc_cache_probe(IF_MV(fat_bpb->volume,) secnum, &flags);
237#endif
238#ifdef HAVE_FAT16SUPPORT
239 /* negative clusters (FAT16 root dir) don't get the 2 offset */
240 int zerocluster = cluster < 0 ? 0 : 2;
241#else
242 const long zerocluster = 2;
243#endif
244 326
245 if (cluster > (long)(fat_bpb->dataclusters + 1)) 327 if (!flags)
246 { 328 {
247 DEBUGF( "cluster2sec() - Bad cluster number (%ld)\n", cluster); 329 int rc = storage_read_sectors(IF_MD(fat_bpb->drive,)
248 return -1; 330 secnum + fat_bpb->startsector, 1, buf);
331 if (UNLIKELY(rc < 0))
332 {
333 DEBUGF("%s() - Could not read sector %ld"
334 " (error %d)\n", __func__, secnum, rc);
335 dc_discard_buf(buf);
336 return NULL;
337 }
249 } 338 }
250 339
251 return (cluster - zerocluster) * fat_bpb->bpb_secperclus 340 return buf;
252 + fat_bpb->firstdatasector;
253} 341}
254 342
255void fat_size(IF_MV(int volume,) unsigned long* size, unsigned long* free) 343/* returns a raw buffer for a sector; buffer counts as INUSE but filesystem
344 * contents are NOT loaded before returning - use when completely overwriting
345 * a sector's contents in order to avoid a fill */
346static void * cache_sector_buffer(IF_MV(struct bpb *fat_bpb,)
347 unsigned long secnum)
256{ 348{
257#ifndef HAVE_MULTIVOLUME 349 unsigned int flags;
258 const int volume = 0; 350 return dc_cache_probe(IF_MV(fat_bpb->volume,) secnum, &flags);
259#endif
260 struct bpb* fat_bpb = &fat_bpbs[volume];
261 if (size)
262 *size = fat_bpb->dataclusters * (fat_bpb->bpb_secperclus * SECTOR_SIZE / 1024);
263 if (free)
264 *free = fat_bpb->fsinfo.freecount * (fat_bpb->bpb_secperclus * SECTOR_SIZE / 1024);
265} 351}
266 352
267void fat_init(void) 353/* flush a cache buffer to storage */
354void dc_writeback_callback(IF_MV(int volume,) unsigned long sector, void *buf)
268{ 355{
269 unsigned int i; 356 struct bpb * const fat_bpb = &fat_bpbs[IF_MV_VOL(volume)];
357 unsigned int copies = !IS_FAT_SECTOR(fat_bpb, sector) ?
358 1 : fat_bpb->bpb_numfats;
359
360 sector += fat_bpb->startsector;
270 361
271 if (!initialized) 362 while (1)
272 { 363 {
273 initialized = true; 364 int rc = storage_write_sectors(IF_MD(fat_bpb->drive,) sector, 1, buf);
274 mutex_init(&cache_mutex); 365 if (rc < 0)
275 mutex_init(&tempbuf_mutex); 366 {
276 tempbuf_locked = false; 367 panicf("%s() - Could not write sector %ld"
368 " (error %d)\n", __func__, sector, rc);
369 }
370
371 if (--copies == 0)
372 break;
373
374 /* Update next FAT */
375 sector += fat_bpb->fatsize;
277 } 376 }
377}
278 378
279#ifdef HAVE_PRIORITY_SCHEDULING 379static void raw_dirent_set_fstclus(union raw_dirent *ent, long fstclus)
280 /* Disable this because it is dangerous due to the assumption that 380{
281 * mutex_unlock won't yield */ 381 ent->fstclushi = htole16(fstclus >> 16);
282 mutex_set_preempt(&cache_mutex, false); 382 ent->fstcluslo = htole16(fstclus & 0xffff);
283#endif 383}
284 384
285 /* mark the FAT cache as unused */ 385static int bpb_is_sane(struct bpb *fat_bpb)
286 for(i = 0;i < FAT_CACHE_SIZE;i++) 386{
387 if (fat_bpb->bpb_bytspersec % SECTOR_SIZE)
287 { 388 {
288 fat_cache[i].secnum = 8; /* We use a "safe" sector just in case */ 389 DEBUGF("%s() - Error: sector size is not sane (%lu)\n",
289 fat_cache[i].inuse = false; 390 __func__, fat_bpb->bpb_bytspersec);
290 fat_cache[i].dirty = false; 391 return -1;
291#ifdef HAVE_MULTIVOLUME
292 fat_cache[i].fat_vol = NULL;
293#endif
294 } 392 }
295#ifdef HAVE_MULTIVOLUME 393
296 /* mark the possible volumes as not mounted */ 394 if (fat_bpb->bpb_secperclus * fat_bpb->bpb_bytspersec > 128*1024ul)
297 for (i=0; i<NUM_VOLUMES;i++)
298 { 395 {
299 fat_bpbs[i].mounted = false; 396 DEBUGF("%s() - Error: cluster size is larger than 128K "
397 "(%lu * %lu = %lu)\n", __func__,
398 fat_bpb->bpb_bytspersec, fat_bpb->bpb_secperclus,
399 fat_bpb->bpb_bytspersec * fat_bpb->bpb_secperclus);
400 return -2;
300 } 401 }
301#endif
302}
303 402
304/* fat_mount_internal is split out of fat_mount() to avoid having both the sector 403 if (fat_bpb->bpb_numfats != 2)
305 * buffer used here and the sector buffer used by update_fsinfo() on stack */ 404 {
306static int fat_mount_internal(IF_MV(int volume,) IF_MD(int drive,) long startsector) 405 DEBUGF("%s() - Warning: NumFATS is not 2 (%u)\n",
307{ 406 __func__, fat_bpb->bpb_numfats);
308#ifndef HAVE_MULTIVOLUME 407 }
309 const int volume = 0;
310#endif
311 struct bpb* fat_bpb = &fat_bpbs[volume];
312 int rc;
313 int secmult;
314 long datasec;
315#ifdef HAVE_FAT16SUPPORT
316 int rootdirsectors;
317#endif
318 408
319 unsigned char* buf = fat_get_sector_buffer(); 409 if (fat_bpb->bpb_media != 0xf0 && fat_bpb->bpb_media < 0xf8)
320 /* Read the sector */
321 rc = storage_read_sectors(IF_MD(drive,) startsector,1,buf);
322 if(rc)
323 { 410 {
324 fat_release_sector_buffer(); 411 DEBUGF("%s() - Warning: Non-standard media type "
325 DEBUGF( "fat_mount() - Couldn't read BPB (error code %d)\n", rc); 412 "(0x%02x)\n", __func__, fat_bpb->bpb_media);
326 return rc * 10 - 1;
327 } 413 }
328 414
329 memset(fat_bpb, 0, sizeof(struct bpb)); 415 if (fat_bpb->last_word != 0xaa55)
330 fat_bpb->startsector = startsector; 416 {
331#ifdef HAVE_MULTIDRIVE 417 DEBUGF("%s() - Error: Last word is not "
332 fat_bpb->drive = drive; 418 "0xaa55 (0x%04x)\n", __func__, fat_bpb->last_word);
333#endif 419 return -3;
420 }
334 421
335 fat_bpb->bpb_bytspersec = BYTES2INT16(buf,BPB_BYTSPERSEC); 422 if (fat_bpb->fsinfo.freecount >
336 secmult = fat_bpb->bpb_bytspersec / SECTOR_SIZE; 423 (fat_bpb->totalsectors - fat_bpb->firstdatasector) /
337 /* Sanity check is performed later */ 424 fat_bpb->bpb_secperclus)
425 {
426 DEBUGF("%s() - Error: FSInfo.Freecount > disk size "
427 "(0x%04lx)\n", __func__,
428 (unsigned long)fat_bpb->fsinfo.freecount);
429 return -4;
430 }
338 431
339 fat_bpb->bpb_secperclus = secmult * buf[BPB_SECPERCLUS]; 432 return 0;
340 fat_bpb->bpb_rsvdseccnt = secmult * BYTES2INT16(buf,BPB_RSVDSECCNT); 433}
341 fat_bpb->bpb_numfats = buf[BPB_NUMFATS];
342 fat_bpb->bpb_media = buf[BPB_MEDIA];
343 fat_bpb->bpb_fatsz16 = secmult * BYTES2INT16(buf,BPB_FATSZ16);
344 fat_bpb->bpb_fatsz32 = secmult * BYTES2INT32(buf,BPB_FATSZ32);
345 fat_bpb->bpb_totsec16 = secmult * BYTES2INT16(buf,BPB_TOTSEC16);
346 fat_bpb->bpb_totsec32 = secmult * BYTES2INT32(buf,BPB_TOTSEC32);
347 fat_bpb->last_word = BYTES2INT16(buf,BPB_LAST_WORD);
348 434
349 /* calculate a few commonly used values */ 435static uint8_t shortname_checksum(const unsigned char *shortname)
350 if (fat_bpb->bpb_fatsz16 != 0) 436{
351 fat_bpb->fatsize = fat_bpb->bpb_fatsz16; 437 /* calculate shortname checksum */
352 else 438 uint8_t chksum = 0;
353 fat_bpb->fatsize = fat_bpb->bpb_fatsz32;
354 439
355 if (fat_bpb->bpb_totsec16 != 0) 440 for (unsigned int i = 0; i < 11; i++)
356 fat_bpb->totalsectors = fat_bpb->bpb_totsec16; 441 chksum = (chksum << 7) + (chksum >> 1) + shortname[i];
357 else
358 fat_bpb->totalsectors = fat_bpb->bpb_totsec32;
359 442
360#ifdef HAVE_FAT16SUPPORT 443 return chksum;
361 fat_bpb->bpb_rootentcnt = BYTES2INT16(buf,BPB_ROOTENTCNT); 444}
362 if (!fat_bpb->bpb_bytspersec)
363 {
364 fat_release_sector_buffer();
365 return -2;
366 }
367 rootdirsectors = secmult * ((fat_bpb->bpb_rootentcnt * DIR_ENTRY_SIZE
368 + fat_bpb->bpb_bytspersec - 1) / fat_bpb->bpb_bytspersec);
369#endif /* #ifdef HAVE_FAT16SUPPORT */
370 445
371 fat_bpb->firstdatasector = fat_bpb->bpb_rsvdseccnt 446static void parse_short_direntry(const union raw_dirent *ent,
372#ifdef HAVE_FAT16SUPPORT 447 struct fat_direntry *fatent)
373 + rootdirsectors 448{
374#endif 449 fatent->attr = ent->attr;
375 + fat_bpb->bpb_numfats * fat_bpb->fatsize; 450 fatent->crttimetenth = ent->crttimetenth;
451 fatent->crttime = letoh16(ent->crttime);
452 fatent->crtdate = letoh16(ent->crtdate);
453 fatent->lstaccdate = letoh16(ent->lstaccdate);
454 fatent->wrttime = letoh16(ent->wrttime);
455 fatent->wrtdate = letoh16(ent->wrtdate);
456 fatent->filesize = letoh32(ent->filesize);
457 fatent->firstcluster = ((uint32_t)letoh16(ent->fstcluslo) ) |
458 ((uint32_t)letoh16(ent->fstclushi) << 16);
376 459
377 /* Determine FAT type */ 460 /* fix the name */
378 datasec = fat_bpb->totalsectors - fat_bpb->firstdatasector; 461 bool lowercase = ent->ntres & FAT_NTRES_LC_NAME;
379 if (fat_bpb->bpb_secperclus) 462 unsigned char c = ent->name[0];
380 fat_bpb->dataclusters = datasec / fat_bpb->bpb_secperclus; 463
381 else 464 if (c == 0x05) /* special kanji char */
465 c = 0xe5;
466
467 int j = 0;
468
469 for (int i = 0; c != ' '; c = ent->name[i])
382 { 470 {
383 fat_release_sector_buffer(); 471 fatent->shortname[j++] = lowercase ? tolower(c) : c;
384 return -2;
385 }
386 472
387#ifdef TEST_FAT 473 if (++i >= 8)
388 /* 474 break;
389 we are sometimes testing with "illegally small" fat32 images,
390 so we don't use the proper fat32 test case for test code
391 */
392 if ( fat_bpb->bpb_fatsz16 )
393#else
394 if ( fat_bpb->dataclusters < 65525 )
395#endif
396 { /* FAT16 */
397#ifdef HAVE_FAT16SUPPORT
398 fat_bpb->is_fat16 = true;
399 if (fat_bpb->dataclusters < 4085)
400 { /* FAT12 */
401 fat_release_sector_buffer();
402 DEBUGF("This is FAT12. Go away!\n");
403 return -2;
404 }
405#else /* #ifdef HAVE_FAT16SUPPORT */
406 fat_release_sector_buffer();
407 DEBUGF("This is not FAT32. Go away!\n");
408 return -2;
409#endif /* #ifndef HAVE_FAT16SUPPORT */
410 } 475 }
411 476
412#ifdef HAVE_FAT16SUPPORT 477 if (ent->name[8] != ' ')
413 if (fat_bpb->is_fat16) 478 {
414 { /* FAT16 specific part of BPB */ 479 lowercase = ent->ntres & FAT_NTRES_LC_EXT;
415 int dirclusters; 480 fatent->shortname[j++] = '.';
416 fat_bpb->rootdirsector = fat_bpb->bpb_rsvdseccnt 481
417 + fat_bpb->bpb_numfats * fat_bpb->bpb_fatsz16; 482 for (int i = 8; i < 11 && (c = ent->name[i]) != ' '; i++)
418 dirclusters = ((rootdirsectors + fat_bpb->bpb_secperclus - 1) 483 fatent->shortname[j++] = lowercase ? tolower(c) : c;
419 / fat_bpb->bpb_secperclus); /* rounded up, to full clusters */
420 /* I assign negative pseudo cluster numbers for the root directory,
421 their range is counted upward until -1. */
422 fat_bpb->bpb_rootclus = 0 - dirclusters; /* backwards, before the data*/
423 fat_bpb->rootdiroffset = dirclusters * fat_bpb->bpb_secperclus
424 - rootdirsectors;
425 }
426 else
427#endif /* #ifdef HAVE_FAT16SUPPORT */
428 { /* FAT32 specific part of BPB */
429 fat_bpb->bpb_rootclus = BYTES2INT32(buf,BPB_ROOTCLUS);
430 fat_bpb->bpb_fsinfo = secmult * BYTES2INT16(buf,BPB_FSINFO);
431 fat_bpb->rootdirsector = cluster2sec(IF_MV(fat_bpb,)
432 fat_bpb->bpb_rootclus);
433 } 484 }
434 485
435 rc = bpb_is_sane(IF_MV(fat_bpb)); 486 fatent->shortname[j] = 0;
436 if (rc < 0) 487}
488
489static unsigned char char2dos(unsigned char c, int *np)
490{
491 /* FIXME: needs conversion to OEM charset FIRST but there is currently
492 no unicode function for that! */
493
494 /* smallest tables with one-step lookup that directly map the lists;
495 here we're only concerned with what gets through the longname
496 filter (check_longname will have been called earlier so common
497 illegal chars are neither in these tables nor checked for) */
498 static const unsigned char remove_chars_tbl[3] =
499 { 0, '.', ' ' };
500
501 static const unsigned char replace_chars_tbl[11] =
502 { ',', 0, 0, '[', ';', ']', '=', 0, 0, 0, '+' };
503
504 if (remove_chars_tbl[c % 3] == c)
437 { 505 {
438 fat_release_sector_buffer(); 506 /* Illegal char, remove */
439 DEBUGF( "fat_mount() - BPB is not sane\n"); 507 c = 0;
440 return rc * 10 - 3; 508 *np = 0;
441 } 509 }
442 510 else if (c >= 0x80 || replace_chars_tbl[c % 11] == c)
443#ifdef HAVE_FAT16SUPPORT
444 if (fat_bpb->is_fat16)
445 { 511 {
446 fat_bpb->fsinfo.freecount = 0xffffffff; /* force recalc below */ 512 /* Illegal char, replace (note: NTFS behavior for extended chars) */
447 fat_bpb->fsinfo.nextfree = 0xffffffff; 513 c = '_';
514 *np = 0;
448 } 515 }
449 else 516 else
450#endif /* #ifdef HAVE_FAT16SUPPORT */
451 { 517 {
452 /* Read the fsinfo sector */ 518 c = toupper(c);
453 rc = storage_read_sectors(IF_MD(drive,)
454 startsector + fat_bpb->bpb_fsinfo, 1, buf);
455 if (rc < 0)
456 {
457 fat_release_sector_buffer();
458 DEBUGF( "fat_mount() - Couldn't read FSInfo (error code %d)\n", rc);
459 return rc * 10 - 4;
460 }
461 fat_bpb->fsinfo.freecount = BYTES2INT32(buf, FSINFO_FREECOUNT);
462 fat_bpb->fsinfo.nextfree = BYTES2INT32(buf, FSINFO_NEXTFREE);
463 } 519 }
464 fat_release_sector_buffer();
465 return 0;
466}
467 520
468void* fat_get_sector_buffer() 521 return c;
469{
470 mutex_lock(&tempbuf_mutex);
471 if (tempbuf_locked)
472 panicf("FAT: Tried to lock temporary sector buffer twice!");
473 tempbuf_locked = true;
474 return fat_tempbuf;
475} 522}
476 523
477void fat_release_sector_buffer() 524/* convert long name into dos name, possibly recommending randomization */
525static void create_dos_name(unsigned char *basisname,
526 const unsigned char *name, int *np)
478{ 527{
479 tempbuf_locked = false; 528 int i;
480 mutex_unlock(&tempbuf_mutex); 529
530 /* FIXME: needs conversion to OEM charset FIRST but there is currently
531 no unicode function for that! */
532
533 /* as per FAT spec, set "lossy conversion" flag if any destructive
534 alterations to the name occur other than case changes */
535 *np = -1;
536
537 /* find extension part */
538 unsigned char *ext = strrchr(name, '.');
539 if (ext && (ext == name || strchr(ext, ' ')))
540 ext = NULL; /* handle .dotnames / extensions cannot have spaces */
541
542 /* name part */
543 for (i = 0; *name && (!ext || name < ext) && (i < 8); name++)
544 {
545 unsigned char c = char2dos(*name, np);
546 if (c)
547 basisname[i++] = c;
548 }
549
550 /* pad both name and extension */
551 while (i < 11)
552 basisname[i++] = ' ';
553
554 if (basisname[0] == 0xe5) /* special kanji character */
555 basisname[0] = 0x05;
556
557 /* extension part */
558 if (!ext++)
559 return; /* no extension */
560
561 for (i = 8; *ext && i < 11; ext++)
562 {
563 unsigned char c = char2dos(*ext, np);
564 if (c)
565 basisname[i++] = c;
566 }
567
568 if (*ext)
569 *np = 0; /* extension too long */
481} 570}
482 571
483#ifdef MAX_LOG_SECTOR_SIZE 572static void randomize_dos_name(unsigned char *dosname,
484int fat_get_bytes_per_sector(IF_MV_NONVOID(int volume)) 573 const unsigned char *basisname,
574 int *np)
485{ 575{
486#ifdef HAVE_MULTIVOLUME 576 int n = *np;
487 if(!fat_bpbs[volume].mounted) 577
488 return 0; 578 memcpy(dosname, basisname, 11);
489 return fat_bpbs[volume].bpb_bytspersec; 579
490#else 580 if (n < 0)
491 return fat_bpbs[0].bpb_bytspersec; 581 {
492#endif 582 /* first one just copies */
583 *np = 0;
584 return;
585 }
586
587 /* the "~n" string can range from "~1" to "~999999"
588 of course a directory can have at most 65536 entries which means
589 the numbers will never be required to get that big in order to map
590 to a unique name */
591 if (++n > 999999)
592 n = 1;
593
594 unsigned char numtail[8]; /* holds "~n" */
595 unsigned int numtaillen = snprintf(numtail, 8, "~%d", n);
596
597 unsigned int basislen = 0;
598 while (basislen < 8 && basisname[basislen] != ' ')
599 basislen++;
600
601 memcpy(dosname + MIN(8 - numtaillen, basislen), numtail, numtaillen);
602
603 *np = n;
493} 604}
494#endif
495 605
496int fat_mount(IF_MV(int volume,) IF_MD(int drive,) long startsector) 606/* check long filename for validity */
607static int check_longname(const unsigned char *name)
497{ 608{
498#ifndef HAVE_MULTIVOLUME 609 /* smallest table with one-step lookup that directly maps the list */
499 const int volume = 0; 610 static const unsigned char invalid_chars_tbl[19] =
500#endif 611 {
501 struct bpb* fat_bpb = &fat_bpbs[volume]; 612 0, ':', 0, '<', '*', '>', '?', 0, 0,
502 int rc; 613 '/', '|', 0, 0, 0x7f, 0, '"', '\\', 0, 0
614 };
503 615
504 rc = fat_mount_internal(IF_MV(volume,) IF_MD(drive,) startsector); 616 if (!name)
617 return -1;
505 618
506 if(rc!=0) return rc; 619 unsigned int c = *name;
507 620
508 /* calculate freecount if unset */ 621 do
509 if ( fat_bpb->fsinfo.freecount == 0xffffffff )
510 { 622 {
511 fat_recalc_free(IF_MV(volume)); 623 if (c < 0x20 || invalid_chars_tbl[c % 19] == c)
624 return -2;
512 } 625 }
626 while ((c = *++name));
513 627
514 LDEBUGF("Freecount: %ld\n",(unsigned long)fat_bpb->fsinfo.freecount); 628 /* check trailing space(s) and periods */
515 LDEBUGF("Nextfree: 0x%lx\n",(unsigned long)fat_bpb->fsinfo.nextfree); 629 c = *--name;
516 LDEBUGF("Cluster count: 0x%lx\n",(unsigned long)fat_bpb->dataclusters); 630 if (c == ' ' || c == '.')
517 LDEBUGF("Sectors per cluster: %d\n",fat_bpb->bpb_secperclus); 631 return -3;
518 LDEBUGF("FAT sectors: 0x%lx\n",(unsigned long)fat_bpb->fatsize);
519
520#ifdef HAVE_MULTIVOLUME
521 fat_bpb->mounted = true;
522#endif
523 632
524 return 0; 633 return 0;
525} 634}
526 635
527int fat_unmount(int volume, bool flush) 636/* Get first longname entry name offset */
637static inline unsigned int longent_char_first(void)
528{ 638{
529 int rc; 639 return 1;
530#ifdef HAVE_MULTIVOLUME 640}
531 struct bpb* fat_bpb = &fat_bpbs[volume];
532#else
533 (void)volume;
534#endif
535 641
536 if(flush) 642/* Get the next longname entry offset or 0 if the end is reached */
643static inline unsigned int longent_char_next(unsigned int i)
644{
645 switch (i += 2)
537 { 646 {
538 rc = flush_fat(IF_MV(fat_bpb)); /* the clean way, while still alive */ 647 case 26: i -= 1; /* return 28 */
648 case 11: i += 3; /* return 14 */
539 } 649 }
540 else 650
541 { /* volume is not accessible any more, e.g. MMC removed */ 651 return i < 32 ? i : 0;
542 int i;
543 mutex_lock(&cache_mutex);
544 for(i = 0;i < FAT_CACHE_SIZE;i++)
545 {
546 struct fat_cache_entry *fce = &fat_cache[i];
547 if(fce->inuse
548#ifdef HAVE_MULTIVOLUME
549 && fce->fat_vol == fat_bpb
550#endif
551 )
552 {
553 fce->inuse = false; /* discard all from that volume */
554 fce->dirty = false;
555 }
556 }
557 mutex_unlock(&cache_mutex);
558 rc = 0;
559 }
560#ifdef HAVE_MULTIVOLUME
561 fat_bpb->mounted = false;
562#endif
563 return rc;
564} 652}
565 653
566void fat_recalc_free(IF_MV_NONVOID(int volume)) 654/* initialize the parse state; call before parsing first long entry */
655static void NO_INLINE fatlong_parse_start(struct fatlong_parse_state *lnparse)
567{ 656{
568#ifndef HAVE_MULTIVOLUME 657 /* no inline so gcc can't figure out what isn't initialized here;
569 const int volume = 0; 658 ord_max is king as to the validity of all other fields */
570#endif 659 lnparse->ord_max = -1; /* one resync per parse operation */
571 struct bpb* fat_bpb = &fat_bpbs[volume]; 660}
572 long free = 0; 661
573 unsigned long i; 662/* convert the FAT long name entry to a contiguous segment */
574#ifdef HAVE_FAT16SUPPORT 663static bool fatlong_parse_entry(struct fatlong_parse_state *lnparse,
575 if (fat_bpb->is_fat16) 664 const union raw_dirent *ent,
665 struct fat_direntry *fatent)
666{
667 int ord = ent->ldir_ord;
668
669 if (ord & FATLONG_ORD_F_LAST)
576 { 670 {
577 for (i = 0; i<fat_bpb->fatsize; i++) { 671 /* this entry is the first long entry (first in order but
578 unsigned int j; 672 containing last part) */
579 uint16_t* fat = cache_fat_sector(IF_MV(fat_bpb,) i, false); 673 ord &= ~FATLONG_ORD_F_LAST;
580 for (j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++) { 674
581 unsigned int c = i * CLUSTERS_PER_FAT16_SECTOR + j; 675 if (ord == 0 || ord > FATLONG_MAX_ORDER)
582 if ( c > fat_bpb->dataclusters+1 ) /* nr 0 is unused */ 676 {
583 break; 677 lnparse->ord_max = 0;
584 678 return true;
585 if (letoh16(fat[j]) == 0x0000) {
586 free++;
587 if ( fat_bpb->fsinfo.nextfree == 0xffffffff )
588 fat_bpb->fsinfo.nextfree = c;
589 }
590 }
591 } 679 }
680
681 lnparse->ord_max = ord;
682 lnparse->ord = ord;
683 lnparse->chksum = ent->ldir_chksum;
592 } 684 }
593 else 685 else
594#endif /* #ifdef HAVE_FAT16SUPPORT */ 686 {
595 { 687 /* valid ordinals yet? */
596 for (i = 0; i<fat_bpb->fatsize; i++) { 688 if (lnparse->ord_max <= 0)
597 unsigned int j; 689 {
598 uint32_t* fat = cache_fat_sector(IF_MV(fat_bpb,) i, false); 690 if (lnparse->ord_max == 0)
599 for (j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++) { 691 return true;
600 unsigned long c = i * CLUSTERS_PER_FAT_SECTOR + j; 692
601 if ( c > fat_bpb->dataclusters+1 ) /* nr 0 is unused */ 693 lnparse->ord_max = 0;
602 break; 694 return false; /* try resync */
603 695 }
604 if (!(letoh32(fat[j]) & 0x0fffffff)) { 696
605 free++; 697 /* check ordinal continuity and that the checksum matches the
606 if ( fat_bpb->fsinfo.nextfree == 0xffffffff ) 698 one stored in the last entry */
607 fat_bpb->fsinfo.nextfree = c; 699 if (ord == 0 || ord != lnparse->ord - 1 ||
608 } 700 lnparse->chksum != ent->ldir_chksum)
609 } 701 {
702 lnparse->ord_max = 0;
703 return true;
610 } 704 }
611 } 705 }
612 fat_bpb->fsinfo.freecount = free;
613 update_fsinfo(IF_MV(fat_bpb));
614}
615 706
616static int bpb_is_sane(IF_MV_NONVOID(struct bpb* fat_bpb)) 707 /* so far so good; save entry information */
617{ 708 lnparse->ord = ord;
618#ifndef HAVE_MULTIVOLUME 709
619 struct bpb* fat_bpb = &fat_bpbs[0]; 710 uint16_t *ucsp = fatent->ucssegs[ord - 1 + 5];
620#endif 711 unsigned int i = longent_char_first();
621 if(fat_bpb->bpb_bytspersec % SECTOR_SIZE) 712
713 while ((*ucsp++ = BYTES2INT16(ent->data, i)))
622 { 714 {
623 DEBUGF( "bpb_is_sane() - Error: sector size is not sane (%d)\n", 715 if (!(i = longent_char_next(i)))
624 fat_bpb->bpb_bytspersec); 716 return true;
625 return -1;
626 }
627 if((long)fat_bpb->bpb_secperclus * SECTOR_SIZE > 128L*1024L)
628 {
629 /* We don't multiply by bpb_bytspersec here, because
630 * back in fat_mount_internal() bpb_secperclus has been
631 * "normalised" to 512 byte clusters, by multiplying with
632 * secmult. */
633 DEBUGF( "bpb_is_sane() - Error: cluster size is larger than 128K "
634 "(%d * %d = %d)\n",
635 fat_bpb->bpb_bytspersec,
636 fat_bpb->bpb_secperclus / (fat_bpb->bpb_bytspersec / SECTOR_SIZE),
637 fat_bpb->bpb_bytspersec * fat_bpb->bpb_secperclus /
638 (fat_bpb->bpb_bytspersec / SECTOR_SIZE));
639 return -2;
640 } 717 }
641 if(fat_bpb->bpb_numfats != 2) 718
719 /* segment may end early only in last entry */
720 if (ord == lnparse->ord_max)
642 { 721 {
643 DEBUGF( "bpb_is_sane() - Warning: NumFATS is not 2 (%d)\n", 722 /* the only valid padding, if any, is 0xffff */
644 fat_bpb->bpb_numfats); 723 do
724 {
725 if (!(i = longent_char_next(i)))
726 return true;
727 }
728 while (BYTES2INT16(ent->data, i) == 0xffff);
645 } 729 }
646 if(fat_bpb->bpb_media != 0xf0 && fat_bpb->bpb_media < 0xf8) 730
731 /* long filename is corrupt */
732 lnparse->ord_max = 0;
733 return true;
734}
735
736/* finish parsing of the longname entries and do the conversion to
737 UTF-8 if we have all the segments */
738static bool fatlong_parse_finish(struct fatlong_parse_state *lnparse,
739 const union raw_dirent *ent,
740 struct fat_direntry *fatent)
741{
742 parse_short_direntry(ent, fatent);
743
744 /* ord_max must not have been set to <= 0 because of any earlier problems
745 and the ordinal immediately before the shortname entry must be 1 */
746 if (lnparse->ord_max <= 0 || lnparse->ord != 1)
747 return false;
748
749 /* check the longname checksums against the shortname checksum */
750 if (lnparse->chksum != shortname_checksum(ent->name))
751 return false;
752
753 /* longname is good so far so convert all the segments to UTF-8 */
754 unsigned char * const name = fatent->name;
755 unsigned char *p = name;
756
757 /* ensure the last segment is NULL-terminated if it is filled */
758 fatent->ucssegs[lnparse->ord_max + 5][0] = 0x0000;
759
760 for (uint16_t *ucsp = fatent->ucssegs[5], ucc = *ucsp;
761 ucc; ucc = *++ucsp)
647 { 762 {
648 DEBUGF( "bpb_is_sane() - Warning: Non-standard " 763 /* end should be hit before ever seeing padding */
649 "media type (0x%02x)\n", 764 if (ucc == 0xffff)
650 fat_bpb->bpb_media); 765 return false;
766
767 if ((p = utf8encode(ucc, p)) - name > FAT_DIRENTRY_NAME_MAX)
768 return false;
651 } 769 }
652 if(fat_bpb->last_word != 0xaa55) 770
771 /* longname ok */
772 *p = '\0';
773 return true;
774}
775
776static unsigned long cluster2sec(struct bpb *fat_bpb, long cluster)
777{
778 long zerocluster = 2;
779
780 /* negative clusters (FAT16 root dir) don't get the 2 offset */
781#ifdef HAVE_FAT16SUPPORT
782 if (fat_bpb->is_fat16 && cluster < 0)
653 { 783 {
654 DEBUGF( "bpb_is_sane() - Error: Last word is not " 784 zerocluster = 0;
655 "0xaa55 (0x%04x)\n", fat_bpb->last_word);
656 return -3;
657 } 785 }
658 786 else
659 if (fat_bpb->fsinfo.freecount > 787#endif /* HAVE_FAT16SUPPORT */
660 (fat_bpb->totalsectors - fat_bpb->firstdatasector)/ 788 if ((unsigned long)cluster > fat_bpb->dataclusters + 1)
661 fat_bpb->bpb_secperclus)
662 { 789 {
663 DEBUGF( "bpb_is_sane() - Error: FSInfo.Freecount > disk size " 790 DEBUGF( "%s() - Bad cluster number (%ld)\n", __func__, cluster);
664 "(0x%04lx)\n", (unsigned long)fat_bpb->fsinfo.freecount); 791 return 0;
665 return -4;
666 } 792 }
667 793
668 return 0; 794 return (unsigned long)(cluster - zerocluster)*fat_bpb->bpb_secperclus
795 + fat_bpb->firstdatasector;
669} 796}
670 797
671static void flush_fat_sector(struct fat_cache_entry *fce, 798#ifdef HAVE_FAT16SUPPORT
672 unsigned char *sectorbuf) 799static long get_next_cluster16(struct bpb *fat_bpb, long startcluster)
673{ 800{
674 int rc; 801 /* if FAT16 root dir, dont use the FAT */
675 long secnum; 802 if (startcluster < 0)
803 return startcluster + 1;
676 804
677 /* With multivolume, use only the FAT info from the cached sector! */ 805 unsigned long entry = startcluster;
678#ifdef HAVE_MULTIVOLUME 806 unsigned long sector = entry / CLUSTERS_PER_FAT16_SECTOR;
679 secnum = fce->secnum + fce->fat_vol->startsector; 807 unsigned long offset = entry % CLUSTERS_PER_FAT16_SECTOR;
680#else 808
681 secnum = fce->secnum + fat_bpbs[0].startsector; 809 dc_lock_cache();
682#endif
683 810
684 /* Write to the first FAT */ 811 uint16_t *sec = cache_sector(fat_bpb, sector + fat_bpb->fatrgnstart);
685 rc = storage_write_sectors(IF_MD(fce->fat_vol->drive,) 812 if (!sec)
686 secnum, 1,
687 sectorbuf);
688 if(rc < 0)
689 { 813 {
690 panicf("flush_fat_sector() - Could not write sector %ld" 814 dc_unlock_cache();
691 " (error %d)\n", 815 DEBUGF("%s: Could not cache sector %d\n", __func__, sector);
692 secnum, rc); 816 return -1;
693 } 817 }
694#ifdef HAVE_MULTIVOLUME 818
695 if(fce->fat_vol->bpb_numfats > 1) 819 long next = letoh16(sec[offset]);
696#else 820
697 if(fat_bpbs[0].bpb_numfats > 1) 821 /* is this last cluster in chain? */
698#endif 822 if (next >= FAT16_EOF_MARK)
823 next = 0;
824
825 dc_unlock_cache();
826 return next;
827}
828
829static long find_free_cluster16(struct bpb *fat_bpb, long startcluster)
830{
831 unsigned long entry = startcluster;
832 unsigned long sector = entry / CLUSTERS_PER_FAT16_SECTOR;
833 unsigned long offset = entry % CLUSTERS_PER_FAT16_SECTOR;
834
835 for (unsigned long i = 0; i < fat_bpb->fatsize; i++)
699 { 836 {
700 /* Write to the second FAT */ 837 unsigned long nr = (i + sector) % fat_bpb->fatsize;
701#ifdef HAVE_MULTIVOLUME 838 uint16_t *sec = cache_sector(fat_bpb, nr + fat_bpb->fatrgnstart);
702 secnum += fce->fat_vol->fatsize; 839 if (!sec)
703#else 840 break;
704 secnum += fat_bpbs[0].fatsize; 841
705#endif 842 for (unsigned long j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++)
706 rc = storage_write_sectors(IF_MD(fce->fat_vol->drive,)
707 secnum, 1, sectorbuf);
708 if(rc < 0)
709 { 843 {
710 panicf("flush_fat_sector() - Could not write sector %ld" 844 unsigned long k = (j + offset) % CLUSTERS_PER_FAT16_SECTOR;
711 " (error %d)\n", 845
712 secnum, rc); 846 if (letoh16(sec[k]) == 0x0000)
847 {
848 unsigned long c = nr * CLUSTERS_PER_FAT16_SECTOR + k;
849 /* Ignore the reserved clusters 0 & 1, and also
850 cluster numbers out of bounds */
851 if (c < 2 || c > fat_bpb->dataclusters + 1)
852 continue;
853
854 DEBUGF("%s(%lx) == %x\n", __func__, startcluster, c);
855
856 fat_bpb->fsinfo.nextfree = c;
857 return c;
858 }
713 } 859 }
860
861 offset = 0;
714 } 862 }
715 fce->dirty = false; 863
864 DEBUGF("%s(%lx) == 0\n", __func__, startcluster);
865 return 0; /* 0 is an illegal cluster number */
716} 866}
717 867
718/* Note: The returned pointer is only safely valid until the next 868static int update_fat_entry16(struct bpb *fat_bpb, unsigned long entry,
719 task switch! (Any subsequent ata read/write may yield.) */ 869 unsigned long val)
720static void *cache_fat_sector(IF_MV(struct bpb* fat_bpb,)
721 long fatsector, bool dirty)
722{ 870{
723#ifndef HAVE_MULTIVOLUME 871 unsigned long sector = entry / CLUSTERS_PER_FAT16_SECTOR;
724 struct bpb* fat_bpb = &fat_bpbs[0]; 872 unsigned long offset = entry % CLUSTERS_PER_FAT16_SECTOR;
725#endif
726 long secnum = fatsector + fat_bpb->bpb_rsvdseccnt;
727 int cache_index = secnum & FAT_CACHE_MASK;
728 struct fat_cache_entry *fce = &fat_cache[cache_index];
729 unsigned char *sectorbuf = &fat_cache_sectors[cache_index][0];
730 int rc;
731 873
732 mutex_lock(&cache_mutex); /* make changes atomic */ 874 val &= 0xFFFF;
733 875
734 /* Delete the cache entry if it isn't the sector we want */ 876 DEBUGF("%s(entry:%lx,val:%lx)\n", __func__, entry, val);
735 if(fce->inuse && (fce->secnum != secnum 877
736#ifdef HAVE_MULTIVOLUME 878 if (entry == val)
737 || fce->fat_vol != fat_bpb 879 panicf("Creating FAT16 loop: %lx,%lx\n", entry, val);
738#endif 880
739 )) 881 if (entry < 2)
882 panicf("Updating reserved FAT16 entry %lu\n", entry);
883
884 dc_lock_cache();
885
886 int16_t *sec = cache_sector(fat_bpb, sector + fat_bpb->fatrgnstart);
887 if (!sec)
740 { 888 {
741 /* Write back if it is dirty */ 889 dc_unlock_cache();
742 if(fce->dirty) 890 DEBUGF("Could not cache sector %u\n", sector);
743 { 891 return -1;
744 flush_fat_sector(fce, sectorbuf);
745 }
746 fce->inuse = false;
747 } 892 }
748 893
749 /* Load the sector if it is not cached */ 894 uint16_t curval = letoh16(sec[offset]);
750 if(!fce->inuse) 895
896 if (val)
751 { 897 {
752 rc = storage_read_sectors(IF_MD(fat_bpb->drive,) 898 /* being allocated */
753 secnum + fat_bpb->startsector,1, 899 if (curval == 0x0000 && fat_bpb->fsinfo.freecount > 0)
754 sectorbuf); 900 fat_bpb->fsinfo.freecount--;
755 if(rc < 0)
756 {
757 DEBUGF( "cache_fat_sector() - Could not read sector %ld"
758 " (error %d)\n", secnum, rc);
759 mutex_unlock(&cache_mutex);
760 return NULL;
761 }
762 fce->inuse = true;
763 fce->secnum = secnum;
764#ifdef HAVE_MULTIVOLUME
765 fce->fat_vol = fat_bpb;
766#endif
767 } 901 }
768 if (dirty) 902 else
769 fce->dirty = true; /* dirt remains, sticky until flushed */ 903 {
770 mutex_unlock(&cache_mutex); 904 /* being freed */
771 return sectorbuf; 905 if (curval != 0x0000)
906 fat_bpb->fsinfo.freecount++;
907 }
908
909 DEBUGF("%lu free clusters\n", (unsigned long)fat_bpb->fsinfo.freecount);
910
911 sec[offset] = htole16(val);
912 dc_dirty_buf(sec);
913
914 dc_unlock_cache();
915 return 0;
772} 916}
773 917
774static unsigned long find_free_cluster(IF_MV(struct bpb* fat_bpb,) 918static void fat_recalc_free_internal16(struct bpb *fat_bpb)
775 unsigned long startcluster)
776{ 919{
777#ifndef HAVE_MULTIVOLUME 920 unsigned long free = 0;
778 struct bpb* fat_bpb = &fat_bpbs[0];
779#endif
780 unsigned long sector;
781 unsigned long offset;
782 unsigned long i;
783 921
784#ifdef HAVE_FAT16SUPPORT 922 for (unsigned long i = 0; i < fat_bpb->fatsize; i++)
785 if (fat_bpb->is_fat16)
786 { 923 {
787 sector = startcluster / CLUSTERS_PER_FAT16_SECTOR; 924 uint16_t *sec = cache_sector(fat_bpb, i + fat_bpb->fatrgnstart);
788 offset = startcluster % CLUSTERS_PER_FAT16_SECTOR; 925 if (!sec)
926 break;
789 927
790 for (i = 0; i<fat_bpb->fatsize; i++) { 928 for (unsigned long j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++)
791 unsigned int j; 929 {
792 unsigned int nr = (i + sector) % fat_bpb->fatsize; 930 unsigned long c = i * CLUSTERS_PER_FAT16_SECTOR + j;
793 uint16_t* fat = cache_fat_sector(IF_MV(fat_bpb,) nr, false); 931
794 if ( !fat ) 932 if (c < 2 || c > fat_bpb->dataclusters + 1) /* nr 0 is unused */
795 break; 933 continue;
796 for (j = 0; j < CLUSTERS_PER_FAT16_SECTOR; j++) { 934
797 int k = (j + offset) % CLUSTERS_PER_FAT16_SECTOR; 935 if (letoh16(sec[j]) != 0x0000)
798 if (letoh16(fat[k]) == 0x0000) { 936 continue;
799 unsigned int c = nr * CLUSTERS_PER_FAT16_SECTOR + k; 937
800 /* Ignore the reserved clusters 0 & 1, and also 938 free++;
801 cluster numbers out of bounds */ 939 if (fat_bpb->fsinfo.nextfree == 0xffffffff)
802 if ( c < 2 || c > fat_bpb->dataclusters+1 ) 940 fat_bpb->fsinfo.nextfree = c;
803 continue;
804 LDEBUGF("find_free_cluster(%lx) == %x\n",startcluster,c);
805 fat_bpb->fsinfo.nextfree = c;
806 return c;
807 }
808 }
809 offset = 0;
810 } 941 }
811 } 942 }
812 else
813#endif /* #ifdef HAVE_FAT16SUPPORT */
814 {
815 sector = startcluster / CLUSTERS_PER_FAT_SECTOR;
816 offset = startcluster % CLUSTERS_PER_FAT_SECTOR;
817 943
818 for (i = 0; i<fat_bpb->fatsize; i++) { 944 fat_bpb->fsinfo.freecount = free;
819 unsigned int j; 945}
820 unsigned long nr = (i + sector) % fat_bpb->fatsize; 946#endif /* HAVE_FAT16SUPPORT */
821 uint32_t* fat = cache_fat_sector(IF_MV(fat_bpb,) nr, false); 947
822 if ( !fat ) 948static void update_fsinfo32(struct bpb *fat_bpb)
823 break; 949{
824 for (j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++) { 950 uint8_t *fsinfo = cache_sector(fat_bpb, fat_bpb->bpb_fsinfo);
825 int k = (j + offset) % CLUSTERS_PER_FAT_SECTOR; 951 if (!fsinfo)
826 if (!(letoh32(fat[k]) & 0x0fffffff)) { 952 {
827 unsigned long c = nr * CLUSTERS_PER_FAT_SECTOR + k; 953 DEBUGF("%s() - Couldn't read FSInfo"
828 /* Ignore the reserved clusters 0 & 1, and also 954 " (err code %d)", __func__, rc);
829 cluster numbers out of bounds */ 955 return;
830 if ( c < 2 || c > fat_bpb->dataclusters+1 )
831 continue;
832 LDEBUGF("find_free_cluster(%lx) == %lx\n",startcluster,c);
833 fat_bpb->fsinfo.nextfree = c;
834 return c;
835 }
836 }
837 offset = 0;
838 }
839 } 956 }
840 957
841 LDEBUGF("find_free_cluster(%lx) == 0\n",startcluster); 958 INT322BYTES(fsinfo, FSINFO_FREECOUNT, fat_bpb->fsinfo.freecount);
842 return 0; /* 0 is an illegal cluster number */ 959 INT322BYTES(fsinfo, FSINFO_NEXTFREE, fat_bpb->fsinfo.nextfree);
960 dc_dirty_buf(fsinfo);
843} 961}
844 962
845static int update_fat_entry(IF_MV(struct bpb* fat_bpb,) unsigned long entry, 963static long get_next_cluster32(struct bpb *fat_bpb, long startcluster)
846 unsigned long val)
847{ 964{
848#ifndef HAVE_MULTIVOLUME 965 unsigned long entry = startcluster;
849 struct bpb* fat_bpb = &fat_bpbs[0]; 966 unsigned long sector = entry / CLUSTERS_PER_FAT_SECTOR;
850#endif 967 unsigned long offset = entry % CLUSTERS_PER_FAT_SECTOR;
851#ifdef HAVE_FAT16SUPPORT 968
852 if (fat_bpb->is_fat16) 969 dc_lock_cache();
970
971 uint32_t *sec = cache_sector(fat_bpb, sector + fat_bpb->fatrgnstart);
972 if (!sec)
853 { 973 {
854 int sector = entry / CLUSTERS_PER_FAT16_SECTOR; 974 dc_unlock_cache();
855 int offset = entry % CLUSTERS_PER_FAT16_SECTOR; 975 DEBUGF("%s: Could not cache sector %d\n", __func__, sector);
856 unsigned short* sec; 976 return -1;
977 }
857 978
858 val &= 0xFFFF; 979 long next = letoh32(sec[offset]) & 0x0fffffff;
859 980
860 LDEBUGF("update_fat_entry(%lx,%lx)\n",entry,val); 981 /* is this last cluster in chain? */
982 if (next >= FAT_EOF_MARK)
983 next = 0;
861 984
862 if (entry==val) 985 dc_unlock_cache();
863 panicf("Creating FAT loop: %lx,%lx\n",entry,val); 986 return next;
987}
864 988
865 if ( entry < 2 ) 989static long find_free_cluster32(struct bpb *fat_bpb, long startcluster)
866 panicf("Updating reserved FAT entry %ld.\n",entry); 990{
991 unsigned long entry = startcluster;
992 unsigned long sector = entry / CLUSTERS_PER_FAT_SECTOR;
993 unsigned long offset = entry % CLUSTERS_PER_FAT_SECTOR;
867 994
868 sec = cache_fat_sector(IF_MV(fat_bpb,) sector, true); 995 for (unsigned long i = 0; i < fat_bpb->fatsize; i++)
996 {
997 unsigned long nr = (i + sector) % fat_bpb->fatsize;
998 uint32_t *sec = cache_sector(fat_bpb, nr + fat_bpb->fatrgnstart);
869 if (!sec) 999 if (!sec)
1000 break;
1001
1002 for (unsigned long j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++)
870 { 1003 {
871 DEBUGF( "update_fat_entry() - Could not cache sector %d\n", sector); 1004 unsigned long k = (j + offset) % CLUSTERS_PER_FAT_SECTOR;
872 return -1;
873 }
874 1005
875 if ( val ) { 1006 if (!(letoh32(sec[k]) & 0x0fffffff))
876 if (letoh16(sec[offset]) == 0x0000 && fat_bpb->fsinfo.freecount > 0) 1007 {
877 fat_bpb->fsinfo.freecount--; 1008 unsigned long c = nr * CLUSTERS_PER_FAT_SECTOR + k;
878 } 1009 /* Ignore the reserved clusters 0 & 1, and also
879 else { 1010 cluster numbers out of bounds */
880 if (letoh16(sec[offset])) 1011 if (c < 2 || c > fat_bpb->dataclusters + 1)
881 fat_bpb->fsinfo.freecount++; 1012 continue;
1013
1014 DEBUGF("%s(%lx) == %lx\n", __func__, startcluster, c);
1015
1016 fat_bpb->fsinfo.nextfree = c;
1017 return c;
1018 }
882 } 1019 }
883 1020
884 LDEBUGF("update_fat_entry: %lu free clusters\n", 1021 offset = 0;
885 (unsigned long)fat_bpb->fsinfo.freecount); 1022 }
1023
1024 DEBUGF("%s(%lx) == 0\n", __func__, startcluster);
1025 return 0; /* 0 is an illegal cluster number */
1026}
1027
1028static int update_fat_entry32(struct bpb *fat_bpb, unsigned long entry,
1029 unsigned long val)
1030{
1031 unsigned long sector = entry / CLUSTERS_PER_FAT_SECTOR;
1032 unsigned long offset = entry % CLUSTERS_PER_FAT_SECTOR;
1033
1034 DEBUGF("%s(entry:%lx,val:%lx)\n", __func__, entry, val);
1035
1036 if (entry == val)
1037 panicf("Creating FAT32 loop: %lx,%lx\n", entry, val);
1038
1039 if (entry < 2)
1040 panicf("Updating reserved FAT32 entry %lu\n", entry);
1041
1042 dc_lock_cache();
1043
1044 uint32_t *sec = cache_sector(fat_bpb, sector + fat_bpb->fatrgnstart);
1045 if (!sec)
1046 {
1047 dc_unlock_cache();
1048 DEBUGF("Could not cache sector %u\n", sector);
1049 return -1;
1050 }
886 1051
887 sec[offset] = htole16(val); 1052 uint32_t curval = letoh32(sec[offset]);
1053
1054 if (val)
1055 {
1056 /* being allocated */
1057 if (!(curval & 0x0fffffff) && fat_bpb->fsinfo.freecount > 0)
1058 fat_bpb->fsinfo.freecount--;
888 } 1059 }
889 else 1060 else
890#endif /* #ifdef HAVE_FAT16SUPPORT */
891 { 1061 {
892 long sector = entry / CLUSTERS_PER_FAT_SECTOR; 1062 /* being freed */
893 int offset = entry % CLUSTERS_PER_FAT_SECTOR; 1063 if (curval & 0x0fffffff)
894 uint32_t* sec; 1064 fat_bpb->fsinfo.freecount++;
1065 }
1066
1067 DEBUGF("%lu free clusters\n", (unsigned long)fat_bpb->fsinfo.freecount);
895 1068
896 LDEBUGF("update_fat_entry(%lx,%lx)\n",entry,val); 1069 /* don't change top 4 bits */
1070 sec[offset] = htole32((curval & 0xf0000000) | (val & 0x0fffffff));
1071 dc_dirty_buf(sec);
897 1072
898 if (entry==val) 1073 dc_unlock_cache();
899 panicf("Creating FAT loop: %lx,%lx\n",entry,val); 1074 return 0;
1075}
900 1076
901 if ( entry < 2 ) 1077static void fat_recalc_free_internal32(struct bpb *fat_bpb)
902 panicf("Updating reserved FAT entry %ld.\n",entry); 1078{
1079 unsigned long free = 0;
903 1080
904 sec = cache_fat_sector(IF_MV(fat_bpb,) sector, true); 1081 for (unsigned long i = 0; i < fat_bpb->fatsize; i++)
1082 {
1083 uint32_t *sec = cache_sector(fat_bpb, i + fat_bpb->fatrgnstart);
905 if (!sec) 1084 if (!sec)
1085 break;
1086
1087 for (unsigned long j = 0; j < CLUSTERS_PER_FAT_SECTOR; j++)
906 { 1088 {
907 DEBUGF("update_fat_entry() - Could not cache sector %ld\n", sector); 1089 unsigned long c = i * CLUSTERS_PER_FAT_SECTOR + j;
908 return -1;
909 }
910 1090
911 if ( val ) { 1091 if (c < 2 || c > fat_bpb->dataclusters + 1) /* nr 0 is unused */
912 if (!(letoh32(sec[offset]) & 0x0fffffff) && 1092 continue;
913 fat_bpb->fsinfo.freecount > 0)
914 fat_bpb->fsinfo.freecount--;
915 }
916 else {
917 if (letoh32(sec[offset]) & 0x0fffffff)
918 fat_bpb->fsinfo.freecount++;
919 }
920 1093
921 LDEBUGF("update_fat_entry: %ld free clusters\n", 1094 if (letoh32(sec[j]) & 0x0fffffff)
922 (unsigned long)fat_bpb->fsinfo.freecount); 1095 continue;
923 1096
924 /* don't change top 4 bits */ 1097 free++;
925 sec[offset] &= htole32(0xf0000000); 1098 if (fat_bpb->fsinfo.nextfree == 0xffffffff)
926 sec[offset] |= htole32(val & 0x0fffffff); 1099 fat_bpb->fsinfo.nextfree = c;
1100 }
927 } 1101 }
928 1102
929 return 0; 1103 fat_bpb->fsinfo.freecount = free;
1104 update_fsinfo32(fat_bpb);
930} 1105}
931 1106
932static long read_fat_entry(IF_MV(struct bpb* fat_bpb,) unsigned long entry) 1107static int fat_mount_internal(struct bpb *fat_bpb)
933{ 1108{
1109 int rc;
1110 /* safe to grab buffer: bpb is irrelevant and no sector will be cached
1111 for this volume since it isn't mounted */
1112 uint8_t * const buf = dc_get_buffer();
1113 if (!buf)
1114 FAT_ERROR(-1);
1115
1116 /* Read the sector */
1117 rc = storage_read_sectors(IF_MD(fat_bpb->drive,) fat_bpb->startsector,
1118 1, buf);
1119 if(rc)
1120 {
1121 DEBUGF("%s() - Couldn't read BPB"
1122 " (err %d)\n", __func__, rc);
1123 FAT_ERROR(rc * 10 - 2);
1124 }
1125
1126 fat_bpb->bpb_bytspersec = BYTES2INT16(buf, BPB_BYTSPERSEC);
1127 unsigned long secmult = fat_bpb->bpb_bytspersec / SECTOR_SIZE;
1128 /* Sanity check is performed later */
1129
1130 fat_bpb->bpb_secperclus = secmult * buf[BPB_SECPERCLUS];
1131 fat_bpb->bpb_rsvdseccnt = secmult * BYTES2INT16(buf, BPB_RSVDSECCNT);
1132 fat_bpb->bpb_numfats = buf[BPB_NUMFATS];
1133 fat_bpb->bpb_media = buf[BPB_MEDIA];
1134 fat_bpb->bpb_fatsz16 = secmult * BYTES2INT16(buf, BPB_FATSZ16);
1135 fat_bpb->bpb_fatsz32 = secmult * BYTES2INT32(buf, BPB_FATSZ32);
1136 fat_bpb->bpb_totsec16 = secmult * BYTES2INT16(buf, BPB_TOTSEC16);
1137 fat_bpb->bpb_totsec32 = secmult * BYTES2INT32(buf, BPB_TOTSEC32);
1138 fat_bpb->last_word = BYTES2INT16(buf, BPB_LAST_WORD);
1139
1140 /* calculate a few commonly used values */
1141 if (fat_bpb->bpb_fatsz16 != 0)
1142 fat_bpb->fatsize = fat_bpb->bpb_fatsz16;
1143 else
1144 fat_bpb->fatsize = fat_bpb->bpb_fatsz32;
1145
1146 fat_bpb->fatrgnstart = fat_bpb->bpb_rsvdseccnt;
1147 fat_bpb->fatrgnend = fat_bpb->bpb_rsvdseccnt + fat_bpb->fatsize;
1148
1149 if (fat_bpb->bpb_totsec16 != 0)
1150 fat_bpb->totalsectors = fat_bpb->bpb_totsec16;
1151 else
1152 fat_bpb->totalsectors = fat_bpb->bpb_totsec32;
1153
1154 unsigned int rootdirsectors = 0;
1155#ifdef HAVE_FAT16SUPPORT
1156 fat_bpb->bpb_rootentcnt = BYTES2INT16(buf, BPB_ROOTENTCNT);
1157
1158 if (!fat_bpb->bpb_bytspersec)
1159 FAT_ERROR(-3);
1160
1161 rootdirsectors = secmult * ((fat_bpb->bpb_rootentcnt * DIR_ENTRY_SIZE
1162 + fat_bpb->bpb_bytspersec - 1) / fat_bpb->bpb_bytspersec);
1163#endif /* HAVE_FAT16SUPPORT */
1164
1165 fat_bpb->firstdatasector = fat_bpb->bpb_rsvdseccnt
1166 + fat_bpb->bpb_numfats * fat_bpb->fatsize
1167 + rootdirsectors;
1168
1169 /* Determine FAT type */
1170 unsigned long datasec = fat_bpb->totalsectors - fat_bpb->firstdatasector;
1171
1172 if (!fat_bpb->bpb_secperclus)
1173 FAT_ERROR(-4);
1174
1175 fat_bpb->dataclusters = datasec / fat_bpb->bpb_secperclus;
1176
1177#ifdef TEST_FAT
1178 /*
1179 we are sometimes testing with "illegally small" fat32 images,
1180 so we don't use the proper fat32 test case for test code
1181 */
1182 if (fat_bpb->bpb_fatsz16)
1183#else /* !TEST_FAT */
1184 if (fat_bpb->dataclusters < 65525)
1185#endif /* TEST_FAT */
1186 { /* FAT16 */
1187#ifdef HAVE_FAT16SUPPORT
1188 fat_bpb->is_fat16 = true;
1189 if (fat_bpb->dataclusters < 4085)
1190 { /* FAT12 */
1191 DEBUGF("This is FAT12. Go away!\n");
1192 FAT_ERROR(-5);
1193 }
1194#else /* !HAVE_FAT16SUPPORT */
1195 DEBUGF("This is not FAT32. Go away!\n");
1196 FAT_ERROR(-6);
1197#endif /* HAVE_FAT16SUPPORT */
1198 }
1199
934#ifdef HAVE_FAT16SUPPORT 1200#ifdef HAVE_FAT16SUPPORT
935#ifndef HAVE_MULTIVOLUME
936 struct bpb* fat_bpb = &fat_bpbs[0];
937#endif
938 if (fat_bpb->is_fat16) 1201 if (fat_bpb->is_fat16)
939 { 1202 {
940 int sector = entry / CLUSTERS_PER_FAT16_SECTOR; 1203 /* FAT16 specific part of BPB */
941 int offset = entry % CLUSTERS_PER_FAT16_SECTOR; 1204 fat_bpb->rootdirsector = fat_bpb->bpb_rsvdseccnt
942 unsigned short* sec; 1205 + fat_bpb->bpb_numfats * fat_bpb->bpb_fatsz16;
1206 long dirclusters = ((rootdirsectors + fat_bpb->bpb_secperclus - 1)
1207 / fat_bpb->bpb_secperclus); /* rounded up, to full clusters */
1208 /* I assign negative pseudo cluster numbers for the root directory,
1209 their range is counted upward until -1. */
1210 fat_bpb->bpb_rootclus = 0 - dirclusters; /* backwards, before the data */
1211 fat_bpb->rootdirsectornum = dirclusters * fat_bpb->bpb_secperclus
1212 - rootdirsectors;
1213 }
1214 else
1215#endif /* HAVE_FAT16SUPPORT */
1216 {
1217 /* FAT32 specific part of BPB */
1218 fat_bpb->bpb_rootclus = BYTES2INT32(buf, BPB_ROOTCLUS);
1219 fat_bpb->bpb_fsinfo = secmult * BYTES2INT16(buf, BPB_FSINFO);
1220 fat_bpb->rootdirsector = cluster2sec(fat_bpb, fat_bpb->bpb_rootclus);
1221 }
943 1222
944 sec = cache_fat_sector(IF_MV(fat_bpb,) sector, false); 1223 rc = bpb_is_sane(fat_bpb);
945 if (!sec) 1224 if (rc < 0)
946 { 1225 {
947 DEBUGF( "read_fat_entry() - Could not cache sector %d\n", sector); 1226 DEBUGF("%s: BPB is insane!\n", __func__);
948 return -1; 1227 FAT_ERROR(rc * 10 - 7);
949 } 1228 }
950 1229
951 return letoh16(sec[offset]); 1230#ifdef HAVE_FAT16SUPPORT
1231 if (fat_bpb->is_fat16)
1232 {
1233 fat_bpb->fsinfo.freecount = 0xffffffff; /* force recalc later */
1234 fat_bpb->fsinfo.nextfree = 0xffffffff;
952 } 1235 }
953 else 1236 else
954#endif /* #ifdef HAVE_FAT16SUPPORT */ 1237#endif /* HAVE_FAT16SUPPORT */
955 { 1238 {
956 long sector = entry / CLUSTERS_PER_FAT_SECTOR; 1239 /* Read the fsinfo sector */
957 int offset = entry % CLUSTERS_PER_FAT_SECTOR; 1240 rc = storage_read_sectors(IF_MD(fat_bpb->drive,)
958 uint32_t* sec; 1241 fat_bpb->startsector + fat_bpb->bpb_fsinfo, 1, buf);
959 1242
960 sec = cache_fat_sector(IF_MV(fat_bpb,) sector, false); 1243 if (rc < 0)
961 if (!sec)
962 { 1244 {
963 DEBUGF( "read_fat_entry() - Could not cache sector %ld\n", sector); 1245 DEBUGF("%s() - Couldn't read FSInfo"
964 return -1; 1246 " (error code %d)\n", __func__, rc);
1247 FAT_ERROR(rc * 10 - 8);
965 } 1248 }
966 1249
967 return letoh32(sec[offset]) & 0x0fffffff; 1250 fat_bpb->fsinfo.freecount = BYTES2INT32(buf, FSINFO_FREECOUNT);
1251 fat_bpb->fsinfo.nextfree = BYTES2INT32(buf, FSINFO_NEXTFREE);
968 } 1252 }
969}
970
971static long get_next_cluster(IF_MV(struct bpb* fat_bpb,) long cluster)
972{
973 long next_cluster;
974 long eof_mark = FAT_EOF_MARK;
975 1253
976#ifdef HAVE_FAT16SUPPORT 1254#ifdef HAVE_FAT16SUPPORT
977#ifndef HAVE_MULTIVOLUME 1255 /* Fix up calls that change per FAT type */
978 struct bpb* fat_bpb = &fat_bpbs[0];
979#endif
980 if (fat_bpb->is_fat16) 1256 if (fat_bpb->is_fat16)
981 { 1257 {
982 eof_mark &= 0xFFFF; /* only 16 bit */ 1258 BPB_FN_SET16(fat_bpb, get_next_cluster);
983 if (cluster < 0) /* FAT16 root dir */ 1259 BPB_FN_SET16(fat_bpb, find_free_cluster);
984 return cluster + 1; /* don't use the FAT */ 1260 BPB_FN_SET16(fat_bpb, update_fat_entry);
1261 BPB_FN_SET16(fat_bpb, fat_recalc_free_internal);
985 } 1262 }
986#endif
987 next_cluster = read_fat_entry(IF_MV(fat_bpb,) cluster);
988
989 /* is this last cluster in chain? */
990 if ( next_cluster >= eof_mark )
991 return 0;
992 else 1263 else
993 return next_cluster; 1264 {
1265 BPB_FN_SET32(fat_bpb, get_next_cluster);
1266 BPB_FN_SET32(fat_bpb, find_free_cluster);
1267 BPB_FN_SET32(fat_bpb, update_fat_entry);
1268 BPB_FN_SET32(fat_bpb, fat_recalc_free_internal);
1269 }
1270#endif /* HAVE_FAT16SUPPORT */
1271
1272 rc = 0;
1273fat_error:
1274 dc_release_buffer(buf);
1275 return rc;
994} 1276}
995 1277
996static int update_fsinfo(IF_MV_NONVOID(struct bpb* fat_bpb)) 1278static union raw_dirent * cache_direntry(struct bpb *fat_bpb,
1279 struct fat_filestr *filestr,
1280 unsigned int entry)
997{ 1281{
998#ifndef HAVE_MULTIVOLUME 1282 filestr->eof = false;
999 struct bpb* fat_bpb = &fat_bpbs[0];
1000#endif
1001 uint32_t* intptr;
1002 int rc;
1003 1283
1004#ifdef HAVE_FAT16SUPPORT 1284 if (entry >= MAX_DIRENTRIES)
1005 if (fat_bpb->is_fat16) 1285 {
1006 return 0; /* FAT16 has no FsInfo */ 1286 DEBUGF("%s() - Dir is too large (entry %u)\n", __func__, entry);
1007#endif /* #ifdef HAVE_FAT16SUPPORT */ 1287 return NULL;
1288 }
1008 1289
1009 unsigned char* fsinfo = fat_get_sector_buffer(); 1290 unsigned long sector = entry / DIR_ENTRIES_PER_SECTOR;
1010 /* update fsinfo */ 1291
1011 rc = storage_read_sectors(IF_MD(fat_bpb->drive,) 1292 if (fat_query_sectornum(filestr) != sector + 1)
1012 fat_bpb->startsector + fat_bpb->bpb_fsinfo, 1,fsinfo);
1013 if (rc < 0)
1014 { 1293 {
1015 fat_release_sector_buffer(); 1294 int rc = fat_seek(filestr, sector + 1);
1016 DEBUGF( "update_fsinfo() - Couldn't read FSInfo (error code %d)", rc); 1295 if (rc < 0)
1017 return rc * 10 - 1; 1296 {
1297 if (rc == FAT_SEEK_EOF)
1298 {
1299 DEBUGF("%s() - End of dir (entry %u)\n", __func__, entry);
1300 fat_seek(filestr, sector);
1301 filestr->eof = true;
1302 }
1303
1304 return NULL;
1305 }
1018 } 1306 }
1019 intptr = (uint32_t*)&(fsinfo[FSINFO_FREECOUNT]);
1020 *intptr = htole32(fat_bpb->fsinfo.freecount);
1021 1307
1022 intptr = (uint32_t*)&(fsinfo[FSINFO_NEXTFREE]); 1308 union raw_dirent *ent = cache_sector(fat_bpb, filestr->lastsector);
1023 *intptr = htole32(fat_bpb->fsinfo.nextfree);
1024 1309
1025 rc = storage_write_sectors(IF_MD(fat_bpb->drive,) 1310 if (ent)
1026 fat_bpb->startsector + fat_bpb->bpb_fsinfo,1,fsinfo); 1311 ent += entry % DIR_ENTRIES_PER_SECTOR;
1027 fat_release_sector_buffer(); 1312
1028 if (rc < 0) 1313 return ent;
1314}
1315
1316static long next_write_cluster(struct bpb *fat_bpb, long oldcluster)
1317{
1318 DEBUGF("%s(old:%lx)\n", __func__, oldcluster);
1319
1320 long cluster = 0;
1321
1322 /* cluster already allocated? */
1323 if (oldcluster)
1324 cluster = get_next_cluster(fat_bpb, oldcluster);
1325
1326 if (!cluster)
1029 { 1327 {
1030 DEBUGF( "update_fsinfo() - Couldn't write FSInfo (error code %d)", rc); 1328 /* passed end of existing entries and now need to append */
1031 return rc * 10 - 2; 1329 #ifdef HAVE_FAT16SUPPORT
1330 if (UNLIKELY(oldcluster < 0))
1331 return 0; /* negative, pseudo-cluster of the root dir */
1332 /* impossible to append something to the root */
1333 #endif /* HAVE_FAT16SUPPORT */
1334
1335 dc_lock_cache();
1336
1337 long findstart = oldcluster > 0 ?
1338 oldcluster + 1 : (long)fat_bpb->fsinfo.nextfree;
1339
1340 cluster = find_free_cluster(fat_bpb, findstart);
1341
1342 if (cluster)
1343 {
1344 /* create the cluster chain */
1345 if (oldcluster)
1346 update_fat_entry(fat_bpb, oldcluster, cluster);
1347
1348 update_fat_entry(fat_bpb, cluster, FAT_EOF_MARK);
1349 }
1350 else
1351 {
1352 #ifdef TEST_FAT
1353 if (fat_bpb->fsinfo.freecount > 0)
1354 panicf("There is free space, but find_free_cluster() "
1355 "didn't find it!\n");
1356 #endif
1357 DEBUGF("Disk full!\n");
1358 }
1359
1360 dc_unlock_cache();
1032 } 1361 }
1033 1362
1034 return 0; 1363 return cluster;
1035} 1364}
1036 1365
1037static int flush_fat(IF_MV_NONVOID(struct bpb* fat_bpb)) 1366/* extend dir file by one cluster and clear it; file position should be at the
1367 current last cluster before calling and size of dir checked */
1368static int fat_extend_dir(struct bpb *fat_bpb, struct fat_filestr *dirstr)
1038{ 1369{
1039 int i; 1370 DEBUGF("%s()\n", __func__);
1371
1040 int rc; 1372 int rc;
1041 unsigned char *sec;
1042 LDEBUGF("flush_fat()\n");
1043 1373
1044 mutex_lock(&cache_mutex); 1374 long cluster = dirstr->lastcluster;
1045 for(i = 0;i < FAT_CACHE_SIZE;i++) 1375 long newcluster = next_write_cluster(fat_bpb, cluster);
1376
1377 if (!newcluster)
1046 { 1378 {
1047 struct fat_cache_entry *fce = &fat_cache[i]; 1379 /* no more room or something went wrong */
1048 if(fce->inuse 1380 DEBUGF("Out of space\n");
1049#ifdef HAVE_MULTIVOLUME 1381 FAT_ERROR(FAT_RC_ENOSPC);
1050 && fce->fat_vol == fat_bpb 1382 }
1051#endif 1383
1052 && fce->dirty) 1384 /* we must clear whole clusters */
1385 unsigned long startsector = cluster2sec(fat_bpb, newcluster);
1386 unsigned long sector = startsector - 1;
1387
1388 if (startsector == 0)
1389 FAT_ERROR(-1);
1390
1391 for (unsigned int i = 0; i < fat_bpb->bpb_secperclus; i++)
1392 {
1393 dc_lock_cache();
1394
1395 void *sec = cache_sector_buffer(IF_MV(fat_bpb,) ++sector);
1396 if (!sec)
1053 { 1397 {
1054 sec = fat_cache_sectors[i]; 1398 dc_unlock_cache();
1055 flush_fat_sector(fce, sec); 1399 DEBUGF("Cannot clear cluster %ld\n", newcluster);
1400 update_fat_entry(fat_bpb, newcluster, 0);
1401 FAT_ERROR(-2);
1056 } 1402 }
1403
1404 memset(sec, 0, SECTOR_SIZE);
1405 dc_dirty_buf(sec);
1406 dc_unlock_cache();
1057 } 1407 }
1058 mutex_unlock(&cache_mutex);
1059 1408
1060 rc = update_fsinfo(IF_MV(fat_bpb)); 1409 if (!dirstr->fatfilep->firstcluster)
1061 if (rc < 0) 1410 dirstr->fatfilep->firstcluster = newcluster;
1062 return rc * 10 - 3;
1063 1411
1064 return 0; 1412 dirstr->lastcluster = newcluster;
1413 dirstr->lastsector = sector;
1414 dirstr->clusternum++;
1415 dirstr->sectornum = sector - startsector;
1416 dirstr->eof = false;
1417
1418 rc = 0;
1419fat_error:
1420 return rc;
1065} 1421}
1066 1422
1067static void fat_time(unsigned short* date, 1423static void fat_open_internal(IF_MV(int volume,) long startcluster,
1068 unsigned short* time, 1424 struct fat_file *file)
1069 unsigned short* tenth )
1070{ 1425{
1426#ifdef HAVE_MULTIVOLUME
1427 file->volume = volume;
1428#endif
1429 file->firstcluster = startcluster;
1430 file->dircluster = 0;
1431 file->e.entry = 0;
1432 file->e.entries = 0;
1433}
1434
1071#if CONFIG_RTC 1435#if CONFIG_RTC
1072 struct tm* tm = get_time(); 1436static void fat_time(uint16_t *date, uint16_t *time, int16_t *tenth)
1437{
1438 struct tm *tm = get_time();
1073 1439
1074 if (date) 1440 if (date)
1441 {
1075 *date = ((tm->tm_year - 80) << 9) | 1442 *date = ((tm->tm_year - 80) << 9) |
1076 ((tm->tm_mon + 1) << 5) | 1443 ((tm->tm_mon + 1) << 5) |
1077 tm->tm_mday; 1444 tm->tm_mday;
1445 }
1078 1446
1079 if (time) 1447 if (time)
1448 {
1080 *time = (tm->tm_hour << 11) | 1449 *time = (tm->tm_hour << 11) |
1081 (tm->tm_min << 5) | 1450 (tm->tm_min << 5) |
1082 (tm->tm_sec >> 1); 1451 (tm->tm_sec >> 1);
1452 }
1083 1453
1084 if (tenth) 1454 if (tenth)
1085 *tenth = (tm->tm_sec & 1) * 100; 1455 *tenth = (tm->tm_sec & 1) * 100;
1086#else 1456}
1457
1458#else /* !CONFIG_RTC */
1459
1460static void fat_time(uint16_t *date, uint16_t *time, int16_t *tenth)
1461{
1087 /* non-RTC version returns an increment from the supplied time, or a 1462 /* non-RTC version returns an increment from the supplied time, or a
1088 * fixed standard time/date if no time given as input */ 1463 * fixed standard time/date if no time given as input */
1089 1464
1090/* Macros to convert a 2-digit string to a decimal constant. 1465 /* Macros to convert a 2-digit string to a decimal constant.
1091 (YEAR), MONTH and DAY are set by the date command, which outputs 1466 (YEAR), MONTH and DAY are set by the date command, which outputs
1092 DAY as 00..31 and MONTH as 01..12. The leading zero would lead to 1467 DAY as 00..31 and MONTH as 01..12. The leading zero would lead to
1093 misinterpretation as an octal constant. */ 1468 misinterpretation as an octal constant. */
1094#define S100(x) 1 ## x 1469 #define S100(x) 1 ## x
1095#define C2DIG2DEC(x) (S100(x)-100) 1470 #define C2DIG2DEC(x) (S100(x)-100)
1096/* The actual build date, as FAT date constant */ 1471 /* The actual build date, as FAT date constant */
1097#define BUILD_DATE_FAT (((YEAR - 1980) << 9) \ 1472 #define BUILD_DATE_FAT (((YEAR - 1980) << 9) \
1098 | (C2DIG2DEC(MONTH) << 5) \ 1473 | (C2DIG2DEC(MONTH) << 5) \
1099 | C2DIG2DEC(DAY)) 1474 | C2DIG2DEC(DAY))
1100 1475
1101 bool date_forced = false; 1476 bool date_forced = false;
1102 bool next_day = false; 1477 bool next_day = false;
@@ -1107,7 +1482,7 @@ static void fat_time(unsigned short* date,
1107 *date = BUILD_DATE_FAT; 1482 *date = BUILD_DATE_FAT;
1108 date_forced = true; 1483 date_forced = true;
1109 } 1484 }
1110 1485
1111 if (time) 1486 if (time)
1112 { 1487 {
1113 time2 = *time << 1; 1488 time2 = *time << 1;
@@ -1119,8 +1494,8 @@ static void fat_time(unsigned short* date,
1119 { 1494 {
1120 unsigned mins = (time2 >> 6) & 0x3f; 1495 unsigned mins = (time2 >> 6) & 0x3f;
1121 unsigned hours = (time2 >> 12) & 0x1f; 1496 unsigned hours = (time2 >> 12) & 0x1f;
1122 1497
1123 mins = 11 * ((mins/11) + 1); /* advance to next multiple of 11 */ 1498 mins = 11 * ((mins / 11) + 1); /* advance to next multiple of 11 */
1124 if (mins > 59) 1499 if (mins > 59)
1125 { 1500 {
1126 mins = 11; /* 00 would be a bad marker */ 1501 mins = 11; /* 00 would be a bad marker */
@@ -1134,14 +1509,14 @@ static void fat_time(unsigned short* date,
1134 } 1509 }
1135 *time = time2 >> 1; 1510 *time = time2 >> 1;
1136 } 1511 }
1137 1512
1138 if (tenth) 1513 if (tenth)
1139 *tenth = (time2 & 1) * 100; 1514 *tenth = (time2 & 1) * 100;
1140 1515
1141 if (date && next_day) 1516 if (date && next_day)
1142 { 1517 {
1143 static const unsigned char daysinmonth[] = 1518 static const unsigned char daysinmonth[] =
1144 {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 1519 { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
1145 unsigned day = *date & 0x1f; 1520 unsigned day = *date & 0x1f;
1146 unsigned month = (*date >> 5) & 0x0f; 1521 unsigned month = (*date >> 5) & 0x0f;
1147 unsigned year = (*date >> 9) & 0x7f; 1522 unsigned year = (*date >> 9) & 0x7f;
@@ -1156,1514 +1531,1393 @@ static void fat_time(unsigned short* date,
1156 year++; 1531 year++;
1157 } 1532 }
1158 } 1533 }
1534
1159 *date = (year << 9) | (month << 5) | day; 1535 *date = (year << 9) | (month << 5) | day;
1160 } 1536 }
1161
1162#endif /* CONFIG_RTC */
1163} 1537}
1538#endif /* CONFIG_RTC */
1164 1539
1165static int write_long_name(struct fat_file* file, 1540static int write_longname(struct bpb *fat_bpb, struct fat_filestr *parentstr,
1166 unsigned int firstentry, 1541 struct fat_file *file, const unsigned char *name,
1167 unsigned int numentries, 1542 unsigned long ucslen, const unsigned char *shortname,
1168 const unsigned char* name, 1543 union raw_dirent *srcent, uint8_t attr,
1169 const unsigned char* shortname, 1544 unsigned int flags)
1170 bool is_directory)
1171{ 1545{
1172 unsigned char* entry; 1546 DEBUGF("%s(file:%lx, first:%d, num:%d, name:%s)\n", __func__,
1173 unsigned int idx = firstentry % DIR_ENTRIES_PER_SECTOR; 1547 parent->info->firstcluster, firstentry, numentries, name);
1174 unsigned int sector = firstentry / DIR_ENTRIES_PER_SECTOR; 1548
1175 unsigned char chksum = 0;
1176 unsigned int i, j=0;
1177 unsigned int nameidx=0, namelen = utf8length(name);
1178 int rc; 1549 int rc;
1179 unsigned short name_utf16[namelen + 1]; 1550 union raw_dirent *ent;
1551
1552 uint16_t date = 0, time = 0, tenth = 0;
1553 fat_time(&date, &time, &tenth);
1554 time = htole16(time);
1555 date = htole16(date);
1180 1556
1181 LDEBUGF("write_long_name(file:%lx, first:%d, num:%d, name:%s)\n", 1557 /* shortname checksum saved in each longname entry */
1182 file->firstcluster, firstentry, numentries, name); 1558 uint8_t chksum = shortname_checksum(shortname);
1183 1559
1184 rc = fat_seek(file, sector); 1560 /* we need to convert the name first since the entries are written in
1185 if (rc<0) 1561 reverse order */
1186 return rc * 10 - 1; 1562 unsigned long ucspadlen = ALIGN_UP(ucslen, FATLONG_NAME_CHARS);
1563 uint16_t ucsname[ucspadlen];
1187 1564
1188 unsigned char* buf = fat_get_sector_buffer(); 1565 for (unsigned long i = 0; i < ucspadlen; i++)
1189 rc = fat_readwrite(file, 1, buf, false);
1190 if (rc<1)
1191 { 1566 {
1192 fat_release_sector_buffer(); 1567 if (i < ucslen)
1193 return rc * 10 - 2; 1568 name = utf8decode(name, &ucsname[i]);
1569 else if (i == ucslen)
1570 ucsname[i] = 0x0000; /* name doesn't fill last block */
1571 else /* i > ucslen */
1572 ucsname[i] = 0xffff; /* pad-out to end */
1194 } 1573 }
1195 1574
1196 /* calculate shortname checksum */ 1575 dc_lock_cache();
1197 for (i=11; i>0; i--)
1198 chksum = ((chksum & 1) ? 0x80 : 0) + (chksum >> 1) + shortname[j++];
1199
1200 /* calc position of last name segment */
1201 if ( namelen > NAME_BYTES_PER_ENTRY )
1202 for (nameidx=0;
1203 nameidx < (namelen - NAME_BYTES_PER_ENTRY);
1204 nameidx += NAME_BYTES_PER_ENTRY);
1205
1206 /* we need to convert the name first */
1207 /* since it is written in reverse order */
1208 for (i = 0; i <= namelen; i++)
1209 name = utf8decode(name, &name_utf16[i]);
1210
1211 for (i=0; i < numentries; i++) {
1212 /* new sector? */
1213 if ( idx >= DIR_ENTRIES_PER_SECTOR ) {
1214 /* update current sector */
1215 rc = fat_seek(file, sector);
1216 if (rc<0)
1217 {
1218 fat_release_sector_buffer();
1219 return rc * 10 - 3;
1220 }
1221
1222 rc = fat_readwrite(file, 1, buf, true);
1223 if (rc<1)
1224 {
1225 fat_release_sector_buffer();
1226 return rc * 10 - 4;
1227 }
1228
1229 /* read next sector */
1230 rc = fat_readwrite(file, 1, buf, false);
1231 if (rc<0) {
1232 fat_release_sector_buffer();
1233 LDEBUGF("Failed writing new sector: %d\n",rc);
1234 return rc * 10 - 5;
1235 }
1236 if (rc==0)
1237 /* end of dir */
1238 memset(buf, 0, SECTOR_SIZE);
1239 1576
1240 sector++; 1577 const unsigned int longentries = file->e.entries - 1;
1241 idx = 0; 1578 const unsigned int firstentry = file->e.entry - longentries;
1242 }
1243 1579
1244 entry = buf + idx * DIR_ENTRY_SIZE; 1580 /* longame entries */
1581 for (unsigned int i = 0; i < longentries; i++)
1582 {
1583 ent = cache_direntry(fat_bpb, parentstr, firstentry + i);
1584 if (!ent)
1585 FAT_ERROR(-2);
1245 1586
1246 /* verify this entry is free */ 1587 /* verify this entry is free */
1247 if (entry[0] && entry[0] != 0xe5 ) 1588 if (ent->name[0] && ent->name[0] != 0xe5)
1248 { 1589 {
1249 fat_release_sector_buffer();
1250 panicf("Dir entry %d in sector %x is not free! " 1590 panicf("Dir entry %d in sector %x is not free! "
1251 "%02x %02x %02x %02x", 1591 "%02x %02x %02x %02x",
1252 idx, sector, 1592 i + firstentry, (unsigned)parentstr->lastsector,
1253 entry[0], entry[1], entry[2], entry[3]); 1593 (unsigned)ent->data[0], (unsigned)ent->data[1],
1594 (unsigned)ent->data[2], (unsigned)ent->data[3]);
1254 } 1595 }
1255 1596
1256 memset(entry, 0, DIR_ENTRY_SIZE); 1597 memset(ent->data, 0, DIR_ENTRY_SIZE);
1257 if ( i+1 < numentries ) {
1258 /* longname entry */
1259 unsigned int k, l = nameidx;
1260
1261 entry[FATLONG_ORDER] = numentries-i-1;
1262 if (i==0) {
1263 /* mark this as last long entry */
1264 entry[FATLONG_ORDER] |= FATLONG_LAST_LONG_ENTRY;
1265
1266 /* pad name with 0xffff */
1267 for (k=1; k<11; k++) entry[k] = FAT_LONGNAME_PAD_BYTE;
1268 for (k=14; k<26; k++) entry[k] = FAT_LONGNAME_PAD_BYTE;
1269 for (k=28; k<32; k++) entry[k] = FAT_LONGNAME_PAD_BYTE;
1270 };
1271 /* set name */
1272 for (k=0; k<5 && l <= namelen; k++) {
1273 entry[k*2 + 1] = (unsigned char)(name_utf16[l] & 0xff);
1274 entry[k*2 + 2] = (unsigned char)(name_utf16[l++] >> 8);
1275 }
1276 for (k=0; k<6 && l <= namelen; k++) {
1277 entry[k*2 + 14] = (unsigned char)(name_utf16[l] & 0xff);
1278 entry[k*2 + 15] = (unsigned char)(name_utf16[l++] >> 8);
1279 }
1280 for (k=0; k<2 && l <= namelen; k++) {
1281 entry[k*2 + 28] = (unsigned char)(name_utf16[l] & 0xff);
1282 entry[k*2 + 29] = (unsigned char)(name_utf16[l++] >> 8);
1283 }
1284 1598
1285 entry[FATDIR_ATTR] = FAT_ATTR_LONG_NAME; 1599 unsigned int ord = longentries - i;
1286 entry[FATDIR_FSTCLUSLO] = 0; 1600
1287 entry[FATLONG_TYPE] = 0; 1601 ent->ldir_ord = ord | (i == 0 ? FATLONG_ORD_F_LAST : 0);
1288 entry[FATLONG_CHKSUM] = chksum; 1602 ent->ldir_attr = ATTR_LONG_NAME;
1289 LDEBUGF("Longname entry %d: %s\n", idx, name+nameidx); 1603 ent->ldir_chksum = chksum;
1290 } 1604
1291 else { 1605 /* set name */
1292 /* shortname entry */ 1606 uint16_t *ucsptr = &ucsname[(ord - 1) * FATLONG_NAME_CHARS];
1293 unsigned short date=0, time=0, tenth=0; 1607 for (unsigned j = longent_char_first(); j; j = longent_char_next(j))
1294 LDEBUGF("Shortname entry: %s\n", shortname); 1608 {
1295 memcpy(entry + FATDIR_NAME, shortname, 11); 1609 uint16_t ucs = *ucsptr++;
1296 entry[FATDIR_ATTR] = is_directory?FAT_ATTR_DIRECTORY:0; 1610 INT162BYTES(ent->data, j, ucs);
1297 entry[FATDIR_NTRES] = 0;
1298
1299 fat_time(&date, &time, &tenth);
1300 entry[FATDIR_CRTTIMETENTH] = tenth;
1301 *(unsigned short*)(entry + FATDIR_CRTTIME) = htole16(time);
1302 *(unsigned short*)(entry + FATDIR_WRTTIME) = htole16(time);
1303 *(unsigned short*)(entry + FATDIR_CRTDATE) = htole16(date);
1304 *(unsigned short*)(entry + FATDIR_WRTDATE) = htole16(date);
1305 *(unsigned short*)(entry + FATDIR_LSTACCDATE) = htole16(date);
1306 } 1611 }
1307 idx++; 1612
1308 nameidx -= NAME_BYTES_PER_ENTRY; 1613 dc_dirty_buf(ent);
1614 DEBUGF("Longname entry %d\n", ord);
1309 } 1615 }
1310 1616
1311 /* update last sector */ 1617 /* shortname entry */
1312 rc = fat_seek(file, sector); 1618 DEBUGF("Shortname '%s'\n", shortname);
1313 if (rc<0) 1619
1620 ent = cache_direntry(fat_bpb, parentstr, file->e.entry);
1621 if (!ent)
1622 FAT_ERROR(-2);
1623
1624 if (srcent && (flags & DIRENT_TEMPL))
1314 { 1625 {
1315 fat_release_sector_buffer(); 1626 /* srcent points to short entry template */
1316 return rc * 10 - 6; 1627 *ent = *srcent;
1628 }
1629 else
1630 {
1631 /* make our own short entry */
1632 memset(ent->data, 0, DIR_ENTRY_SIZE);
1633 ent->attr = attr;
1317 } 1634 }
1318 1635
1319 rc = fat_readwrite(file, 1, buf, true); 1636 /* short name may change even if just moving */
1320 fat_release_sector_buffer(); 1637 memcpy(ent->name, shortname, 11);
1321 if (rc<1) 1638 raw_dirent_set_fstclus(ent, file->firstcluster);
1322 return rc * 10 - 7;
1323 1639
1324 return 0; 1640 if (!(flags & DIRENT_TEMPL_CRT))
1325}
1326
1327static int fat_checkname(const unsigned char* newname)
1328{
1329 static const char invalid_chars[] = "\"*/:<>?\\|";
1330 int len = strlen(newname);
1331 /* More sanity checks are probably needed */
1332 if (len > 255 || newname[len - 1] == '.')
1333 { 1641 {
1334 return -1; 1642 ent->crttimetenth = tenth;
1643 ent->crttime = time;
1644 ent->crtdate = date;
1335 } 1645 }
1336 while (*newname) 1646
1647 if (!(flags & DIRENT_TEMPL_WRT))
1337 { 1648 {
1338 if (*newname < ' ' || strchr(invalid_chars, *newname) != NULL) 1649 ent->wrttime = time;
1339 return -1; 1650 ent->wrtdate = date;
1340 newname++;
1341 } 1651 }
1342 /* check trailing space(s) */
1343 if(*(--newname) == ' ')
1344 return -1;
1345 1652
1346 return 0; 1653 if (!(flags & DIRENT_TEMPL_ACC))
1654 ent->lstaccdate = date;
1655
1656 if (srcent && (flags & DIRENT_RETURN))
1657 *srcent = *ent; /* caller wants */
1658
1659 dc_dirty_buf(ent);
1660
1661 rc = 0;
1662fat_error:
1663 dc_unlock_cache();
1664 return rc;
1347} 1665}
1348 1666
1349static int add_dir_entry(struct fat_dir* dir, 1667static int add_dir_entry(struct bpb *fat_bpb, struct fat_filestr *parentstr,
1350 struct fat_file* file, 1668 struct fat_file *file, const char *name,
1351 const char* name, 1669 union raw_dirent *srcent, uint8_t attr,
1352 bool is_directory, 1670 unsigned int flags)
1353 bool dotdir)
1354{ 1671{
1355#ifdef HAVE_MULTIVOLUME 1672 DEBUGF("%s(name:\"%s\",first:%lx)\n", __func__, name,
1356 struct bpb* fat_bpb = &fat_bpbs[dir->file.volume]; 1673 file->firstcluster);
1357#else
1358 struct bpb* fat_bpb = &fat_bpbs[0];
1359#endif
1360 unsigned char shortname[12];
1361 int rc;
1362 unsigned int sector;
1363 bool done = false;
1364 int entries_needed, entries_found = 0;
1365 int firstentry;
1366
1367 LDEBUGF( "add_dir_entry(%s,%lx)\n",
1368 name, file->firstcluster);
1369
1370 /* Don't check dotdirs name for validity */
1371 if (dotdir == false) {
1372 rc = fat_checkname(name);
1373 if (rc < 0) {
1374 /* filename is invalid */
1375 return rc * 10 - 1;
1376 }
1377 }
1378 1674
1379#ifdef HAVE_MULTIVOLUME 1675 int rc;
1380 file->volume = dir->file.volume; /* inherit the volume, to make sure */
1381#endif
1382 1676
1383 /* The "." and ".." directory entries must not be long names */ 1677 unsigned char basisname[11], shortname[11];
1384 if(dotdir) { 1678 int n;
1385 int i; 1679 int entries_needed;
1386 strlcpy(shortname, name, 12); 1680 unsigned long ucslen = 0;
1387 for(i = strlen(shortname); i < 12; i++)
1388 shortname[i] = ' ';
1389 1681
1682 if (is_dotdir_name(name) && (attr & ATTR_DIRECTORY))
1683 {
1684 /* The "." and ".." directory entries must not be long names */
1685 int dots = strlcpy(shortname, name, 11);
1686 memset(&shortname[dots], ' ', 11 - dots);
1390 entries_needed = 1; 1687 entries_needed = 1;
1391 } else { 1688 }
1392 create_dos_name(name, shortname); 1689 else
1690 {
1691 rc = check_longname(name);
1692 if (rc < 0)
1693 FAT_ERROR(rc * 10 - 1); /* filename is invalid */
1694
1695 create_dos_name(basisname, name, &n);
1696 randomize_dos_name(shortname, basisname, &n);
1393 1697
1394 /* one dir entry needed for every 13 bytes of filename, 1698 /* one dir entry needed for every 13 characters of filename,
1395 plus one entry for the short name */ 1699 plus one entry for the short name */
1396 entries_needed = (utf8length(name) + (NAME_BYTES_PER_ENTRY-1)) 1700 ucslen = utf8length(name);
1397 / NAME_BYTES_PER_ENTRY + 1; 1701 if (ucslen > 255)
1702 FAT_ERROR(-2); /* name is too long */
1703
1704 entries_needed = (ucslen + FATLONG_NAME_CHARS - 1)
1705 / FATLONG_NAME_CHARS + 1;
1398 } 1706 }
1399 1707
1400 unsigned char* buf = fat_get_sector_buffer(); 1708 int entry = 0, entries_found = 0, firstentry = -1;
1401 restart: 1709 const int entperclus = DIR_ENTRIES_PER_SECTOR*fat_bpb->bpb_secperclus;
1402 firstentry = -1;
1403 1710
1404 rc = fat_seek(&dir->file, 0); 1711 /* step 1: search for a sufficiently-long run of free entries and check
1405 if (rc < 0) 1712 for duplicate shortname */
1406 { 1713 dc_lock_cache();
1407 fat_release_sector_buffer();
1408 return rc * 10 - 2;
1409 }
1410 1714
1411 /* step 1: search for free entries and check for duplicate shortname */ 1715 for (bool done = false; !done;)
1412 for (sector = 0; !done; sector++)
1413 { 1716 {
1414 unsigned int i; 1717 union raw_dirent *ent = cache_direntry(fat_bpb, parentstr, entry);
1415 1718
1416 rc = fat_readwrite(&dir->file, 1, buf, false); 1719 if (!ent)
1417 if (rc < 0) { 1720 {
1418 fat_release_sector_buffer(); 1721 if (parentstr->eof)
1419 DEBUGF( "add_dir_entry() - Couldn't read dir" 1722 {
1420 " (error code %d)\n", rc); 1723 DEBUGF("End of dir (entry %d)\n", entry);
1421 return rc * 10 - 3; 1724 break;
1422 } 1725 }
1423 1726
1424 if (rc == 0) { /* current end of dir reached */ 1727 DEBUGF("Couldn't read dir (entry %d)\n", entry);
1425 LDEBUGF("End of dir on cluster boundary\n"); 1728 dc_unlock_cache();
1426 break; 1729 FAT_ERROR(-3);
1427 } 1730 }
1428 1731
1429 /* look for free slots */ 1732 switch (ent->name[0])
1430 for (i = 0; i < DIR_ENTRIES_PER_SECTOR; i++)
1431 { 1733 {
1432 switch (buf[i * DIR_ENTRY_SIZE]) { 1734 case 0: /* all remaining entries in cluster are free */
1433 case 0: 1735 DEBUGF("Found end of dir %d\n", entry);
1434 entries_found += DIR_ENTRIES_PER_SECTOR - i; 1736 int found = entperclus - (entry % entperclus);
1435 LDEBUGF("Found end of dir %d\n", 1737 entries_found += found;
1436 sector * DIR_ENTRIES_PER_SECTOR + i); 1738 entry += found; /* move entry passed end of cluster */
1437 i = DIR_ENTRIES_PER_SECTOR - 1; 1739 done = true;
1438 done = true; 1740 break;
1439 break;
1440 1741
1441 case 0xe5: 1742 case 0xe5: /* individual free entry */
1442 entries_found++; 1743 entries_found++;
1443 LDEBUGF("Found free entry %d (%d/%d)\n", 1744 entry++;
1444 sector * DIR_ENTRIES_PER_SECTOR + i, 1745 DEBUGF("Found free entry %d (%d/%d)\n",
1445 entries_found, entries_needed); 1746 entry, entries_found, entries_needed);
1446 break; 1747 break;
1447 1748
1448 default: 1749 default: /* occupied */
1449 entries_found = 0; 1750 entries_found = 0;
1751 entry++;
1450 1752
1451 /* check that our intended shortname doesn't already exist */ 1753 if ((ent->ldir_attr & ATTR_LONG_NAME_MASK) == ATTR_LONG_NAME)
1452 if (!strncmp(shortname, buf + i * DIR_ENTRY_SIZE, 11)) { 1754 break; /* ignore long name entry */
1453 /* shortname exists already, make a new one */
1454 randomize_dos_name(shortname);
1455 LDEBUGF("Duplicate shortname, changing to %s\n",
1456 shortname);
1457 1755
1458 /* name has changed, we need to restart search */ 1756 /* check that our intended shortname doesn't already exist */
1459 goto restart; 1757 if (!strncmp(shortname, ent->name, 11))
1460 } 1758 {
1461 break; 1759 /* shortname exists already, make a new one */
1760 DEBUGF("Duplicate shortname '%.11s'", shortname);
1761 randomize_dos_name(shortname, basisname, &n);
1762 DEBUGF(", changing to '%.11s'\n", shortname);
1763
1764 /* name has changed, we need to restart search */
1765 entry = 0;
1766 firstentry = -1;
1462 } 1767 }
1463 if (firstentry < 0 && (entries_found >= entries_needed)) 1768 break;
1464 firstentry = sector * DIR_ENTRIES_PER_SECTOR + i + 1 1769 }
1465 - entries_found; 1770
1771 if (firstentry < 0 && entries_found >= entries_needed)
1772 {
1773 /* found adequate space; point to initial free entry */
1774 firstentry = entry - entries_found;
1466 } 1775 }
1467 } 1776 }
1468 1777
1778 dc_unlock_cache();
1779
1469 /* step 2: extend the dir if necessary */ 1780 /* step 2: extend the dir if necessary */
1470 if (firstentry < 0) 1781 if (firstentry < 0)
1471 { 1782 {
1472 LDEBUGF("Adding new sector(s) to dir\n"); 1783 DEBUGF("Adding new cluster(s) to dir\n");
1473 rc = fat_seek(&dir->file, sector); 1784
1474 if (rc < 0) 1785 if (entry + entries_needed - entries_found > MAX_DIRENTRIES)
1475 { 1786 {
1476 fat_release_sector_buffer(); 1787 /* FAT specification allows no more than 65536 entries (2MB)
1477 return rc * 10 - 4; 1788 per directory */
1789 DEBUGF("Directory would be too large.\n");
1790 FAT_ERROR(-4);
1478 } 1791 }
1479 memset(buf, 0, SECTOR_SIZE);
1480 1792
1481 /* we must clear whole clusters */ 1793 while (entries_found < entries_needed)
1482 for (; (entries_found < entries_needed) ||
1483 (dir->file.sectornum < (int)fat_bpb->bpb_secperclus); sector++)
1484 { 1794 {
1485 if (sector >= (65536/DIR_ENTRIES_PER_SECTOR)) 1795 rc = fat_extend_dir(fat_bpb, parentstr);
1486 { 1796 if (rc == FAT_RC_ENOSPC)
1487 fat_release_sector_buffer(); 1797 FAT_ERROR(RC);
1488 return -5; /* dir too large -- FAT specification */ 1798 else if (rc < 0)
1489 } 1799 FAT_ERROR(rc * 10 - 5);
1490 1800
1491 rc = fat_readwrite(&dir->file, 1, buf, true); 1801 entries_found += entperclus;
1492 if (rc < 1) /* No more room or something went wrong */ 1802 entry += entperclus;
1493 {
1494 fat_release_sector_buffer();
1495 return rc * 10 - 6;
1496 }
1497
1498 entries_found += DIR_ENTRIES_PER_SECTOR;
1499 } 1803 }
1500 1804
1501 firstentry = sector * DIR_ENTRIES_PER_SECTOR - entries_found; 1805 firstentry = entry - entries_found;
1502 } 1806 }
1503 fat_release_sector_buffer();
1504 1807
1505 /* step 3: add entry */ 1808 /* remember the parent directory entry information */
1506 sector = firstentry / DIR_ENTRIES_PER_SECTOR; 1809#ifdef HAVE_MULTIVOLUME
1507 LDEBUGF("Adding longname to entry %d in sector %d\n", 1810 file->volume = parentstr->fatfilep->volume;
1508 firstentry, sector); 1811#endif
1812 file->dircluster = parentstr->fatfilep->firstcluster;
1813 file->e.entry = firstentry + entries_needed - 1;
1814 file->e.entries = entries_needed;
1509 1815
1510 rc = write_long_name(&dir->file, firstentry, 1816 /* step 3: add entry */
1511 entries_needed, name, 1817 DEBUGF("Adding longname to entry %d\n", firstentry);
1512 shortname, is_directory); 1818 rc = write_longname(fat_bpb, parentstr, file, name, ucslen,
1819 shortname, srcent, attr, flags);
1513 if (rc < 0) 1820 if (rc < 0)
1514 return rc * 10 - 7; 1821 FAT_ERROR(rc * 10 - 6);
1515 1822
1516 /* remember where the shortname dir entry is located */ 1823 DEBUGF("Added new dir entry %u; using %u entries\n",
1517 file->direntry = firstentry + entries_needed - 1; 1824 file->e.entry, file->e.entries);
1518 file->direntries = entries_needed;
1519 file->dircluster = dir->file.firstcluster;
1520 LDEBUGF("Added new dir entry %d, using %d slots.\n",
1521 file->direntry, file->direntries);
1522 1825
1523 return 0; 1826 rc = 0;
1827fat_error:
1828 return rc;
1524} 1829}
1525 1830
1526static unsigned char char2dos(unsigned char c, int* randomize) 1831static int update_short_entry(struct bpb *fat_bpb, struct fat_file *file,
1832 uint32_t size, struct fat_direntry *fatent)
1527{ 1833{
1528 static const char invalid_chars[] = "\"*+,./:;<=>?[\\]|"; 1834 DEBUGF("%s(cluster:%lx entry:%d size:%ld)\n",
1529 1835 __func__, file->firstcluster, file->e.entry, size);
1530 if (c <= 0x20)
1531 c = 0; /* Illegal char, remove */
1532 else if (strchr(invalid_chars, c) != NULL)
1533 {
1534 /* Illegal char, replace */
1535 c = '_';
1536 *randomize = 1; /* as per FAT spec */
1537 }
1538 else
1539 c = toupper(c);
1540 1836
1541 return c; 1837 int rc;
1542}
1543
1544static void create_dos_name(const unsigned char *name, unsigned char *newname)
1545{
1546 int i;
1547 unsigned char *ext;
1548 int randomize = 0;
1549 1838
1550 /* Find extension part */ 1839#if CONFIG_RTC
1551 ext = strrchr(name, '.'); 1840 uint16_t time = 0;
1552 if (ext == name) /* handle .dotnames */ 1841 uint16_t date = 0;
1553 ext = NULL; 1842#else
1843 /* get old time to increment from */
1844 uint16_t time = letoh16(fatent->wrttime);
1845 uint16_t date = letoh16(fatent->wrtdate);
1846#endif
1847 fat_time(&date, &time, NULL);
1848 date = htole16(date);
1849 time = htole16(time);
1554 1850
1555 /* needs to randomize? */ 1851 /* open the parent directory */
1556 if((ext && (strlen(ext) > 4)) || 1852 struct fat_file parent;
1557 ((ext ? (unsigned int)(ext-name) : strlen(name)) > 8) ) 1853 fat_open_internal(IF_MV(file->volume,) file->dircluster, &parent);
1558 randomize = 1;
1559 1854
1560 /* Name part */ 1855 struct fat_filestr parentstr;
1561 for (i = 0; *name && (!ext || name < ext) && (i < 8); name++) 1856 fat_filestr_init(&parentstr, &parent);
1562 {
1563 unsigned char c = char2dos(*name, &randomize);
1564 if (c)
1565 newname[i++] = c;
1566 }
1567 1857
1568 /* Pad both name and extension */ 1858 dc_lock_cache();
1569 while (i < 11)
1570 newname[i++] = ' ';
1571 1859
1572 if (newname[0] == 0xe5) /* Special kanji character */ 1860 union raw_dirent *ent = cache_direntry(fat_bpb, &parentstr, file->e.entry);
1573 newname[0] = 0x05; 1861 if (!ent)
1862 FAT_ERROR(-1);
1574 1863
1575 if (ext) 1864 if (!ent->name[0] || ent->name[0] == 0xe5)
1576 { /* Extension part */ 1865 panicf("Updating size on empty dir entry %d\n", file->e.entry);
1577 ext++;
1578 for (i = 8; *ext && (i < 11); ext++)
1579 {
1580 unsigned char c = char2dos(*ext, &randomize);
1581 if (c)
1582 newname[i++] = c;
1583 }
1584 }
1585 1866
1586 if(randomize) 1867 /* basic file data */
1587 randomize_dos_name(newname); 1868 raw_dirent_set_fstclus(ent, file->firstcluster);
1588} 1869 ent->filesize = htole32(size);
1589 1870
1590static void randomize_dos_name(unsigned char *name) 1871 /* time and date info */
1591{ 1872 ent->wrttime = time;
1592 unsigned char* tilde = NULL; /* ~ location */ 1873 ent->wrtdate = date;
1593 unsigned char* lastpt = NULL; /* last point of filename */ 1874 ent->lstaccdate = date;
1594 unsigned char* nameptr = name; /* working copy of name pointer */
1595 unsigned char num[9]; /* holds number as string */
1596 int i = 0;
1597 int cnt = 1;
1598 int numlen;
1599 int offset;
1600 1875
1601 while(i++ < 8) 1876 if (fatent)
1602 { 1877 {
1603 /* hunt for ~ and where to put it */ 1878 fatent->name[0] = '\0'; /* not gonna bother here */
1604 if(!tilde && *nameptr == '~') 1879 parse_short_direntry(ent, fatent);
1605 tilde = nameptr;
1606 if(!lastpt && (*nameptr == ' ' || *nameptr == '~'))
1607 lastpt = nameptr;
1608 nameptr++;
1609 } 1880 }
1610 if(tilde)
1611 {
1612 /* extract current count and increment */
1613 memcpy(num,tilde+1,7-(unsigned int)(tilde-name));
1614 num[7-(unsigned int)(tilde-name)] = 0;
1615 cnt = atoi(num) + 1;
1616 }
1617 cnt %= 10000000; /* protection */
1618 snprintf(num, 9, "~%d", cnt); /* allow room for trailing zero */
1619 numlen = strlen(num); /* required space */
1620 offset = (unsigned int)(lastpt ? lastpt - name : 8); /* prev startpoint */
1621 if(offset > (8-numlen)) offset = 8-numlen; /* correct for new numlen */
1622 1881
1623 memcpy(&name[offset], num, numlen); 1882 dc_dirty_buf(ent);
1624 1883
1625 /* in special case of counter overflow: pad with spaces */ 1884 rc = 0;
1626 for(offset = offset+numlen; offset < 8; offset++) 1885fat_error:
1627 name[offset] = ' '; 1886 dc_unlock_cache();
1887 return rc;
1628} 1888}
1629 1889
1630static int update_short_entry( struct fat_file* file, long size, int attr ) 1890static int free_direntries(struct bpb *fat_bpb, struct fat_file *file)
1631{ 1891{
1632 int sector = file->direntry / DIR_ENTRIES_PER_SECTOR; 1892 /* open the parent directory */
1633 uint32_t* sizeptr; 1893 struct fat_file parent;
1634 uint16_t* clusptr; 1894 fat_open_internal(IF_MV(file->volume,) file->dircluster, &parent);
1635 struct fat_file dir;
1636 int rc;
1637 1895
1638 LDEBUGF("update_file_size(cluster:%lx entry:%d size:%ld)\n", 1896 struct fat_filestr parentstr;
1639 file->firstcluster, file->direntry, size); 1897 fat_filestr_init(&parentstr, &parent);
1640 1898
1641 /* create a temporary file handle for the dir holding this file */ 1899 for (unsigned int entries = file->e.entries,
1642 rc = fat_open(IF_MV(file->volume,) file->dircluster, &dir, NULL); 1900 entry = file->e.entry - entries + 1;
1643 if (rc < 0) 1901 entries; entries--, entry++)
1644 return rc * 10 - 1;
1645
1646 rc = fat_seek( &dir, sector );
1647 if (rc<0)
1648 return rc * 10 - 2;
1649
1650 unsigned char* buf = fat_get_sector_buffer();
1651 unsigned char* entry =
1652 buf + DIR_ENTRY_SIZE * (file->direntry % DIR_ENTRIES_PER_SECTOR);
1653 rc = fat_readwrite(&dir, 1, buf, false);
1654 if (rc < 1)
1655 { 1902 {
1656 fat_release_sector_buffer(); 1903 DEBUGF("Clearing dir entry %d (%d/%d)\n",
1657 return rc * 10 - 3; 1904 entry, entry - numentries + 1, numentries);
1658 }
1659 1905
1660 if (!entry[0] || entry[0] == 0xe5) 1906 dc_lock_cache();
1661 {
1662 fat_release_sector_buffer();
1663 panicf("Updating size on empty dir entry %d\n", file->direntry);
1664 }
1665 1907
1666 entry[FATDIR_ATTR] = attr & 0xFF; 1908 union raw_dirent *ent = cache_direntry(fat_bpb, &parentstr, entry);
1909 if (!ent)
1910 {
1911 dc_unlock_cache();
1667 1912
1668 clusptr = (uint16_t*)(entry + FATDIR_FSTCLUSHI); 1913 if (entries == file->e.entries)
1669 *clusptr = htole16(file->firstcluster >> 16); 1914 return -1; /* nothing at all freed */
1670 1915
1671 clusptr = (uint16_t*)(entry + FATDIR_FSTCLUSLO); 1916 /* longname already destroyed; revert to shortname */
1672 *clusptr = htole16(file->firstcluster & 0xffff); 1917 file->e.entries = 1;
1918 return 0;
1919 }
1673 1920
1674 sizeptr = (uint32_t*)(entry + FATDIR_FILESIZE); 1921 ent->data[0] = 0xe5;
1675 *sizeptr = htole32(size);
1676 1922
1677 { 1923 dc_dirty_buf(ent);
1678#if CONFIG_RTC 1924 dc_unlock_cache();
1679 uint16_t time = 0;
1680 uint16_t date = 0;
1681#else
1682 /* get old time to increment from */
1683 uint16_t time = htole16(*(uint16_t*)(entry+FATDIR_WRTTIME));
1684 uint16_t date = htole16(*(uint16_t*)(entry+FATDIR_WRTDATE));
1685#endif
1686 fat_time(&date, &time, NULL);
1687 *(uint16_t*)(entry + FATDIR_WRTTIME) = htole16(time);
1688 *(uint16_t*)(entry + FATDIR_WRTDATE) = htole16(date);
1689 *(uint16_t*)(entry + FATDIR_LSTACCDATE) = htole16(date);
1690 } 1925 }
1691 1926
1692 rc = fat_seek( &dir, sector ); 1927 /* directory entry info is now gone */
1693 if (rc < 0) 1928 file->dircluster = 0;
1694 { 1929 file->e.entry = FAT_RW_VAL;
1695 fat_release_sector_buffer(); 1930 file->e.entries = 0;
1696 return rc * 10 - 4;
1697 }
1698
1699 rc = fat_readwrite(&dir, 1, buf, true);
1700 fat_release_sector_buffer();
1701 if (rc < 1)
1702 return rc * 10 - 5;
1703 1931
1704 return 0; 1932 return 1;
1705} 1933}
1706 1934
1707static int parse_direntry(struct fat_direntry *de, const unsigned char *buf) 1935static int free_cluster_chain(struct bpb *fat_bpb, long startcluster)
1708{ 1936{
1709 int i=0,j=0; 1937 for (long last = startcluster, next; last; last = next)
1710 unsigned char c; 1938 {
1711 bool lowercase; 1939 next = get_next_cluster(fat_bpb, last);
1940 int rc = update_fat_entry(fat_bpb, last, 0);
1941 if (LIKELY(rc >= 0 && !startcluster))
1942 continue;
1712 1943
1713 memset(de, 0, sizeof(struct fat_direntry)); 1944 if (rc < 0)
1714 de->attr = buf[FATDIR_ATTR]; 1945 return startcluster ? -1 : 0;
1715 de->crttimetenth = buf[FATDIR_CRTTIMETENTH];
1716 de->crtdate = BYTES2INT16(buf,FATDIR_CRTDATE);
1717 de->crttime = BYTES2INT16(buf,FATDIR_CRTTIME);
1718 de->wrtdate = BYTES2INT16(buf,FATDIR_WRTDATE);
1719 de->wrttime = BYTES2INT16(buf,FATDIR_WRTTIME);
1720 de->filesize = BYTES2INT32(buf,FATDIR_FILESIZE);
1721 de->firstcluster = ((long)(unsigned)BYTES2INT16(buf,FATDIR_FSTCLUSLO)) |
1722 ((long)(unsigned)BYTES2INT16(buf,FATDIR_FSTCLUSHI) << 16);
1723 /* The double cast is to prevent a sign-extension to be done on CalmRISC16.
1724 (the result of the shift is always considered signed) */
1725 1946
1726 /* fix the name */ 1947 startcluster = 0;
1727 lowercase = (buf[FATDIR_NTRES] & FAT_NTRES_LC_NAME);
1728 c = buf[FATDIR_NAME];
1729 if (c == 0x05) /* special kanji char */
1730 c = 0xe5;
1731 i = 0;
1732 while (c != ' ') {
1733 de->name[j++] = lowercase ? tolower(c) : c;
1734 if (++i >= 8)
1735 break;
1736 c = buf[FATDIR_NAME+i];
1737 }
1738 if (buf[FATDIR_NAME+8] != ' ') {
1739 lowercase = (buf[FATDIR_NTRES] & FAT_NTRES_LC_EXT);
1740 de->name[j++] = '.';
1741 for (i = 8; (i < 11) && ((c = buf[FATDIR_NAME+i]) != ' '); i++)
1742 de->name[j++] = lowercase ? tolower(c) : c;
1743 } 1948 }
1949
1744 return 1; 1950 return 1;
1745} 1951}
1746 1952
1747int fat_open(IF_MV(int volume,)
1748 long startcluster,
1749 struct fat_file *file,
1750 const struct fat_dir* dir)
1751{
1752 /* Remember where the file's dir entry is located
1753 * Do it before assigning other fields so that fat_open
1754 * can be called with file == &dir->file (see fat_opendir) */
1755 if ( dir ) {
1756 file->direntry = dir->entry - 1;
1757 file->direntries = dir->entrycount;
1758 file->dircluster = dir->file.firstcluster;
1759 }
1760
1761 file->firstcluster = startcluster;
1762 file->lastcluster = startcluster;
1763 file->lastsector = 0;
1764 file->clusternum = 0;
1765 file->sectornum = 0;
1766 file->eof = false;
1767#ifdef HAVE_MULTIVOLUME
1768 file->volume = volume;
1769 /* fixme: remove error check when done */
1770 if (volume >= NUM_VOLUMES || !fat_bpbs[volume].mounted)
1771 {
1772 LDEBUGF("fat_open() illegal volume %d\n", volume);
1773 return -1;
1774 }
1775#endif
1776 1953
1777 LDEBUGF("fat_open(%lx), entry %d\n",startcluster,file->direntry); 1954/** File entity functions **/
1778 return 0;
1779}
1780 1955
1781int fat_create_file(const char* name, 1956int fat_create_file(struct fat_file *parent, const char *name,
1782 struct fat_file* file, 1957 uint8_t attr, struct fat_file *file,
1783 struct fat_dir* dir) 1958 struct fat_direntry *fatent)
1784{ 1959{
1785 int rc; 1960 DEBUGF("%s(\"%s\",%lx,%lx)\n", __func__, name, (long)file, (long)dir);
1786 1961 struct bpb * const fat_bpb = FAT_BPB(parent->volume);
1787 LDEBUGF("fat_create_file(\"%s\",%lx,%lx)\n",name,(long)file,(long)dir); 1962 if (!fat_bpb)
1788 rc = add_dir_entry(dir, file, name, false, false); 1963 return -1;
1789 if (!rc) {
1790 file->firstcluster = 0;
1791 file->lastcluster = 0;
1792 file->lastsector = 0;
1793 file->clusternum = 0;
1794 file->sectornum = 0;
1795 file->eof = false;
1796 }
1797 1964
1798 return rc; 1965 int rc;
1799}
1800 1966
1801/* noinline because this is only split out of fat_create_dir to make sure 1967 fat_open_internal(IF_MV(parent->volume,) 0, file);
1802 * the sector buffer doesn't remain on the stack, to avoid nasty stack
1803 * overflows later on (when flush_fat() is called) */
1804static __attribute__((noinline)) int fat_clear_cluster(int sector,
1805 struct bpb *fat_bpb)
1806{
1807 unsigned char* buf = fat_get_sector_buffer();
1808 int i,rc;
1809 memset(buf, 0, SECTOR_SIZE);
1810 for(i = 0;i < (int)fat_bpb->bpb_secperclus;i++) {
1811 rc = transfer(IF_MV(fat_bpb,) sector + i, 1, buf, true );
1812 if (rc < 0)
1813 {
1814 fat_release_sector_buffer();
1815 return rc * 10 - 2;
1816 }
1817 }
1818 fat_release_sector_buffer();
1819 return 0;
1820}
1821 1968
1822int fat_create_dir(const char* name, 1969 struct fat_filestr parentstr;
1823 struct fat_dir* newdir, 1970 fat_filestr_init(&parentstr, parent);
1824 struct fat_dir* dir)
1825{
1826#ifdef HAVE_MULTIVOLUME
1827 struct bpb* fat_bpb = &fat_bpbs[dir->file.volume];
1828#else
1829 struct bpb* fat_bpb = &fat_bpbs[0];
1830#endif
1831 long sector;
1832 int rc;
1833 struct fat_file dummyfile;
1834 1971
1835 LDEBUGF("fat_create_dir(\"%s\",%lx,%lx)\n",name,(long)newdir,(long)dir); 1972 const bool isdir = attr & ATTR_DIRECTORY;
1973 unsigned int addflags = fatent ? DIRENT_RETURN : 0;
1974 union raw_dirent *newentp = (isdir || fatent) ?
1975 alloca(sizeof (union raw_dirent)) : NULL;
1836 1976
1837 memset(newdir, 0, sizeof(struct fat_dir)); 1977 if (isdir)
1838 memset(&dummyfile, 0, sizeof(struct fat_file)); 1978 {
1979 struct fat_filestr dirstr;
1980 fat_filestr_init(&dirstr, file);
1839 1981
1840 /* First, add the entry in the parent directory */ 1982 /* create the first cluster */
1841 rc = add_dir_entry(dir, &newdir->file, name, true, false); 1983 rc = fat_extend_dir(fat_bpb, &dirstr);
1842 if (rc < 0) 1984 if (rc == FAT_RC_ENOSPC)
1843 return rc * 10 - 1; 1985 FAT_ERROR(RC);
1986 else if (rc < 0)
1987 FAT_ERROR(rc * 10 - 2);
1844 1988
1845 /* Allocate a new cluster for the directory */ 1989 struct fat_file dummyfile;
1846 newdir->file.firstcluster = find_free_cluster(IF_MV(fat_bpb,)
1847 fat_bpb->fsinfo.nextfree);
1848 if(newdir->file.firstcluster == 0)
1849 return -1;
1850 1990
1851 update_fat_entry(IF_MV(fat_bpb,) newdir->file.firstcluster, FAT_EOF_MARK); 1991 /* add the "." entry */
1992 fat_open_internal(IF_MV(0,) file->firstcluster, &dummyfile);
1852 1993
1853 /* Clear the entire cluster */ 1994 /* this returns the short entry template for the remaining entries */
1854 sector = cluster2sec(IF_MV(fat_bpb,) newdir->file.firstcluster); 1995 rc = add_dir_entry(fat_bpb, &dirstr, &dummyfile, ".", newentp,
1855 rc = fat_clear_cluster(sector,fat_bpb); 1996 attr, DIRENT_RETURN);
1856 if (rc < 0) 1997 if (rc < 0)
1857 return rc; 1998 FAT_ERROR(rc * 10 - 3);
1858 1999
2000 /* and the ".." entry */
2001 /* the root cluster is cluster 0 in the ".." entry */
2002 fat_open_internal(IF_MV(0,)
2003 parent->firstcluster == fat_bpb->bpb_rootclus ?
2004 0 : parent->firstcluster, &dummyfile);
1859 2005
1860 /* Then add the "." entry */ 2006 rc = add_dir_entry(fat_bpb, &dirstr, &dummyfile, "..", newentp,
1861 rc = add_dir_entry(newdir, &dummyfile, ".", true, true); 2007 attr, DIRENT_TEMPL_TIMES);
1862 if (rc < 0) 2008 if (rc < 0)
1863 return rc * 10 - 3; 2009 FAT_ERROR(rc * 10 - 4);
1864 dummyfile.firstcluster = newdir->file.firstcluster;
1865 update_short_entry(&dummyfile, 0, FAT_ATTR_DIRECTORY);
1866 2010
1867 /* and the ".." entry */ 2011 addflags |= DIRENT_TEMPL_TIMES;
1868 rc = add_dir_entry(newdir, &dummyfile, "..", true, true); 2012 }
1869 if (rc < 0)
1870 return rc * 10 - 4;
1871 2013
1872 /* The root cluster is cluster 0 in the ".." entry */ 2014 /* lastly, add the entry in the parent directory */
1873 if(dir->file.firstcluster == fat_bpb->bpb_rootclus) 2015 rc = add_dir_entry(fat_bpb, &parentstr, file, name, newentp,
1874 dummyfile.firstcluster = 0; 2016 attr, addflags);
1875 else 2017 if (rc == FAT_RC_ENOSPC)
1876 dummyfile.firstcluster = dir->file.firstcluster; 2018 FAT_ERROR(RC);
1877 update_short_entry(&dummyfile, 0, FAT_ATTR_DIRECTORY); 2019 else if (rc < 0)
2020 FAT_ERROR(rc * 10 - 5);
1878 2021
1879 /* Set the firstcluster field in the direntry */ 2022 if (fatent)
1880 update_short_entry(&newdir->file, 0, FAT_ATTR_DIRECTORY); 2023 {
2024 strcpy(fatent->name, name);
2025 parse_short_direntry(newentp, fatent);
2026 }
1881 2027
1882 rc = flush_fat(IF_MV(fat_bpb)); 2028 rc = 0;
2029fat_error:
1883 if (rc < 0) 2030 if (rc < 0)
1884 return rc * 10 - 5; 2031 free_cluster_chain(fat_bpb, file->firstcluster);
1885 2032
2033 cache_commit(fat_bpb);
1886 return rc; 2034 return rc;
1887} 2035}
1888 2036
1889int fat_truncate(const struct fat_file *file) 2037bool fat_dir_is_parent(const struct fat_file *dir, const struct fat_file *file)
2038{
2039 /* if the directory file's first cluster is the same as the file's
2040 directory cluster and they're on the same volume, 'dir' is its parent
2041 directory; the file must also have a dircluster (ie. not removed) */
2042 long dircluster = file->dircluster;
2043 return dircluster && dircluster == dir->firstcluster
2044 IF_MV( && dir->volume == file->volume );
2045}
2046
2047bool fat_file_is_same(const struct fat_file *file1,
2048 const struct fat_file *file2)
2049{
2050 /* first, triviality */
2051 if (file1 == file2)
2052 return true;
2053
2054 /* if the directory info matches and the volumes are the same, file1 and
2055 file2 refer to the same file/directory */
2056 return file1->dircluster == file2->dircluster
2057 && file1->e.entry == file2->e.entry
2058 IF_MV( && file1->volume == file2->volume );
2059}
2060
2061int fat_open(const struct fat_file *parent, long startcluster,
2062 struct fat_file *file)
1890{ 2063{
1891 /* truncate trailing clusters */ 2064 if (!parent)
1892 long next; 2065 return -2; /* this does _not_ open any root */
1893 long last = file->lastcluster; 2066
2067 struct bpb * const fat_bpb = FAT_BPB(parent->volume);
2068 if (!fat_bpb)
2069 return -1;
2070
2071 /* inherit basic parent information; dirscan info is expected to have been
2072 initialized beforehand (usually via scanning for the entry ;) */
1894#ifdef HAVE_MULTIVOLUME 2073#ifdef HAVE_MULTIVOLUME
1895 struct bpb* fat_bpb = &fat_bpbs[file->volume]; 2074 file->volume = parent->volume;
1896#endif 2075#endif
2076 file->firstcluster = startcluster;
2077 file->dircluster = parent->firstcluster;
1897 2078
1898 LDEBUGF("fat_truncate(%lx, %lx)\n", file->firstcluster, last); 2079 return 0;
2080}
1899 2081
1900 for ( last = get_next_cluster(IF_MV(fat_bpb,) last); last; last = next ) { 2082int fat_open_rootdir(IF_MV(int volume,) struct fat_file *dir)
1901 next = get_next_cluster(IF_MV(fat_bpb,) last); 2083{
1902 update_fat_entry(IF_MV(fat_bpb,) last,0); 2084 struct bpb * const fat_bpb = FAT_BPB(volume);
1903 } 2085 if (!fat_bpb)
1904 if (file->lastcluster) 2086 return -1;
1905 update_fat_entry(IF_MV(fat_bpb,) file->lastcluster,FAT_EOF_MARK);
1906 2087
2088 fat_open_internal(IF_MV(volume,) fat_bpb->bpb_rootclus, dir);
1907 return 0; 2089 return 0;
1908} 2090}
1909 2091
1910int fat_closewrite(struct fat_file *file, long size, int attr) 2092int fat_remove(struct fat_file *file, enum fat_remove_op what)
1911{ 2093{
2094 struct bpb * const fat_bpb = FAT_BPB(file->volume);
2095 if (!fat_bpb)
2096 return -1;
2097
1912 int rc; 2098 int rc;
1913#ifdef HAVE_MULTIVOLUME
1914 struct bpb* fat_bpb = &fat_bpbs[file->volume];
1915#endif
1916 LDEBUGF("fat_closewrite(size=%ld)\n",size);
1917 2099
1918 if (!size) { 2100 if (file->firstcluster == fat_bpb->bpb_rootclus)
1919 /* empty file */ 2101 {
1920 if ( file->firstcluster ) { 2102 DEBUGF("Trying to remove root of volume %d\n",
1921 update_fat_entry(IF_MV(fat_bpb,) file->firstcluster, 0); 2103 IF_MV_VOL(info->volume));
1922 file->firstcluster = 0; 2104 FAT_ERROR(-2);
1923 }
1924 } 2105 }
1925 2106
1926 if (file->dircluster) { 2107 if (file->dircluster && (what & FAT_RM_DIRENTRIES))
1927 rc = update_short_entry(file, size, attr); 2108 {
1928 if (rc < 0) 2109 /* free everything in the parent directory */
1929 return rc * 10 - 1; 2110 DEBUGF("Removing dir entries: %lX\n", info->dircluster);
2111 rc = free_direntries(fat_bpb, file);
2112 if (rc <= 0)
2113 FAT_ERROR(rc * 10 - 3);
1930 } 2114 }
1931 2115
1932 flush_fat(IF_MV(fat_bpb)); 2116 if (file->firstcluster && (what & FAT_RM_DATA))
2117 {
2118 /* mark all clusters in the chain as free */
2119 DEBUGF("Removing cluster chain: %lX\n", file->firstcluster);
2120 rc = free_cluster_chain(fat_bpb, file->firstcluster);
2121 if (rc < 0)
2122 FAT_ERROR(rc * 10 - 4);
1933 2123
1934#ifdef TEST_FAT 2124 /* at least the first cluster was freed */
1935 if ( file->firstcluster ) { 2125 file->firstcluster = 0;
1936 /* debug */ 2126
1937#ifdef HAVE_MULTIVOLUME 2127 if (rc == 0)
1938 struct bpb* fat_bpb = &fat_bpbs[file->volume]; 2128 FAT_ERROR(-5);
1939#else
1940 struct bpb* fat_bpb = &fat_bpbs[0];
1941#endif
1942 long count = 0;
1943 long len;
1944 long next;
1945 for ( next = file->firstcluster; next;
1946 next = get_next_cluster(IF_MV(fat_bpb,) next) ) {
1947 LDEBUGF("cluster %ld: %lx\n", count, next);
1948 count++;
1949 }
1950 len = count * fat_bpb->bpb_secperclus * SECTOR_SIZE;
1951 LDEBUGF("File is %ld clusters (chainlen=%ld, size=%ld)\n",
1952 count, len, size );
1953 if ( len > size + fat_bpb->bpb_secperclus * SECTOR_SIZE)
1954 panicf("Cluster chain is too long\n");
1955 if ( len < size )
1956 panicf("Cluster chain is too short\n");
1957 } 2129 }
1958#endif
1959 2130
1960 return 0; 2131 rc = 0;
2132fat_error:
2133 cache_commit(fat_bpb);
2134 return rc;
1961} 2135}
1962 2136
1963static int free_direntries(struct fat_file* file) 2137int fat_rename(struct fat_file *parent, struct fat_file *file,
2138 const unsigned char *newname)
1964{ 2139{
1965 struct fat_file dir; 2140 struct bpb * const fat_bpb = FAT_BPB(parent->volume);
1966 int numentries = file->direntries; 2141 if (!fat_bpb)
1967 unsigned int entry = file->direntry - numentries + 1; 2142 return -1;
1968 unsigned int sector = entry / DIR_ENTRIES_PER_SECTOR; 2143
1969 int i;
1970 int rc; 2144 int rc;
2145 /* save old file; don't change it unless everything succeeds */
2146 struct fat_file newfile = *file;
1971 2147
1972 /* create a temporary file handle for the dir holding this file */ 2148#ifdef HAVE_MULTIVOLUME
1973 rc = fat_open(IF_MV(file->volume,) file->dircluster, &dir, NULL); 2149 /* rename only works on the same volume */
1974 if (rc < 0) 2150 if (file->volume != parent->volume)
1975 return rc * 10 - 1; 2151 {
2152 DEBUGF("No rename across volumes!\n");
2153 FAT_ERROR(-2);
2154 }
2155#endif
1976 2156
1977 rc = fat_seek( &dir, sector ); 2157 /* root directories can't be renamed */
1978 if (rc < 0) 2158 if (file->firstcluster == fat_bpb->bpb_rootclus)
1979 return rc * 10 - 2; 2159 {
2160 DEBUGF("Trying to rename root of volume %d\n",
2161 IF_MV_VOL(file->volume));
2162 FAT_ERROR(-3);
2163 }
1980 2164
1981 unsigned char* buf = fat_get_sector_buffer(); 2165 if (!file->dircluster)
1982 rc = fat_readwrite(&dir, 1, buf, false);
1983 if (rc < 1)
1984 { 2166 {
1985 fat_release_sector_buffer(); 2167 /* file was removed but is still open */
1986 return rc * 10 - 3; 2168 DEBUGF("File has no dir cluster!\n");
2169 FAT_ERROR(-4);
1987 } 2170 }
1988 2171
1989 for (i=0; i < numentries; i++) { 2172 struct fat_file dir;
1990 LDEBUGF("Clearing dir entry %d (%d/%d)\n", 2173 struct fat_filestr dirstr;
1991 entry, i+1, numentries);
1992 buf[(entry % DIR_ENTRIES_PER_SECTOR) * DIR_ENTRY_SIZE] = 0xe5;
1993 entry++;
1994 2174
1995 if ( (entry % DIR_ENTRIES_PER_SECTOR) == 0 ) { 2175 /* open old parent */
1996 /* flush this sector */ 2176 fat_open_internal(IF_MV(file->volume,) file->dircluster, &dir);
1997 rc = fat_seek(&dir, sector); 2177 fat_filestr_init(&dirstr, &dir);
1998 if (rc < 0)
1999 {
2000 fat_release_sector_buffer();
2001 return rc * 10 - 4;
2002 }
2003 2178
2004 rc = fat_readwrite(&dir, 1, buf, true); 2179 /* fetch a copy of the existing short entry */
2005 if (rc < 1) 2180 dc_lock_cache();
2006 {
2007 fat_release_sector_buffer();
2008 return rc * 10 - 5;
2009 }
2010 2181
2011 if ( i+1 < numentries ) { 2182 union raw_dirent *ent = cache_direntry(fat_bpb, &dirstr, file->e.entry);
2012 /* read next sector */ 2183 if (!ent)
2013 rc = fat_readwrite(&dir, 1, buf, false); 2184 {
2014 if (rc < 1) 2185 dc_unlock_cache();
2015 { 2186 FAT_ERROR(-5);
2016 fat_release_sector_buffer();
2017 return rc * 10 - 6;
2018 }
2019 }
2020 sector++;
2021 }
2022 } 2187 }
2023 2188
2024 if ( entry % DIR_ENTRIES_PER_SECTOR ) { 2189 union raw_dirent rawent = *ent;
2025 /* flush this sector */ 2190
2026 rc = fat_seek(&dir, sector); 2191 dc_unlock_cache();
2027 if (rc < 0) 2192
2193 /* create new name in new parent directory */
2194 fat_filestr_init(&dirstr, parent);
2195 rc = add_dir_entry(fat_bpb, &dirstr, &newfile, newname, &rawent,
2196 0, DIRENT_TEMPL_CRT | DIRENT_TEMPL_WRT);
2197 if (rc == FAT_RC_ENOSPC)
2198 FAT_ERROR(RC);
2199 else if (rc < 0)
2200 FAT_ERROR(rc * 10 - 6);
2201
2202 /* if renaming a directory and it was a move, update the '..' entry to
2203 keep it pointing to its parent directory */
2204 if ((rawent.attr & ATTR_DIRECTORY) && newfile.dircluster != file->dircluster)
2205 {
2206 /* open the dir that was renamed */
2207 fat_open_internal(IF_MV(newfile.volume,) newfile.firstcluster, &dir);
2208 fat_filestr_init(&dirstr, &dir);
2209
2210 /* obtain dot-dot directory entry */
2211 dc_lock_cache();
2212 ent = cache_direntry(fat_bpb, &dirstr, 1);
2213 if (!ent)
2028 { 2214 {
2029 fat_release_sector_buffer(); 2215 dc_unlock_cache();
2030 return rc * 10 - 7; 2216 FAT_ERROR(-7);
2031 } 2217 }
2032 2218
2033 rc = fat_readwrite(&dir, 1, buf, true); 2219 if (strncmp(".. ", ent->name, 11))
2034 if (rc < 1)
2035 { 2220 {
2036 fat_release_sector_buffer(); 2221 /* .. entry must be second entry according to FAT spec (p.29) */
2037 return rc * 10 - 8; 2222 DEBUGF("Second dir entry is not double-dot!\n");
2223 dc_unlock_cache();
2224 FAT_ERROR(-8);
2038 } 2225 }
2039 }
2040 fat_release_sector_buffer();
2041 2226
2042 return 0; 2227 /* parent cluster is 0 if parent dir is the root - FAT spec (p.29) */
2043} 2228 long parentcluster = 0;
2044 2229 if (parent->firstcluster != fat_bpb->bpb_rootclus)
2045int fat_remove(struct fat_file* file) 2230 parentcluster = parent->firstcluster;
2046{
2047 long next, last = file->firstcluster;
2048 int rc;
2049#ifdef HAVE_MULTIVOLUME
2050 struct bpb* fat_bpb = &fat_bpbs[file->volume];
2051#endif
2052 2231
2053 LDEBUGF("fat_remove(%lx)\n",last); 2232 raw_dirent_set_fstclus(ent, parentcluster);
2054 2233
2055 while ( last ) { 2234 dc_dirty_buf(ent);
2056 next = get_next_cluster(IF_MV(fat_bpb,) last); 2235 dc_unlock_cache();
2057 update_fat_entry(IF_MV(fat_bpb,) last,0);
2058 last = next;
2059 } 2236 }
2060 2237
2061 if ( file->dircluster ) { 2238 /* remove old name */
2062 rc = free_direntries(file); 2239 rc = free_direntries(fat_bpb, file);
2063 if (rc < 0) 2240 if (rc <= 0)
2064 return rc * 10 - 1; 2241 FAT_ERROR(rc * 10 - 9);
2065 }
2066 2242
2067 file->firstcluster = 0; 2243 /* finally, update old file with new directory entry info */
2068 file->dircluster = 0; 2244 *file = newfile;
2069 2245
2070 rc = flush_fat(IF_MV(fat_bpb)); 2246 rc = 0;
2071 if (rc < 0) 2247fat_error:
2072 return rc * 10 - 2; 2248 if (rc < 0 && !fat_file_is_same(&newfile, file))
2249 free_direntries(fat_bpb, &newfile);
2073 2250
2074 return 0; 2251 cache_commit(fat_bpb);
2252 return rc;
2075} 2253}
2076 2254
2077int fat_rename(struct fat_file* file,
2078 struct fat_dir* dir,
2079 const unsigned char* newname,
2080 long size,
2081 int attr)
2082{
2083 int rc;
2084 struct fat_file olddir_file;
2085 struct fat_file newfile = *file;
2086 unsigned char* entry = NULL;
2087 unsigned short* clusptr = NULL;
2088 unsigned int parentcluster;
2089#ifdef HAVE_MULTIVOLUME
2090 struct bpb* fat_bpb = &fat_bpbs[file->volume];
2091 2255
2092 if (file->volume != dir->file.volume) { 2256/** File stream functions **/
2093 DEBUGF("No rename across volumes!\n");
2094 return -1;
2095 }
2096#else
2097 struct bpb* fat_bpb = &fat_bpbs[0];
2098#endif
2099
2100 if ( !file->dircluster ) {
2101 DEBUGF("File has no dir cluster!\n");
2102 return -2;
2103 }
2104
2105 /* create new name */
2106 rc = add_dir_entry(dir, &newfile, newname, false, false);
2107 if (rc < 0)
2108 return rc * 10 - 2;
2109
2110 /* write size and cluster link */
2111 rc = update_short_entry(&newfile, size, attr);
2112 if (rc < 0)
2113 return rc * 10 - 3;
2114 2257
2115 /* remove old name */ 2258int fat_closewrite(struct fat_filestr *filestr, uint32_t size,
2116 rc = free_direntries(file); 2259 struct fat_direntry *fatentp)
2117 if (rc < 0) 2260{
2118 return rc * 10 - 4; 2261 DEBUGF("%s(size=%ld)\n", __func__, size);
2262 struct fat_file * const file = filestr->fatfilep;
2263 struct bpb * const fat_bpb = FAT_BPB(file->volume);
2264 if (!fat_bpb)
2265 return -1;
2119 2266
2120 rc = flush_fat(IF_MV(fat_bpb)); 2267 int rc;
2121 if (rc < 0)
2122 return rc * 10 - 5;
2123 2268
2124 /* if renaming a directory, update the .. entry to make sure 2269 if (!size && file->firstcluster)
2125 it points to its parent directory (we don't check if it was a move) */ 2270 {
2126 if(FAT_ATTR_DIRECTORY == attr) { 2271 /* empty file */
2127 /* open the dir that was renamed, we re-use the olddir_file struct */ 2272 rc = update_fat_entry(fat_bpb, file->firstcluster, 0);
2128 rc = fat_open(IF_MV(file->volume,) newfile.firstcluster, &olddir_file, NULL);
2129 if (rc < 0) 2273 if (rc < 0)
2130 return rc * 10 - 6; 2274 FAT_ERROR(rc * 10 - 2);
2131 2275
2132 /* get the first sector of the dir */ 2276 file->firstcluster = 0;
2133 rc = fat_seek(&olddir_file, 0); 2277 fat_rewind(filestr);
2134 if (rc < 0) 2278 }
2135 return rc * 10 - 7;
2136 2279
2137 unsigned char* buf = fat_get_sector_buffer(); 2280 if (file->dircluster)
2138 rc = fat_readwrite(&olddir_file, 1, buf, false); 2281 {
2282 rc = update_short_entry(fat_bpb, file, size, fatentp);
2139 if (rc < 0) 2283 if (rc < 0)
2140 { 2284 FAT_ERROR(rc * 10 - 3);
2141 fat_release_sector_buffer(); 2285 }
2142 return rc * 10 - 8; 2286 else if (fatentp)
2143 } 2287 {
2144 2288 fat_empty_fat_direntry(fatentp);
2145 /* parent cluster is 0 if parent dir is the root - FAT spec (p.29) */ 2289 }
2146 if(dir->file.firstcluster == fat_bpb->bpb_rootclus)
2147 parentcluster = 0;
2148 else
2149 parentcluster = dir->file.firstcluster;
2150 2290
2151 entry = buf + DIR_ENTRY_SIZE; 2291#ifdef TEST_FAT
2152 if(strncmp(".. ", entry, 11)) 2292 if (file->firstcluster)
2293 {
2294 unsigned long count = 0;
2295 for (long next = file->firstcluster; next;
2296 next = get_next_cluster(fat_bpb, next))
2153 { 2297 {
2154 fat_release_sector_buffer(); 2298 DEBUGF("cluster %ld: %lx\n", count, next);
2155 /* .. entry must be second entry according to FAT spec (p.29) */ 2299 count++;
2156 DEBUGF("Second dir entry is not double-dot!\n");
2157 return rc * 10 - 9;
2158 } 2300 }
2159 clusptr = (short*)(entry + FATDIR_FSTCLUSHI);
2160 *clusptr = htole16(parentcluster >> 16);
2161 2301
2162 clusptr = (short*)(entry + FATDIR_FSTCLUSLO); 2302 uint32_t len = count * fat_bpb->bpb_secperclus * SECTOR_SIZE;
2163 *clusptr = htole16(parentcluster & 0xffff); 2303 DEBUGF("File is %lu clusters (chainlen=%lu, size=%lu)\n",
2304 count, len, size );
2164 2305
2165 /* write back this sector */ 2306 if (len > size + fat_bpb->bpb_secperclus * SECTOR_SIZE)
2166 rc = fat_seek(&olddir_file, 0); 2307 panicf("Cluster chain is too long\n");
2167 if (rc < 0)
2168 {
2169 fat_release_sector_buffer();
2170 return rc * 10 - 7;
2171 }
2172 2308
2173 rc = fat_readwrite(&olddir_file, 1, buf, true); 2309 if (len < size)
2174 fat_release_sector_buffer(); 2310 panicf("Cluster chain is too short\n");
2175 if (rc < 1)
2176 return rc * 10 - 8;
2177 } 2311 }
2312#endif /* TEST_FAT */
2178 2313
2179 return 0; 2314 rc = 0;
2315fat_error:
2316 cache_commit(fat_bpb);
2317 return rc;
2180} 2318}
2181 2319
2182static long next_write_cluster(struct fat_file* file, 2320void fat_filestr_init(struct fat_filestr *fatstr, struct fat_file *file)
2183 long oldcluster,
2184 long* newsector)
2185{ 2321{
2186#ifdef HAVE_MULTIVOLUME 2322 fatstr->fatfilep = file;
2187 struct bpb* fat_bpb = &fat_bpbs[file->volume]; 2323 fat_rewind(fatstr);
2188#else 2324}
2189 struct bpb* fat_bpb = &fat_bpbs[0];
2190#endif
2191 long cluster = 0;
2192 long sector;
2193
2194 LDEBUGF("next_write_cluster(%lx,%lx)\n",file->firstcluster, oldcluster);
2195
2196 if (oldcluster)
2197 cluster = get_next_cluster(IF_MV(fat_bpb,) oldcluster);
2198
2199 if (!cluster) {
2200 if (oldcluster > 0)
2201 cluster = find_free_cluster(IF_MV(fat_bpb,) oldcluster+1);
2202 else if (oldcluster == 0)
2203 cluster = find_free_cluster(IF_MV(fat_bpb,)
2204 fat_bpb->fsinfo.nextfree);
2205#ifdef HAVE_FAT16SUPPORT
2206 else /* negative, pseudo-cluster of the root dir */
2207 return 0; /* impossible to append something to the root */
2208#endif
2209 2325
2210 if (cluster) { 2326unsigned long fat_query_sectornum(const struct fat_filestr *filestr)
2211 if (oldcluster) 2327{
2212 update_fat_entry(IF_MV(fat_bpb,) oldcluster, cluster); 2328 /* return next sector number to be transferred */
2213 else 2329 struct bpb * const fat_bpb = FAT_BPB(filestr->fatfilep->volume);
2214 file->firstcluster = cluster; 2330 if (!fat_bpb)
2215 update_fat_entry(IF_MV(fat_bpb,) cluster, FAT_EOF_MARK); 2331 return INVALID_SECNUM;
2216 }
2217 else {
2218#ifdef TEST_FAT
2219 if (fat_bpb->fsinfo.freecount>0)
2220 panicf("There is free space, but find_free_cluster() "
2221 "didn't find it!\n");
2222#endif
2223 DEBUGF("next_write_cluster(): Disk full!\n");
2224 return 0;
2225 }
2226 }
2227 sector = cluster2sec(IF_MV(fat_bpb,) cluster);
2228 if (sector<0)
2229 return 0;
2230 2332
2231 *newsector = sector; 2333 return fat_bpb->bpb_secperclus*filestr->clusternum + filestr->sectornum + 1;
2232 return cluster;
2233} 2334}
2234 2335
2235static int transfer(IF_MV(struct bpb* fat_bpb,) 2336/* helper for fat_readwrite */
2236 unsigned long start, long count, char* buf, bool write ) 2337static long transfer(struct bpb *fat_bpb, unsigned long start, long count,
2338 char *buf, bool write)
2237{ 2339{
2238#ifndef HAVE_MULTIVOLUME 2340 long rc = 0;
2239 struct bpb* fat_bpb = &fat_bpbs[0];
2240#endif
2241 int rc;
2242 2341
2243 LDEBUGF("transfer(s=%lx, c=%lx, %s)\n", 2342 DEBUGF("%s(s=%lx, c=%lx, wr=%u)\n", __func__,
2244 start+ fat_bpb->startsector, count, write?"write":"read"); 2343 start + fat_bpb->startsector, count, write ? 1 : 0);
2245 if (write) { 2344
2345 if (write)
2346 {
2246 unsigned long firstallowed; 2347 unsigned long firstallowed;
2247#ifdef HAVE_FAT16SUPPORT 2348#ifdef HAVE_FAT16SUPPORT
2248 if (fat_bpb->is_fat16) 2349 if (fat_bpb->is_fat16)
2249 firstallowed = fat_bpb->rootdirsector; 2350 firstallowed = fat_bpb->rootdirsector;
2250 else 2351 else
2251#endif 2352#endif /* HAVE_FAT16SUPPORT */
2252 firstallowed = fat_bpb->firstdatasector; 2353 firstallowed = fat_bpb->firstdatasector;
2253 2354
2254 if (start < firstallowed) 2355 if (start < firstallowed)
2255 panicf("Write %ld before data\n", firstallowed - start); 2356 panicf("Write %ld before data\n", firstallowed - start);
2357
2256 if (start + count > fat_bpb->totalsectors) 2358 if (start + count > fat_bpb->totalsectors)
2359 {
2257 panicf("Write %ld after data\n", 2360 panicf("Write %ld after data\n",
2258 start + count - fat_bpb->totalsectors); 2361 start + count - fat_bpb->totalsectors);
2259 rc = storage_write_sectors(IF_MD(fat_bpb->drive,) 2362 }
2260 start + fat_bpb->startsector, count, buf); 2363 else
2364 {
2365 rc = storage_write_sectors(IF_MD(fat_bpb->drive,)
2366 start + fat_bpb->startsector, count, buf);
2367 }
2261 } 2368 }
2262 else 2369 else
2370 {
2263 rc = storage_read_sectors(IF_MD(fat_bpb->drive,) 2371 rc = storage_read_sectors(IF_MD(fat_bpb->drive,)
2264 start + fat_bpb->startsector, count, buf); 2372 start + fat_bpb->startsector, count, buf);
2265 if (rc < 0) { 2373 }
2266 DEBUGF( "transfer() - Couldn't %s sector %lx" 2374
2267 " (error code %d)\n", 2375 if (rc < 0)
2268 write ? "write":"read", start, rc); 2376 {
2377 DEBUGF("Couldn't %s sector %lx (err %d)\n",
2378 write ? "write":"read", start, rc);
2269 return rc; 2379 return rc;
2270 } 2380 }
2381
2271 return 0; 2382 return 0;
2272} 2383}
2273 2384
2274 2385long fat_readwrite(struct fat_filestr *filestr, unsigned long sectorcount,
2275long fat_readwrite( struct fat_file *file, long sectorcount, 2386 void *buf, bool write)
2276 void* buf, bool write )
2277{ 2387{
2278#ifdef HAVE_MULTIVOLUME 2388 struct fat_file * const file = filestr->fatfilep;
2279 struct bpb* fat_bpb = &fat_bpbs[file->volume]; 2389 struct bpb * const fat_bpb = FAT_BPB(file->volume);
2280#else 2390 if (!fat_bpb)
2281 struct bpb* fat_bpb = &fat_bpbs[0]; 2391 return -1;
2282#endif
2283 long cluster = file->lastcluster;
2284 long sector = file->lastsector;
2285 long clusternum = file->clusternum;
2286 long numsec = file->sectornum;
2287 bool eof = file->eof;
2288 long first=0, last=0;
2289 long i;
2290 int rc;
2291 2392
2292 LDEBUGF( "fat_readwrite(file:%lx,count:0x%lx,buf:%lx,%s)\n", 2393 bool eof = filestr->eof;
2293 file->firstcluster,sectorcount,(long)buf,write?"write":"read");
2294 LDEBUGF( "fat_readwrite: sec=%lx numsec=%ld eof=%d\n",
2295 sector,numsec, eof?1:0);
2296 2394
2297 if ( eof && !write) 2395 if ((eof && !write) || !sectorcount)
2298 return 0; 2396 return 0;
2299 2397
2300 /* find sequential sectors and write them all at once */ 2398 long rc;
2301 for (i=0; (i < sectorcount) && (sector > -1); i++ ) {
2302 numsec++;
2303 if ( numsec > (long)fat_bpb->bpb_secperclus || !cluster ) {
2304 long oldcluster = cluster;
2305 long oldsector = sector;
2306 long oldnumsec = numsec;
2307 if (write)
2308 cluster = next_write_cluster(file, cluster, &sector);
2309 else {
2310 cluster = get_next_cluster(IF_MV(fat_bpb,) cluster);
2311 sector = cluster2sec(IF_MV(fat_bpb,) cluster);
2312 }
2313 2399
2314 clusternum++; 2400 long cluster = filestr->lastcluster;
2315 numsec=1; 2401 unsigned long sector = filestr->lastsector;
2402 long clusternum = filestr->clusternum;
2403 unsigned long sectornum = filestr->sectornum;
2316 2404
2317 if (!cluster) { 2405 DEBUGF("%s(file:%lx,count:0x%lx,buf:%lx,%s)\n", __func__,
2318 eof = true; 2406 file->firstcluster, sectorcount, (long)buf,
2319 if ( write ) { 2407 write ? "write":"read");
2320 /* remember last cluster, in case 2408 DEBUGF("%s: sec:%lx numsec:%ld eof:%d\n", __func__,
2321 we want to append to the file */ 2409 sector, (long)sectornum, eof ? 1 : 0);
2322 sector = oldsector; 2410
2323 cluster = oldcluster; 2411 eof = false;
2324 numsec = oldnumsec; 2412
2325 clusternum--; 2413 if (!sector)
2326 i = -1; /* Error code */ 2414 {
2327 break; 2415 /* look up first sector of file */
2328 } 2416 long newcluster = file->firstcluster;
2329 } 2417
2330 else 2418 if (write && !newcluster)
2331 eof = false; 2419 {
2420 /* file is empty; try to allocate its first cluster */
2421 newcluster = next_write_cluster(fat_bpb, 0);
2422 file->firstcluster = newcluster;
2332 } 2423 }
2333 else { 2424
2334 if (sector) 2425 if (newcluster)
2335 sector++; 2426 {
2336 else { 2427 cluster = newcluster;
2337 /* look up first sector of file */ 2428 sector = cluster2sec(fat_bpb, cluster) - 1;
2338 sector = cluster2sec(IF_MV(fat_bpb,) file->firstcluster); 2429
2339 numsec=1; 2430 #ifdef HAVE_FAT16SUPPORT
2340#ifdef HAVE_FAT16SUPPORT 2431 if (fat_bpb->is_fat16 && file->firstcluster < 0)
2341 if (file->firstcluster < 0) 2432 {
2342 { /* FAT16 root dir */ 2433 sector += fat_bpb->rootdirsectornum;
2343 sector += fat_bpb->rootdiroffset; 2434 sectornum = fat_bpb->rootdirsectornum;
2344 numsec += fat_bpb->rootdiroffset;
2345 }
2346#endif
2347 } 2435 }
2436 #endif /* HAVE_FAT16SUPPORT */
2348 } 2437 }
2438 }
2349 2439
2350 if (!first) 2440 if (!sector)
2351 first = sector; 2441 {
2442 sectorcount = 0;
2443 eof = true;
2444 }
2352 2445
2353 if ( ((sector != first) && (sector != last+1)) || /* not sequential */ 2446 unsigned long transferred = 0;
2354 (last-first+1 == 256) ) { /* max 256 sectors per ata request */ 2447 unsigned long count = 0;
2355 long count = last - first + 1; 2448 unsigned long last = sector;
2356 rc = transfer(IF_MV(fat_bpb,) first, count, buf, write );
2357 if (rc < 0)
2358 return rc * 10 - 1;
2359 2449
2360 buf = (char *)buf + count * SECTOR_SIZE; 2450 while (transferred + count < sectorcount)
2361 first = sector; 2451 {
2452 if (++sectornum >= fat_bpb->bpb_secperclus)
2453 {
2454 /* out of sectors in this cluster; get the next cluster */
2455 long newcluster = write ? next_write_cluster(fat_bpb, cluster) :
2456 get_next_cluster(fat_bpb, cluster);
2457 if (newcluster)
2458 {
2459 cluster = newcluster;
2460 sector = cluster2sec(fat_bpb, cluster) - 1;
2461 clusternum++;
2462 sectornum = 0;
2463
2464 /* jumped clusters right at start? */
2465 if (!count)
2466 last = sector;
2467 }
2468 else
2469 {
2470 sectornum--; /* remain in previous position */
2471 eof = true;
2472 break;
2473 }
2362 } 2474 }
2363 2475
2364 if ((i == sectorcount-1) && /* last sector requested */ 2476 /* find sequential sectors and transfer them all at once */
2365 (!eof)) 2477 if (sector != last || count >= FAT_MAX_TRANSFER_SIZE)
2366 { 2478 {
2367 long count = sector - first + 1; 2479 /* not sequential/over limit */
2368 rc = transfer(IF_MV(fat_bpb,) first, count, buf, write ); 2480 rc = transfer(fat_bpb, last - count + 1, count, buf, write);
2369 if (rc < 0) 2481 if (rc < 0)
2370 return rc * 10 - 2; 2482 FAT_ERROR(rc * 10 - 2);
2483
2484 transferred += count;
2485 buf += count * SECTOR_SIZE;
2486 count = 0;
2371 } 2487 }
2372 2488
2373 last = sector; 2489 count++;
2490 last = ++sector;
2374 } 2491 }
2375 2492
2376 file->lastcluster = cluster; 2493 if (count)
2377 file->lastsector = sector; 2494 {
2378 file->clusternum = clusternum; 2495 /* transfer any remainder */
2379 file->sectornum = numsec; 2496 rc = transfer(fat_bpb, last - count + 1, count, buf, write);
2380 file->eof = eof; 2497 if (rc < 0)
2498 FAT_ERROR(rc * 10 - 3);
2499
2500 transferred += count;
2501 }
2502
2503 rc = (eof && write) ? FAT_RC_ENOSPC : (long)transferred;
2504fat_error:
2505 filestr->lastcluster = cluster;
2506 filestr->lastsector = sector;
2507 filestr->clusternum = clusternum;
2508 filestr->sectornum = sectornum;
2509 filestr->eof = eof;
2510
2511 if (rc >= 0)
2512 DEBUGF("Sectors transferred: %ld\n", rc);
2381 2513
2382 /* if eof, don't report last block as read/written */ 2514 return rc;
2383 if (eof) 2515}
2384 i--;
2385 2516
2386 DEBUGF("Sectors written: %ld\n", i); 2517void fat_rewind(struct fat_filestr *filestr)
2387 return i; 2518{
2519 /* rewind the file position */
2520 filestr->lastcluster = filestr->fatfilep->firstcluster;
2521 filestr->lastsector = 0;
2522 filestr->clusternum = 0;
2523 filestr->sectornum = FAT_RW_VAL;
2524 filestr->eof = false;
2388} 2525}
2389 2526
2390int fat_seek(struct fat_file *file, unsigned long seeksector ) 2527int fat_seek(struct fat_filestr *filestr, unsigned long seeksector)
2391{ 2528{
2392#ifdef HAVE_MULTIVOLUME 2529 const struct fat_file * const file = filestr->fatfilep;
2393 struct bpb* fat_bpb = &fat_bpbs[file->volume]; 2530 struct bpb * const fat_bpb = FAT_BPB(file->volume);
2394#else 2531 if (!fat_bpb)
2395 struct bpb* fat_bpb = &fat_bpbs[0]; 2532 return -1;
2396#endif 2533
2397 long clusternum=0, numclusters=0, sectornum=0, sector=0; 2534 int rc;
2398 long cluster = file->firstcluster; 2535 long cluster = file->firstcluster;
2399 long i; 2536 unsigned long sector = 0;
2537 long clusternum = 0;
2538 unsigned long sectornum = FAT_RW_VAL;
2400 2539
2401#ifdef HAVE_FAT16SUPPORT 2540#ifdef HAVE_FAT16SUPPORT
2402 if (cluster < 0) /* FAT16 root dir */ 2541 if (fat_bpb->is_fat16 && cluster < 0) /* FAT16 root dir */
2403 seeksector += fat_bpb->rootdiroffset; 2542 seeksector += fat_bpb->rootdirsectornum;
2404#endif 2543#endif /* HAVE_FAT16SUPPORT */
2544
2545 filestr->eof = false;
2546 if (seeksector)
2547 {
2548 if (cluster == 0)
2549 {
2550 DEBUGF("Seeking beyond the end of empty file! "
2551 "(sector %lu, cluster %ld)\n", seeksector,
2552 seeksector / fat_bpb->bpb_secperclus);
2553 FAT_ERROR(FAT_SEEK_EOF);
2554 }
2405 2555
2406 file->eof = false;
2407 if (seeksector) {
2408 /* we need to find the sector BEFORE the requested, since 2556 /* we need to find the sector BEFORE the requested, since
2409 the file struct stores the last accessed sector */ 2557 the file struct stores the last accessed sector */
2410 seeksector--; 2558 seeksector--;
2411 numclusters = clusternum = seeksector / fat_bpb->bpb_secperclus; 2559 clusternum = seeksector / fat_bpb->bpb_secperclus;
2412 sectornum = seeksector % fat_bpb->bpb_secperclus; 2560 sectornum = seeksector % fat_bpb->bpb_secperclus;
2413 2561
2414 if (file->clusternum && clusternum >= file->clusternum) 2562 long numclusters = clusternum;
2563
2564 if (filestr->clusternum && clusternum >= filestr->clusternum)
2415 { 2565 {
2416 cluster = file->lastcluster; 2566 /* seek forward from current position */
2417 numclusters -= file->clusternum; 2567 cluster = filestr->lastcluster;
2568 numclusters -= filestr->clusternum;
2418 } 2569 }
2419 2570
2420 for (i=0; i<numclusters; i++) { 2571 for (long i = 0; i < numclusters; i++)
2421 cluster = get_next_cluster(IF_MV(fat_bpb,) cluster); 2572 {
2422 if (!cluster) { 2573 cluster = get_next_cluster(fat_bpb, cluster);
2574
2575 if (!cluster)
2576 {
2423 DEBUGF("Seeking beyond the end of the file! " 2577 DEBUGF("Seeking beyond the end of the file! "
2424 "(sector %ld, cluster %ld)\n", seeksector, i); 2578 "(sector %lu, cluster %ld)\n", seeksector, i);
2425 return -1; 2579 FAT_ERROR(FAT_SEEK_EOF);
2426 } 2580 }
2427 } 2581 }
2428 2582
2429 sector = cluster2sec(IF_MV(fat_bpb,) cluster) + sectornum; 2583 sector = cluster2sec(fat_bpb, cluster) + sectornum;
2430 }
2431 else {
2432 sectornum = -1;
2433 } 2584 }
2434 2585
2435 LDEBUGF("fat_seek(%lx, %lx) == %lx, %lx, %lx\n", 2586 DEBUGF("%s(%lx, %lx) == %lx, %lx, %lx\n", __func__,
2436 file->firstcluster, seeksector, cluster, sector, sectornum); 2587 file->firstcluster, seeksector, cluster, sector, sectornum);
2437 2588
2438 file->lastcluster = cluster; 2589 filestr->lastcluster = cluster;
2439 file->lastsector = sector; 2590 filestr->lastsector = sector;
2440 file->clusternum = clusternum; 2591 filestr->clusternum = clusternum;
2441 file->sectornum = sectornum + 1; 2592 filestr->sectornum = sectornum;
2442 return 0; 2593
2594 rc = 0;
2595fat_error:
2596 return rc;
2443} 2597}
2444 2598
2445int fat_opendir(IF_MV(int volume,) 2599int fat_truncate(const struct fat_filestr *filestr)
2446 struct fat_dir *dir, unsigned long startcluster,
2447 const struct fat_dir *parent_dir)
2448{ 2600{
2449#ifdef HAVE_MULTIVOLUME 2601 DEBUGF("%s(): %lX\n", __func__, filestr->lastcluster);
2450 struct bpb* fat_bpb = &fat_bpbs[volume]; 2602
2451 /* fixme: remove error check when done */ 2603 struct bpb * const fat_bpb = FAT_BPB(filestr->fatfilep->volume);
2452 if (volume >= NUM_VOLUMES || !fat_bpbs[volume].mounted) 2604 if (!fat_bpb)
2453 {
2454 LDEBUGF("fat_open() illegal volume %d\n", volume);
2455 return -1; 2605 return -1;
2456 }
2457#else
2458 struct bpb* fat_bpb = &fat_bpbs[0];
2459#endif
2460 int rc;
2461 2606
2462 if (startcluster == 0) 2607 int rc = 1;
2463 startcluster = fat_bpb->bpb_rootclus;
2464 2608
2465 rc = fat_open(IF_MV(volume,) startcluster, &dir->file, parent_dir); 2609 long last = filestr->lastcluster;
2466 if(rc) 2610 long next = 0;
2611
2612 /* truncate trailing clusters after the current position */
2613 if (last)
2467 { 2614 {
2468 DEBUGF( "fat_opendir() - Couldn't open dir" 2615 next = get_next_cluster(fat_bpb, last);
2469 " (error code %d)\n", rc); 2616 int rc2 = update_fat_entry(fat_bpb, last, FAT_EOF_MARK);
2470 return rc * 10 - 1; 2617 if (rc2 < 0)
2618 FAT_ERROR(rc2 * 10 - 2);
2471 } 2619 }
2472
2473 /* assign them after fat_open call so that fat_opendir can be called with the same
2474 * fat_dir as parent and result */
2475 dir->entry = 0;
2476 dir->sector = 0;
2477 2620
2478 return 0; 2621 int rc2 = free_cluster_chain(fat_bpb, next);
2622 if (rc2 <= 0)
2623 {
2624 DEBUGF("Failed freeing cluster chain\n");
2625 rc = 0; /* still partial success */
2626 }
2627
2628fat_error:
2629 return rc;
2479} 2630}
2480 2631
2481int fat_getnext(struct fat_dir *dir, struct fat_direntry *entry) 2632
2633/** Directory stream functions **/
2634
2635int fat_readdir(struct fat_filestr *dirstr, struct fat_dirscan_info *scan,
2636 struct filestr_cache *cachep, struct fat_direntry *entry)
2482{ 2637{
2483 bool done = false; 2638 int rc = 0;
2484 int i, j; 2639
2485 int rc; 2640 /* long file names are stored in special entries; each entry holds up to
2486 int order; 2641 13 UTF-16 characters' thus, UTF-8 converted names can be max 255 chars
2487 unsigned char firstbyte; 2642 (1020 bytes) long, not including the trailing '\0'. */
2488 /* Long file names are stored in special entries. Each entry holds 2643 struct fatlong_parse_state lnparse;
2489 up to 13 characters. Names can be max 255 chars (not bytes!) long */ 2644 fatlong_parse_start(&lnparse);
2490 /* The number of long entries in the long name can be retrieve from the first
2491 * long entry because there are stored in reverse order and have an ordinal */
2492 int nb_longs = 0;
2493 /* The long entries are expected to be in order, so remember the last ordinal */
2494 int last_long_ord = 0;
2495 2645
2496 dir->entrycount = 0; 2646 scan->entries = 0;
2497 2647
2498 while(!done) 2648 while (1)
2499 { 2649 {
2500 if ( !(dir->entry % DIR_ENTRIES_PER_SECTOR) || !dir->sector ) 2650 unsigned int direntry = ++scan->entry;
2651 if (direntry >= MAX_DIRENTRIES)
2501 { 2652 {
2502 rc = fat_readwrite(&dir->file, 1, dir->sectorcache, false); 2653 DEBUGF("%s() - Dir is too large (entry %u)\n", __func__,
2503 if (rc == 0) { 2654 direntry);
2504 /* eof */ 2655 FAT_ERROR(-1);
2505 entry->name[0] = 0; 2656 }
2506 break; 2657
2658 unsigned long sector = direntry / DIR_ENTRIES_PER_SECTOR;
2659 if (cachep->sector != sector)
2660 {
2661 if (cachep->sector + 1 != sector)
2662 {
2663 /* Nothing cached or sector isn't contiguous */
2664 int rc2 = fat_seek(dirstr, sector);
2665 if (rc2 < 0)
2666 FAT_ERROR(rc2 * 10 - 2);
2507 } 2667 }
2508 if (rc < 0) { 2668
2509 DEBUGF( "fat_getnext() - Couldn't read dir" 2669 int rc2 = fat_readwrite(dirstr, 1, cachep->buffer, false);
2510 " (error code %d)\n", rc); 2670 if (rc2 <= 0)
2511 return rc * 10 - 1; 2671 {
2672 if (rc2 == 0)
2673 break; /* eof */
2674
2675 DEBUGF("%s() - Couldn't read dir (err %d)\n", __func__, rc);
2676 FAT_ERROR(rc2 * 10 - 3);
2512 } 2677 }
2513 dir->sector = dir->file.lastsector; 2678
2679 cachep->sector = sector;
2514 } 2680 }
2515 2681
2516 for (i = dir->entry % DIR_ENTRIES_PER_SECTOR; 2682 unsigned int index = direntry % DIR_ENTRIES_PER_SECTOR;
2517 i < DIR_ENTRIES_PER_SECTOR; i++) { 2683 union raw_dirent *ent = &((union raw_dirent *)cachep->buffer)[index];
2518 unsigned int entrypos = i * DIR_ENTRY_SIZE;
2519 2684
2520 firstbyte = dir->sectorcache[entrypos]; 2685 if (ent->name[0] == 0)
2521 dir->entry++; 2686 break; /* last entry */
2522 2687
2523 if (firstbyte == 0xe5) { 2688 if (ent->name[0] == 0xe5)
2524 /* free entry */ 2689 {
2525 dir->entrycount = 0; 2690 scan->entries = 0;
2526 continue; 2691 continue; /* free entry */
2527 } 2692 }
2528 2693
2529 if (firstbyte == 0) { 2694 ++scan->entries;
2530 /* last entry */
2531 entry->name[0] = 0;
2532 dir->entrycount = 0;
2533 return 0;
2534 }
2535 2695
2536 dir->entrycount++; 2696 if (IS_LDIR_ATTR(ent->ldir_attr))
2537 2697 {
2538 /* LFN entry? */ 2698 /* LFN entry */
2539 if ( ( dir->sectorcache[entrypos + FATDIR_ATTR] & 2699 if (UNLIKELY(!fatlong_parse_entry(&lnparse, ent, entry)))
2540 FAT_ATTR_LONG_NAME_MASK ) == FAT_ATTR_LONG_NAME ) { 2700 {
2541 /* extract ordinal */ 2701 /* resync so we don't return just the short name if what we
2542 order = dir->sectorcache[entrypos + FATLONG_ORDER] & ~FATLONG_LAST_LONG_ENTRY; 2702 landed in the middle of is valid (directory changes
2543 /* is this entry the first long entry ? (first in order but containing last part) */ 2703 between calls likely created the situation; ignoring this
2544 if (dir->sectorcache[entrypos + FATLONG_ORDER] & FATLONG_LAST_LONG_ENTRY) { 2704 case can be harmful elsewhere and is destructive to the
2545 /* check that order is not too big ! (and non-zero) */ 2705 entry series itself) */
2546 if(order <= 0 || order > FATLONG_MAX_ORDER) 2706 struct bpb *fat_bpb = FAT_BPB(dirstr->fatfilep->volume);
2547 continue; /* ignore the whole LFN, will trigger lots of warnings */ 2707 if (!fat_bpb)
2548 nb_longs = order; 2708 FAT_ERROR(-4);
2549 last_long_ord = order; 2709
2550 } 2710 dc_lock_cache();
2551 else { 2711
2552 /* check orphan entry */ 2712 while (--scan->entry != FAT_RW_VAL) /* at beginning? */
2553 if (nb_longs == 0) { 2713 {
2554 logf("fat warning: orphan LFN entry"); 2714 ent = cache_direntry(fat_bpb, dirstr, scan->entry);
2555 /* ignore */
2556 continue;
2557 }
2558
2559 /* check order */
2560 if (order != (last_long_ord - 1)) {
2561 logf("fat warning: wrong LFN ordinal");
2562 /* ignore the whole LFN, will trigger lots of warnings */
2563 nb_longs = 0;
2564 }
2565
2566 last_long_ord = order;
2567 }
2568 2715
2569 /* copy part, reuse [order] for another purpose :) */ 2716 /* name[0] == 0 shouldn't happen here but... */
2570 order = (order - 1) * FATLONG_NAME_BYTES_PER_ENTRY; 2717 if (!ent || ent->name[0] == 0 || ent->name[0] == 0xe5 ||
2571 for(j = 0; j < FATLONG_NAME_CHUNKS; j++) { 2718 !IS_LDIR_ATTR(ent->ldir_attr))
2572 memcpy(dir->longname + order, 2719 break;
2573 dir->sectorcache + entrypos + FATLONG_NAME_POS[j],
2574 FATLONG_NAME_SIZE[j]);
2575 order += FATLONG_NAME_SIZE[j];
2576 } 2720 }
2721
2722 dc_unlock_cache();
2723
2724 /* retry it once from the new position */
2725 scan->entries = 0;
2726 continue;
2577 } 2727 }
2578 else { 2728 }
2579 if ( parse_direntry(entry, dir->sectorcache + entrypos) ) { 2729 else if (!IS_VOL_ID_ATTR(ent->attr)) /* ignore volume id entry */
2580 2730 {
2581 /* don't return volume id entry */ 2731 rc = 1;
2582 if ( (entry->attr & 2732
2583 (FAT_ATTR_VOLUME_ID|FAT_ATTR_DIRECTORY)) 2733 if (!fatlong_parse_finish(&lnparse, ent, entry))
2584 == FAT_ATTR_VOLUME_ID) 2734 {
2585 continue; 2735 /* the long entries failed to pass all checks or there is
2586 2736 just a short entry. */
2587 /* replace shortname with longname? */ 2737 DEBUGF("SN-DOS:'%s'", entry->shortname);
2588 /* check that the long name is complete */ 2738 strcpy(entry->name, entry->shortname);
2589 if (nb_longs != 0 && last_long_ord == 1) { 2739 scan->entries = 1;
2590 /* hold a copy of the shortname in case the long one is too long */ 2740 rc = 2; /* name is OEM */
2591 unsigned char shortname[13]; /* 8+3+dot+\0 */
2592 int longname_utf8len = 0;
2593 /* One character at a time, add 1 for trailing \0, 4 is the maximum size
2594 * of a UTF8 encoded character in rockbox */
2595 unsigned char longname_utf8segm[4 + 1];
2596 unsigned short ucs;
2597 int segm_utf8len;
2598 /* Temporarily store short name */
2599 strcpy(shortname, entry->name);
2600 entry->name[0] = 0;
2601
2602 /* Convert the FAT name to a utf8-encoded one.
2603 * The name is not necessary NUL-terminated ! */
2604 for (j = 0; j < nb_longs * FATLONG_NAME_BYTES_PER_ENTRY; j += 2) {
2605 ucs = dir->longname[j] | (dir->longname[j + 1] << 8);
2606 if(ucs == 0 || ucs == FAT_LONGNAME_PAD_UCS)
2607 break;
2608 /* utf8encode will return a pointer after the converted
2609 * string, subtract the pointer to the start to get the length of it */
2610 segm_utf8len = utf8encode(ucs, longname_utf8segm) - longname_utf8segm;
2611
2612 /* warn the trailing zero ! (FAT_FILENAME_BYTES includes it) */
2613 if (longname_utf8len + segm_utf8len >= FAT_FILENAME_BYTES) {
2614 /* force use of short name */
2615 longname_utf8len = FAT_FILENAME_BYTES + 1;
2616 break; /* fallback later */
2617 }
2618 else {
2619 longname_utf8segm[segm_utf8len] = 0;
2620 strcat(entry->name + longname_utf8len, longname_utf8segm);
2621 longname_utf8len += segm_utf8len;
2622 }
2623 }
2624
2625 /* Does the utf8-encoded name fit into the entry? */
2626 /* warn the trailing zero ! (FAT_FILENAME_BYTES includes it) */
2627 if (longname_utf8len >= FAT_FILENAME_BYTES) {
2628 /* Take the short DOS name. Need to utf8-encode it
2629 since it may contain chars from the upper half of
2630 the OEM code page which wouldn't be a valid utf8.
2631 Beware: this file will be shown with strange
2632 glyphs in file browser since unicode 0x80 to 0x9F
2633 are control characters. */
2634 logf("SN-DOS: %s", shortname);
2635 unsigned char *utf8;
2636 utf8 = iso_decode(shortname, entry->name, -1,
2637 strlen(shortname));
2638 *utf8 = 0;
2639 logf("SN: %s", entry->name);
2640 } else {
2641 logf("LN: %s", entry->name);
2642 logf("LNLen: %d", longname_utf8len);
2643 }
2644 }
2645 done = true;
2646 i++;
2647 break;
2648 }
2649 } 2741 }
2742
2743 DEBUGF("LN:\"%s\"", entry->name);
2744 break;
2650 } 2745 }
2746 } /* end while */
2747
2748fat_error:
2749 if (rc <= 0)
2750 {
2751 /* error or eod; stay on last good position */
2752 fat_empty_fat_direntry(entry);
2753 scan->entry--;
2754 scan->entries = 0;
2651 } 2755 }
2756
2757 return rc;
2758}
2759
2760void fat_rewinddir(struct fat_dirscan_info *scan)
2761{
2762 /* rewind the directory scan counter to the beginning */
2763 scan->entry = FAT_RW_VAL;
2764 scan->entries = 0;
2765}
2766
2767
2768/** Mounting and unmounting functions **/
2769
2770bool fat_ismounted(IF_MV_NONVOID(int volume))
2771{
2772 return !!FAT_BPB(volume);
2773}
2774
2775int fat_mount(IF_MV(int volume,) IF_MD(int drive,) unsigned long startsector)
2776{
2777 int rc;
2778
2779 struct bpb * const fat_bpb = &fat_bpbs[IF_MV_VOL(volume)];
2780 if (fat_bpb->mounted)
2781 FAT_ERROR(-1); /* double mount */
2782
2783 /* fill-in basic info first */
2784 fat_bpb->startsector = startsector;
2785#ifdef HAVE_MULTIVOLUME
2786 fat_bpb->volume = volume;
2787#endif
2788#ifdef HAVE_MULTIDRIVE
2789 fat_bpb->drive = drive;
2790#endif
2791
2792 rc = fat_mount_internal(fat_bpb);
2793 if (rc < 0)
2794 FAT_ERROR(rc * 10 - 2);
2795
2796 /* it worked */
2797 fat_bpb->mounted = true;
2798
2799 /* calculate freecount if unset */
2800 if (fat_bpb->fsinfo.freecount == 0xffffffff)
2801 fat_recalc_free(IF_MV(fat_bpb->volume));
2802
2803 DEBUGF("Freecount: %ld\n", (unsigned long)fat_bpb->fsinfo.freecount);
2804 DEBUGF("Nextfree: 0x%lx\n", (unsigned long)fat_bpb->fsinfo.nextfree);
2805 DEBUGF("Cluster count: 0x%lx\n", fat_bpb->dataclusters);
2806 DEBUGF("Sectors per cluster: %d\n", fat_bpb->bpb_secperclus);
2807 DEBUGF("FAT sectors: 0x%lx\n", fat_bpb->fatsize);
2808
2809 rc = 0;
2810fat_error:
2811 return rc;
2812}
2813
2814int fat_unmount(IF_MV_NONVOID(int volume))
2815{
2816 struct bpb * const fat_bpb = FAT_BPB(volume);
2817 if (!fat_bpb)
2818 return -1; /* not mounted */
2819
2820 /* free the entries for this volume */
2821 cache_discard(IF_MV(fat_bpb));
2822 fat_bpb->mounted = false;
2823
2652 return 0; 2824 return 0;
2653} 2825}
2654 2826
2827
2828/** Debug screen stuff **/
2829
2830#ifdef MAX_LOG_SECTOR_SIZE
2831int fat_get_bytes_per_sector(IF_MV_NONVOID(int volume))
2832{
2833 int bytes = 0;
2834
2835 struct bpb * const fat_bpb = FAT_BPB(volume);
2836 if (fat_bpb)
2837 bytes = fat_bpb->bpb_bytspersec;
2838
2839 return bytes;
2840}
2841#endif /* MAX_LOG_SECTOR_SIZE */
2842
2655unsigned int fat_get_cluster_size(IF_MV_NONVOID(int volume)) 2843unsigned int fat_get_cluster_size(IF_MV_NONVOID(int volume))
2656{ 2844{
2657#ifndef HAVE_MULTIVOLUME 2845 unsigned int size = 0;
2658 const int volume = 0; 2846
2659#endif 2847 struct bpb * const fat_bpb = FAT_BPB(volume);
2660 struct bpb* fat_bpb = &fat_bpbs[volume]; 2848 if (fat_bpb)
2661 return fat_bpb->bpb_secperclus * SECTOR_SIZE; 2849 size = fat_bpb->bpb_secperclus * SECTOR_SIZE;
2850
2851 return size;
2662} 2852}
2663 2853
2664#ifdef HAVE_MULTIVOLUME 2854void fat_recalc_free(IF_MV_NONVOID(int volume))
2665bool fat_ismounted(int volume)
2666{ 2855{
2667 return (volume<NUM_VOLUMES && fat_bpbs[volume].mounted); 2856 struct bpb * const fat_bpb = FAT_BPB(volume);
2857 if (!fat_bpb)
2858 return;
2859
2860 dc_lock_cache();
2861 fat_recalc_free_internal(fat_bpb);
2862 dc_unlock_cache();
2863}
2864
2865bool fat_size(IF_MV(int volume,) unsigned long *size, unsigned long *free)
2866{
2867 struct bpb * const fat_bpb = FAT_BPB(volume);
2868 if (!fat_bpb)
2869 return false;
2870
2871 unsigned long factor = fat_bpb->bpb_secperclus * SECTOR_SIZE / 1024;
2872
2873 if (size) *size = fat_bpb->dataclusters * factor;
2874 if (free) *free = fat_bpb->fsinfo.freecount * factor;
2875
2876 return true;
2877}
2878
2879
2880/** Misc. **/
2881
2882void fat_empty_fat_direntry(struct fat_direntry *entry)
2883{
2884 entry->name[0] = 0;
2885 entry->shortname[0] = 0;
2886 entry->attr = 0;
2887 entry->crttimetenth = 0;
2888 entry->crttime = 0;
2889 entry->crtdate = 0;
2890 entry->lstaccdate = 0;
2891 entry->wrttime = 0;
2892 entry->wrtdate = 0;
2893 entry->filesize = 0;
2894 entry->firstcluster = 0;
2895}
2896
2897time_t fattime_mktime(uint16_t fatdate, uint16_t fattime)
2898{
2899 /* this knows our mktime() only uses these struct tm fields */
2900 struct tm tm;
2901 tm.tm_sec = ((fattime ) & 0x1f) * 2;
2902 tm.tm_min = ((fattime >> 5) & 0x3f);
2903 tm.tm_hour = ((fattime >> 11) );
2904 tm.tm_mday = ((fatdate ) & 0x1f);
2905 tm.tm_mon = ((fatdate >> 5) & 0x0f) - 1;
2906 tm.tm_year = ((fatdate >> 9) ) + 80;
2907
2908 return mktime(&tm);
2909}
2910
2911void fat_init(void)
2912{
2913 dc_lock_cache();
2914
2915 /* mark the possible volumes as not mounted */
2916 for (unsigned int i = 0; i < NUM_VOLUMES; i++)
2917 {
2918 dc_discard_all(IF_MV(i));
2919 fat_bpbs[i].mounted = false;
2920 }
2921
2922 dc_unlock_cache();
2668} 2923}
2669#endif
diff --git a/firmware/export/config.h b/firmware/export/config.h
index 5e4178cd4c..2a1cdb3416 100644
--- a/firmware/export/config.h
+++ b/firmware/export/config.h
@@ -27,12 +27,21 @@
27/* symbolic names for multiple choice configurations: */ 27/* symbolic names for multiple choice configurations: */
28 28
29/* CONFIG_STORAGE (note these are combineable bit-flags) */ 29/* CONFIG_STORAGE (note these are combineable bit-flags) */
30#define STORAGE_ATA 0x01 30#define STORAGE_ATA_NUM 0
31#define STORAGE_MMC 0x02 31#define STORAGE_MMC_NUM 1
32#define STORAGE_SD 0x04 32#define STORAGE_SD_NUM 2
33#define STORAGE_NAND 0x08 33#define STORAGE_NAND_NUM 3
34#define STORAGE_RAMDISK 0x10 34#define STORAGE_RAMDISK_NUM 4
35#define STORAGE_HOSTFS 0x20 /* meant for APPLICATION targets (implicit for SIMULATOR) */ 35#define STORAGE_HOSTFS_NUM 5
36#define STORAGE_NUM_TYPES 6
37
38#define STORAGE_ATA (1 << STORAGE_ATA_NUM)
39#define STORAGE_MMC (1 << STORAGE_MMC_NUM)
40#define STORAGE_SD (1 << STORAGE_SD_NUM)
41#define STORAGE_NAND (1 << STORAGE_NAND_NUM)
42#define STORAGE_RAMDISK (1 << STORAGE_RAMDISK_NUM)
43 /* meant for APPLICATION targets (implicit for SIMULATOR) */
44#define STORAGE_HOSTFS (1 << STORAGE_HOSTFS_NUM)
36 45
37/* CONFIG_TUNER (note these are combineable bit-flags) */ 46/* CONFIG_TUNER (note these are combineable bit-flags) */
38#define S1A0903X01 0x01 /* Samsung */ 47#define S1A0903X01 0x01 /* Samsung */
@@ -573,6 +582,8 @@ Lyre prototype 1 */
573#ifdef __PCTOOL__ 582#ifdef __PCTOOL__
574#undef CONFIG_CPU 583#undef CONFIG_CPU
575#define CONFIG_CPU 0 584#define CONFIG_CPU 0
585#undef HAVE_MULTIVOLUME
586#undef HAVE_MULTIDRIVE
576#endif 587#endif
577 588
578#ifdef APPLICATION 589#ifdef APPLICATION
@@ -831,9 +842,11 @@ Lyre prototype 1 */
831 * plenty of RAM. Both features can be enabled independently. */ 842 * plenty of RAM. Both features can be enabled independently. */
832#if (MEMORYSIZE >= 8) && !defined(BOOTLOADER) && !defined(__PCTOOL__) \ 843#if (MEMORYSIZE >= 8) && !defined(BOOTLOADER) && !defined(__PCTOOL__) \
833 && !defined(APPLICATION) 844 && !defined(APPLICATION)
845#ifndef SIMULATOR
834#define HAVE_DIRCACHE 846#define HAVE_DIRCACHE
847#endif
835#ifdef HAVE_TAGCACHE 848#ifdef HAVE_TAGCACHE
836#define HAVE_TC_RAMCACHE 849//#define HAVE_TC_RAMCACHE
837#endif 850#endif
838#endif 851#endif
839 852
@@ -1178,7 +1191,7 @@ Lyre prototype 1 */
1178/* This attribute can be used to enable to detection of plugin file handles leaks. 1191/* This attribute can be used to enable to detection of plugin file handles leaks.
1179 * When enabled, the plugin core will monitor open/close/creat and when the plugin exits 1192 * When enabled, the plugin core will monitor open/close/creat and when the plugin exits
1180 * will display an error message if the plugin leaked some file handles */ 1193 * will display an error message if the plugin leaked some file handles */
1181#if (CONFIG_PLATFORM & PLATFORM_NATIVE) 1194#if (CONFIG_PLATFORM & PLATFORM_NATIVE) || defined (SIMULATOR)
1182#define HAVE_PLUGIN_CHECK_OPEN_CLOSE 1195#define HAVE_PLUGIN_CHECK_OPEN_CLOSE
1183#endif 1196#endif
1184 1197
diff --git a/firmware/export/config/gigabeats.h b/firmware/export/config/gigabeats.h
index 11fc02be3d..6657134f9d 100644
--- a/firmware/export/config/gigabeats.h
+++ b/firmware/export/config/gigabeats.h
@@ -16,6 +16,9 @@
16/* define this if you use an ATA controller */ 16/* define this if you use an ATA controller */
17#define CONFIG_STORAGE STORAGE_ATA 17#define CONFIG_STORAGE STORAGE_ATA
18 18
19/* For the Gigabeat S, we mount the second partition */
20#define CONFIG_DEFAULT_PARTNUM 1
21
19/*define this if the ATA controller and method of USB access support LBA48 */ 22/*define this if the ATA controller and method of USB access support LBA48 */
20#define HAVE_LBA48 23#define HAVE_LBA48
21 24
diff --git a/firmware/export/disk.h b/firmware/export/disk.h
index 8d6b41b5bd..c66028fe45 100644
--- a/firmware/export/disk.h
+++ b/firmware/export/disk.h
@@ -24,7 +24,8 @@
24#include "config.h" 24#include "config.h"
25#include "mv.h" /* for volume definitions */ 25#include "mv.h" /* for volume definitions */
26 26
27struct partinfo { 27struct partinfo
28{
28 unsigned long start; /* first sector (LBA) */ 29 unsigned long start; /* first sector (LBA) */
29 unsigned long size; /* number of sectors */ 30 unsigned long size; /* number of sectors */
30 unsigned char type; 31 unsigned char type;
@@ -35,11 +36,9 @@ struct partinfo {
35#define PARTITION_TYPE_FAT16 0x06 36#define PARTITION_TYPE_FAT16 0x06
36#define PARTITION_TYPE_OS2_HIDDEN_C_DRIVE 0x84 37#define PARTITION_TYPE_OS2_HIDDEN_C_DRIVE 0x84
37 38
38/* returns a pointer to an array of 8 partinfo structs */ 39bool disk_init(IF_MD_NONVOID(int drive));
39struct partinfo* disk_init(IF_MD_NONVOID(int drive)); 40bool disk_partinfo(int partition, struct partinfo *info);
40struct partinfo* disk_partinfo(int partition);
41 41
42void disk_init_subsystem(void) INIT_ATTR; /* Initialises mutexes */
43int disk_mount_all(void); /* returns the # of successful mounts */ 42int disk_mount_all(void); /* returns the # of successful mounts */
44int disk_mount(int drive); 43int disk_mount(int drive);
45int disk_unmount_all(void); 44int disk_unmount_all(void);
@@ -50,4 +49,6 @@ int disk_unmount(int drive);
50int disk_get_sector_multiplier(IF_MD_NONVOID(int drive)); 49int disk_get_sector_multiplier(IF_MD_NONVOID(int drive));
51#endif 50#endif
52 51
53#endif 52bool disk_present(IF_MD_NONVOID(int drive));
53
54#endif /* _DISK_H_ */
diff --git a/firmware/export/fat.h b/firmware/export/fat.h
index a0d52acc35..3aa1e254dc 100644
--- a/firmware/export/fat.h
+++ b/firmware/export/fat.h
@@ -18,123 +18,168 @@
18 * KIND, either express or implied. 18 * KIND, either express or implied.
19 * 19 *
20 ****************************************************************************/ 20 ****************************************************************************/
21
22#ifndef FAT_H 21#ifndef FAT_H
23#define FAT_H 22#define FAT_H
24 23
25#include <stdbool.h> 24#include <stdbool.h>
26#include "mv.h" /* for volume definitions */ 25#include <sys/types.h>
26#include <time.h>
27#include "config.h" 27#include "config.h"
28#include "system.h" 28#include "system.h"
29#include "mv.h" /* for volume definitions */
30
31/********
32 **** DO NOT use these functions directly unless otherwise noted. Required
33 **** synchronization is done by higher-level interfaces to minimize locking
34 **** overhead.
35 ****
36 **** Volume, drive, string, etc. parameters should also be checked by
37 **** callers for gross violations-- NULL strings, out-of-bounds values,
38 **** etc.
39 ****/
40
41/****************************************************************************
42 ** Values that can be overridden by a target in config-[target].h
43 **/
44
45/* if your ATA implementation can do better, go right ahead and increase this
46 * value */
47#ifndef FAT_MAX_TRANSFER_SIZE
48#define FAT_MAX_TRANSFER_SIZE 256
49#endif
29 50
30/* This value can be overwritten by a target in config-[target].h, but 51/* still experimental? */
31 that behaviour is still experimental */ 52/* increasing this will increase the total memory used by the cache; the
53 cache, as noted in disk_cache.h, has other minimum requirements that may
54 prevent reducing its number of entries in order to compensate */
32#ifndef SECTOR_SIZE 55#ifndef SECTOR_SIZE
33#define SECTOR_SIZE 512 56#define SECTOR_SIZE 512
34#endif 57#endif
35 58
59/**
60 ****************************************************************************/
61
62#define INVALID_SECNUM (0xfffffffeul) /* sequential, not FAT */
63#define FAT_MAX_FILE_SIZE (0xfffffffful) /* 2^32-1 bytes */
64#define MAX_DIRENTRIES 65536
65#define MAX_DIRECTORY_SIZE (MAX_DIRENTRIES*32) /* 2MB max size */
66
67/* these aren't I/O error conditions, so define specially; failure rc's
68 * shouldn't return the last digit as "0", therefore this is unambiguous */
69#define FAT_RC_ENOSPC (-10)
70#define FAT_SEEK_EOF (-20)
71
36/* Number of bytes reserved for a file name (including the trailing \0). 72/* Number of bytes reserved for a file name (including the trailing \0).
37 Since names are stored in the entry as UTF-8, we won't be able to 73 Since names are stored in the entry as UTF-8, we won't be ble to
38 store all names allowed by FAT. In FAT, a name can have max 255 74 store all names allowed by FAT. In FAT, a name can have max 255
39 characters (not bytes!). Since the UTF-8 encoding of a char may take 75 characters (not bytes!). Since the UTF-8 encoding of a char may take
40 up to 4 bytes, there will be names that we won't be able to store 76 up to 4 bytes, there will be names that we won't be able to store
41 completely. For such names, the short DOS name is used. */ 77 completely. For such names, the short DOS name is used. */
42#define FAT_FILENAME_BYTES 256 78#define FAT_DIRENTRY_NAME_MAX 255
43
44struct fat_direntry 79struct fat_direntry
45{ 80{
46 unsigned char name[FAT_FILENAME_BYTES]; /* UTF-8 encoded name plus \0 */ 81 union {
47 unsigned short attr; /* Attributes */ 82 uint8_t name[255+1+4]; /* UTF-8 name plus \0 plus parse slop */
48 unsigned char crttimetenth; /* Millisecond creation 83 uint16_t ucssegs[5+20][13]; /* UTF-16 segment buffer - layout saves... */
49 time stamp (0-199) */ 84 }; /* ...130 bytes (important if stacked) */
50 unsigned short crttime; /* Creation time */ 85 uint16_t ucsterm; /* allow one NULL-term after ucssegs */
51 unsigned short crtdate; /* Creation date */ 86 uint8_t shortname[13]; /* DOS filename (OEM charset) */
52 unsigned short lstaccdate; /* Last access date */ 87 uint8_t attr; /* file attributes */
53 unsigned short wrttime; /* Last write time */ 88 uint8_t crttimetenth; /* millisecond creation time stamp (0-199) */
54 unsigned short wrtdate; /* Last write date */ 89 uint16_t crttime; /* creation time */
55 unsigned long filesize; /* File size in bytes */ 90 uint16_t crtdate; /* creation date */
56 long firstcluster; /* fstclusterhi<<16 + fstcluslo */ 91 uint16_t lstaccdate; /* last access date */
92 uint16_t wrttime; /* last write time */
93 uint16_t wrtdate; /* last write date */
94 uint32_t filesize; /* file size in bytes */
95 int32_t firstcluster; /* first FAT cluster of file, 0 if empty */
57}; 96};
58 97
59#define FAT_ATTR_READ_ONLY 0x01 98/* cursor structure used for scanning directories; holds the last-returned
60#define FAT_ATTR_HIDDEN 0x02 99 entry information */
61#define FAT_ATTR_SYSTEM 0x04 100struct fat_dirscan_info
62#define FAT_ATTR_VOLUME_ID 0x08 101{
63#define FAT_ATTR_DIRECTORY 0x10 102 unsigned int entry; /* short dir entry index in parent */
64#define FAT_ATTR_ARCHIVE 0x20 103 unsigned int entries; /* number of dir entries used */
65#define FAT_ATTR_VOLUME 0x40 /* this is a volume, not a real directory */ 104};
105
106#define FAT_RW_VAL (0u - 1)
66 107
108/* basic FAT file information about where to find a file and who houses it */
67struct fat_file 109struct fat_file
68{ 110{
69 long firstcluster; /* first cluster in file */
70 long lastcluster; /* cluster of last access */
71 long lastsector; /* sector of last access */
72 long clusternum; /* current clusternum */
73 long sectornum; /* sector number in this cluster */
74 unsigned int direntry; /* short dir entry index from start of dir */
75 unsigned int direntries; /* number of dir entries used by this file */
76 long dircluster; /* first cluster of dir */
77 bool eof;
78#ifdef HAVE_MULTIVOLUME 111#ifdef HAVE_MULTIVOLUME
79 int volume; /* file resides on which volume */ 112 int volume; /* file resides on which volume (first!) */
80#endif 113#endif
114 long firstcluster; /* first cluster in file */
115 long dircluster; /* first cluster of parent directory */
116 struct fat_dirscan_info e; /* entry information */
81}; 117};
82 118
83struct fat_dir 119/* this stores what was last accessed when read or writing a file's data */
120struct fat_filestr
84{ 121{
85 unsigned char sectorcache[SECTOR_SIZE] CACHEALIGN_ATTR; 122 struct fat_file *fatfilep; /* common file information */
86 unsigned int entry; 123 long lastcluster; /* cluster of last access */
87 unsigned int entrycount; 124 unsigned long lastsector; /* sector of last access */
88 long sector; 125 long clusternum; /* cluster number of last access */
89 struct fat_file file; 126 unsigned long sectornum; /* sector number within current cluster */
90 /* There are 2-bytes per characters. We don't want to bother too much, as LFN entries are 127 bool eof; /* end-of-file reached */
91 * at much 255 characters longs, that's at most 20 LFN entries. Each entry hold at most 128};
92 * 13 characters, that a total of 260 characters. So we keep a buffer of that size. 129
93 * Keep coherent with fat.c code. */ 130/** File entity functions **/
94 unsigned char longname[260 * 2]; 131int fat_create_file(struct fat_file *parent, const char *name,
95} CACHEALIGN_ATTR; 132 uint8_t attr, struct fat_file *file,
96 133 struct fat_direntry *fatent);
97#ifdef HAVE_HOTSWAP 134bool fat_dir_is_parent(const struct fat_file *dir, const struct fat_file *file);
98extern void fat_lock(void); 135bool fat_file_is_same(const struct fat_file *file1, const struct fat_file *file2);
99extern void fat_unlock(void); 136int fat_fstat(struct fat_file *file, struct fat_direntry *entry);
100#endif 137int fat_open(const struct fat_file *parent, long startcluster,
138 struct fat_file *file);
139int fat_open_rootdir(IF_MV(int volume,) struct fat_file *dir);
140enum fat_remove_op /* what should fat_remove(), remove? */
141{
142 FAT_RM_DIRENTRIES = 0x1, /* remove only directory entries */
143 FAT_RM_DATA = 0x2, /* remove only file data */
144 FAT_RM_ALL = 0x3, /* remove all of above */
145};
146int fat_remove(struct fat_file *file, enum fat_remove_op what);
147int fat_rename(struct fat_file *parent, struct fat_file *file,
148 const unsigned char *newname);
101 149
102extern void fat_init(void); 150/** File stream functions **/
103extern int fat_get_bytes_per_sector(IF_MV_NONVOID(int volume)); 151int fat_closewrite(struct fat_filestr *filestr, uint32_t size,
104extern int fat_mount(IF_MV(int volume,) IF_MD(int drive,) long startsector); 152 struct fat_direntry *fatentp);
105extern int fat_unmount(int volume, bool flush); 153void fat_filestr_init(struct fat_filestr *filestr, struct fat_file *file);
106extern void fat_size(IF_MV(int volume,) /* public for info */ 154unsigned long fat_query_sectornum(const struct fat_filestr *filestr);
107 unsigned long* size, 155long fat_readwrite(struct fat_filestr *filestr, unsigned long sectorcount,
108 unsigned long* free); 156 void *buf, bool write);
109extern void fat_recalc_free(IF_MV_NONVOID(int volume)); /* public for debug info screen */ 157void fat_rewind(struct fat_filestr *filestr);
110extern int fat_create_dir(const char* name, 158int fat_seek(struct fat_filestr *filestr, unsigned long sector);
111 struct fat_dir* newdir, 159int fat_truncate(const struct fat_filestr *filestr);
112 struct fat_dir* dir);
113extern int fat_open(IF_MV(int volume,)
114 long cluster,
115 struct fat_file* ent,
116 const struct fat_dir* dir);
117extern int fat_create_file(const char* name,
118 struct fat_file* ent,
119 struct fat_dir* dir);
120extern long fat_readwrite(struct fat_file *ent, long sectorcount,
121 void* buf, bool write );
122extern int fat_closewrite(struct fat_file *ent, long size, int attr);
123extern int fat_seek(struct fat_file *ent, unsigned long sector );
124extern int fat_remove(struct fat_file *ent);
125extern int fat_truncate(const struct fat_file *ent);
126extern int fat_rename(struct fat_file* file,
127 struct fat_dir* dir,
128 const unsigned char* newname,
129 long size, int attr);
130
131extern int fat_opendir(IF_MV(int volume,)
132 struct fat_dir *ent, unsigned long startcluster,
133 const struct fat_dir *parent_dir);
134extern int fat_getnext(struct fat_dir *ent, struct fat_direntry *entry);
135extern unsigned int fat_get_cluster_size(IF_MV_NONVOID(int volume)); /* public for debug info screen */
136extern bool fat_ismounted(int volume);
137extern void* fat_get_sector_buffer(void);
138extern void fat_release_sector_buffer(void);
139 160
140#endif 161/** Directory stream functions **/
162struct filestr_cache;
163int fat_readdir(struct fat_filestr *dirstr, struct fat_dirscan_info *scan,
164 struct filestr_cache *cachep, struct fat_direntry *entry);
165void fat_rewinddir(struct fat_dirscan_info *scan);
166
167/** Mounting and unmounting functions **/
168bool fat_ismounted(IF_MV_NONVOID(int volume));
169int fat_mount(IF_MV(int volume,) IF_MD(int drive,) unsigned long startsector);
170int fat_unmount(IF_MV_NONVOID(int volume));
171
172/** Debug screen stuff **/
173#ifdef MAX_LOG_SECTOR_SIZE
174int fat_get_bytes_per_sector(IF_MV_NONVOID(int volume));
175#endif /* MAX_LOG_SECTOR_SIZE */
176unsigned int fat_get_cluster_size(IF_MV_NONVOID(int volume));
177void fat_recalc_free(IF_MV_NONVOID(int volume));
178bool fat_size(IF_MV(int volume,) unsigned long *size, unsigned long *free);
179
180/** Misc. **/
181time_t fattime_mktime(uint16_t fatdate, uint16_t fattime);
182void fat_empty_fat_direntry(struct fat_direntry *entry);
183void fat_init(void);
184
185#endif /* FAT_H */
diff --git a/firmware/export/hostfs.h b/firmware/export/hostfs.h
index bbadecec31..a24d009ca9 100644
--- a/firmware/export/hostfs.h
+++ b/firmware/export/hostfs.h
@@ -41,4 +41,29 @@ extern bool hostfs_removable(int drive);
41extern bool hostfs_present(int drive); 41extern bool hostfs_present(int drive);
42#endif 42#endif
43 43
44/* This has to be repeated here for now for sim's sake since HAVE_HOSTFS
45 eats all the other stuff in storage.h. The sim probably shouldn't use
46 this. */
47#ifdef CONFIG_STORAGE_MULTI
48extern int hostfs_driver_type(int drive);
49#else
50# ifdef APPLICATION
51# define hostfs_driver_type(drive) (STORAGE_HOSTFS_NUM)
52# else /* !APPLICATION */
53# if (CONFIG_STORAGE & STORAGE_ATA)
54# define hostfs_driver_type(drive) (STORAGE_ATA_NUM)
55# elif (CONFIG_STORAGE & STORAGE_SD)
56# define hostfs_driver_type(drive) (STORAGE_SD_NUM)
57# elif (CONFIG_STORAGE & STORAGE_MMC)
58# define hostfs_driver_type(drive) (STORAGE_MMC_NUM)
59# elif (CONFIG_STORAGE & STORAGE_NAND)
60# define hostfs_driver_type(drive) (STORAGE_NAND_NUM)
61# elif (CONFIG_STORAGE & STORAGE_RAMDISK)
62# define hostfs_driver_type(drive) (STORAGE_RAMDISK_NUM)
63# else
64# error Unknown storage driver
65# endif /* CONFIG_STORAGE */
66# endif /* APPLICATION */
67#endif /* CONFIG_STORAGE_MULTI */
68
44#endif /* HOSTFS_H */ 69#endif /* HOSTFS_H */
diff --git a/firmware/export/load_code.h b/firmware/export/load_code.h
index cca577044e..df85a81f82 100644
--- a/firmware/export/load_code.h
+++ b/firmware/export/load_code.h
@@ -45,7 +45,17 @@ static inline void lc_close(void *handle) { (void)handle; }
45 45
46#elif (CONFIG_PLATFORM & PLATFORM_HOSTED) 46#elif (CONFIG_PLATFORM & PLATFORM_HOSTED)
47 47
48#ifdef APPLICATION
49/* App doesn't simulate code loading from a buffer */
50static inline void * lc_open_from_mem(void *addr, size_t blob_size)
51{
52 return NULL;
53 (void)addr; (void)blob_size;
54}
55#else
48extern void *lc_open_from_mem(void* addr, size_t blob_size); 56extern void *lc_open_from_mem(void* addr, size_t blob_size);
57#endif
58
49extern void *lc_get_header(void *handle); 59extern void *lc_get_header(void *handle);
50extern void lc_close(void *handle); 60extern void lc_close(void *handle);
51 61
diff --git a/firmware/export/mv.h b/firmware/export/mv.h
index 1d0a536663..620d77d30d 100644
--- a/firmware/export/mv.h
+++ b/firmware/export/mv.h
@@ -45,23 +45,44 @@
45#define IF_MV(x...) x /* valist contents or empty */ 45#define IF_MV(x...) x /* valist contents or empty */
46#define IF_MV_NONVOID(x...) x /* valist contents or 'void' */ 46#define IF_MV_NONVOID(x...) x /* valist contents or 'void' */
47#define IF_MV_VOL(v) v /* volume argument or '0' */ 47#define IF_MV_VOL(v) v /* volume argument or '0' */
48/* how to name volumes, first char must be outside of legal file names,
49 a number gets appended to enumerate, if applicable */
50#if (CONFIG_STORAGE & STORAGE_MMC)
51#define VOL_NAMES "<MMC%d>"
52#define VOL_ENUM_POS 4 /* position of %d, to avoid runtime calculation */
53#elif (CONFIG_STORAGE & STORAGE_SD)
54#define VOL_NAMES "<microSD%d>"
55#define VOL_ENUM_POS 8 /* position of %d, to avoid runtime calculation */
56#else
57#define VOL_NAMES "<HD%d>"
58#define VOL_ENUM_POS 3
59#endif /* CONFIG_STORAGE */
60 48
61#ifdef HAVE_HOTSWAP 49/* Format: "/<DEC###>/foo/bar"
62bool volume_removable(int volume); 50 * The "DEC" is pure decoration and treated as a comment. Only an unbroken
63bool volume_present(int volume); 51 * trailing string of digits within the brackets is parsed as the volume
52 * number.
53 *
54 * IMPORTANT!: Adjust VOL_DEC_MAX_LEN if needed to the longest of these
55 */
56#define DEFAULT_VOL_DEC "Volume"
57
58#if (CONFIG_STORAGE & STORAGE_ATA)
59#define ATA_VOL_DEC "HDD"
60#endif
61#if (CONFIG_STORAGE & STORAGE_MMC)
62#define MMC_VOL_DEC "MMC"
63#endif
64#if (CONFIG_STORAGE & STORAGE_SD)
65#define SD_VOL_DEC "microSD"
66#endif
67#if (CONFIG_STORAGE & STORAGE_NAND)
68#define NAND_VOL_DEC "NAND"
69#endif
70#if (CONFIG_STORAGE & STORAGE_RAMDISK)
71#define RAMDISK_VOL_DEC "RAMDisk"
72#endif
73#if (CONFIG_STORAGE & STORAGE_HOSTFS)
74#ifndef HOSTFS_VOL_DEC /* overridable */
75#define HOSTFS_VOL_DEC DEFAULT_VOL_DEC
64#endif 76#endif
77#endif
78
79/* Characters that delimit a volume specifier at any root point in the path.
80 The tokens must be outside of legal filename characters */
81#define VOL_START_TOK '<'
82#define VOL_END_TOK '>'
83#define VOL_DEC_MAX_LEN 7 /* biggest of all xxx_VOL_DEC defines */
84#define VOL_MAX_LEN (1 + VOL_DEC_MAX_LEN + 2 + 1)
85#define VOL_NUM_MAX 100
65 86
66#else /* empty definitions if no multi-volume */ 87#else /* empty definitions if no multi-volume */
67#define IF_MV(x...) 88#define IF_MV(x...)
@@ -69,4 +90,30 @@ bool volume_present(int volume);
69#define IF_MV_VOL(v) 0 90#define IF_MV_VOL(v) 0
70#endif /* HAVE_MULTIVOLUME */ 91#endif /* HAVE_MULTIVOLUME */
71 92
93#define CHECK_VOL(volume) \
94 ((unsigned int)IF_MV_VOL(volume) < NUM_VOLUMES)
95
96#define CHECK_DRV(drive) \
97 ((unsigned int)IF_MD_DRV(drive) < NUM_DRIVES)
98
99/* Volume-centric functions (in disk.c) */
100void volume_recalc_free(IF_MV_NONVOID(int volume));
101unsigned int volume_get_cluster_size(IF_MV_NONVOID(int volume));
102void volume_size(IF_MV(int volume,) unsigned long *size, unsigned long *free);
103bool volume_ismounted(IF_MV_NONVOID(int volume));
104#ifdef HAVE_HOTSWAP
105bool volume_removable(int volume);
106bool volume_present(int volume);
107#endif /* HAVE_HOTSWAP */
108
109#ifdef HAVE_MULTIDRIVE
110int volume_drive(int volume);
111#else /* !HAVE_MULTIDRIVE */
112static inline int volume_drive(int volume)
113{
114 return 0;
115 (void)volume;
116}
117#endif /* HAVE_MULTIDRIVE */
118
72#endif /* __MV_H__ */ 119#endif /* __MV_H__ */
diff --git a/firmware/export/pathfuncs.h b/firmware/export/pathfuncs.h
new file mode 100644
index 0000000000..26eb4a1067
--- /dev/null
+++ b/firmware/export/pathfuncs.h
@@ -0,0 +1,100 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _PATHFUNCS_H_
22#define _PATHFUNCS_H_
23
24#include <sys/types.h>
25#define __need_size_t
26#include <stddef.h>
27#include <stdbool.h>
28#include "config.h"
29
30/* useful char constants that could be reconfigured if desired */
31#define PATH_SEPCH '/'
32#define PATH_SEPSTR "/"
33#define PATH_ROOTSTR "/"
34#define PATH_BADSEPCH '\\'
35#define PATH_DRVSEPCH ':'
36
37/* a nicer way to check for "." and ".." than two strcmp() calls */
38static inline bool is_dotdir_name(const char *name)
39{
40 return name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]));
41}
42
43static inline bool name_is_dot(const char *name)
44{
45 return name[0] == '.' && !name[1];
46}
47
48static inline bool name_is_dot_dot(const char *name)
49{
50 return name[0] == '.' && name[1] == '.' && !name[2];
51}
52
53/* return a pointer to the character following path separators */
54#define GOBBLE_PATH_SEPCH(p) \
55 ({ int _c; \
56 const char *_p = (p); \
57 while ((_c = *_p) == PATH_SEPCH) \
58 ++_p; \
59 _p; })
60
61/* return a pointer to the character following a path component which may
62 be a separator or the terminating nul */
63#define GOBBLE_PATH_COMP(p) \
64 ({ int _c; \
65 const char *_p = (p); \
66 while ((_c = *_p) && _c != PATH_SEPCH) \
67 ++_p; \
68 _p; })
69
70/* does the character terminate a path component? */
71#define IS_COMP_TERMINATOR(c) \
72 ({ int _c = (c); \
73 !_c || _c == PATH_SEPCH; })
74
75#ifdef HAVE_MULTIVOLUME
76int path_strip_volume(const char *name, const char **nameptr, bool greedy);
77int get_volume_name(int volume, char *name);
78#endif
79
80int path_strip_drive(const char *name, const char **nameptr, bool greedy);
81size_t path_trim_whitespace(const char *name, const char **nameptr);
82size_t path_basename(const char *name, const char **nameptr);
83size_t path_dirname(const char *name, const char **nameptr);
84size_t path_strip_trailing_separators(const char *name, const char **nameptr);
85void path_correct_separators(char *dstpath, const char *path);
86
87/* constants useable in basepath and component */
88#define PA_SEP_HARD NULL /* separate even if base is empty */
89#define PA_SEP_SOFT "" /* separate only if base is nonempty */
90size_t path_append(char *buffer, const char *basepath, const char *component,
91 size_t bufsize);
92ssize_t parse_path_component(const char **pathp, const char **namep);
93
94/* return true if path begins with a root '/' component and is not NULL */
95static inline bool path_is_absolute(const char *path)
96{
97 return path && path[0] == PATH_SEPCH;
98}
99
100#endif /* _PATHFUNCS_H_ */
diff --git a/firmware/export/rbpaths.h b/firmware/export/rbpaths.h
index 3600709482..b04d669716 100644
--- a/firmware/export/rbpaths.h
+++ b/firmware/export/rbpaths.h
@@ -70,27 +70,6 @@
70 70
71#define HOME_DIR_LEN (sizeof(HOME_DIR)-1) 71#define HOME_DIR_LEN (sizeof(HOME_DIR)-1)
72 72
73#ifdef APPLICATION
74
75#include <dirent.h>
76#include <fcntl.h>
77#include <unistd.h>
78
79int app_open(const char *name, int o, ...);
80int app_creat(const char* name, mode_t mode);
81int app_remove(const char *name);
82int app_rename(const char *old, const char *new);
83DIR* app_opendir(const char *_name);
84int app_closedir(DIR *dir);
85struct dirent* app_readdir(DIR* dir);
86int app_mkdir(const char* name);
87int app_rmdir(const char* name);
88ssize_t app_readlink(const char *path, char *buf, size_t bufsiz);
89
90extern void paths_init(void);
91
92#endif
93
94#define REC_BASE_DIR HOME_DIR 73#define REC_BASE_DIR HOME_DIR
95#define PLAYLIST_CATALOG_DEFAULT_DIR HOME_DIR "/Playlists" 74#define PLAYLIST_CATALOG_DEFAULT_DIR HOME_DIR "/Playlists"
96 75
diff --git a/firmware/export/storage.h b/firmware/export/storage.h
index 8e7281d523..14cba09b35 100644
--- a/firmware/export/storage.h
+++ b/firmware/export/storage.h
@@ -93,6 +93,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
93 #define storage_removable(drive) hostfs_removable(IF_MD(drive)) 93 #define storage_removable(drive) hostfs_removable(IF_MD(drive))
94 #define storage_present(drive) hostfs_present(IF_MD(drive)) 94 #define storage_present(drive) hostfs_present(IF_MD(drive))
95 #endif 95 #endif
96 #define storage_driver_type(drive) hostfs_driver_type(IF_MV(drive))
96 #elif (CONFIG_STORAGE & STORAGE_ATA) 97 #elif (CONFIG_STORAGE & STORAGE_ATA)
97 #define STORAGE_FUNCTION(NAME) (ata_## NAME) 98 #define STORAGE_FUNCTION(NAME) (ata_## NAME)
98 #define storage_spindown ata_spindown 99 #define storage_spindown ata_spindown
@@ -119,6 +120,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
119 #define storage_removable(drive) ata_removable(IF_MD(drive)) 120 #define storage_removable(drive) ata_removable(IF_MD(drive))
120 #define storage_present(drive) ata_present(IF_MD(drive)) 121 #define storage_present(drive) ata_present(IF_MD(drive))
121 #endif 122 #endif
123 #define storage_driver_type(drive) (STORAGE_ATA_NUM)
122 #elif (CONFIG_STORAGE & STORAGE_SD) 124 #elif (CONFIG_STORAGE & STORAGE_SD)
123 #define STORAGE_FUNCTION(NAME) (sd_## NAME) 125 #define STORAGE_FUNCTION(NAME) (sd_## NAME)
124 #define storage_spindown sd_spindown 126 #define storage_spindown sd_spindown
@@ -145,6 +147,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
145 #define storage_removable(drive) sd_removable(IF_MD(drive)) 147 #define storage_removable(drive) sd_removable(IF_MD(drive))
146 #define storage_present(drive) sd_present(IF_MD(drive)) 148 #define storage_present(drive) sd_present(IF_MD(drive))
147 #endif 149 #endif
150 #define storage_driver_type(drive) (STORAGE_SD_NUM)
148 #elif (CONFIG_STORAGE & STORAGE_MMC) 151 #elif (CONFIG_STORAGE & STORAGE_MMC)
149 #define STORAGE_FUNCTION(NAME) (mmc_## NAME) 152 #define STORAGE_FUNCTION(NAME) (mmc_## NAME)
150 #define storage_spindown mmc_spindown 153 #define storage_spindown mmc_spindown
@@ -170,6 +173,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
170 #define storage_removable(drive) mmc_removable(IF_MD(drive)) 173 #define storage_removable(drive) mmc_removable(IF_MD(drive))
171 #define storage_present(drive) mmc_present(IF_MD(drive)) 174 #define storage_present(drive) mmc_present(IF_MD(drive))
172 #endif 175 #endif
176 #define storage_driver_type(drive) (STORAGE_MMC_NUM)
173 #elif (CONFIG_STORAGE & STORAGE_NAND) 177 #elif (CONFIG_STORAGE & STORAGE_NAND)
174 #define STORAGE_FUNCTION(NAME) (nand_## NAME) 178 #define STORAGE_FUNCTION(NAME) (nand_## NAME)
175 #define storage_spindown nand_spindown 179 #define storage_spindown nand_spindown
@@ -195,6 +199,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
195 #define storage_removable(drive) nand_removable(IF_MD(drive)) 199 #define storage_removable(drive) nand_removable(IF_MD(drive))
196 #define storage_present(drive) nand_present(IF_MD(drive)) 200 #define storage_present(drive) nand_present(IF_MD(drive))
197 #endif 201 #endif
202 #define storage_driver_type(drive) (STORAGE_NAND_NUM)
198 #elif (CONFIG_STORAGE & STORAGE_RAMDISK) 203 #elif (CONFIG_STORAGE & STORAGE_RAMDISK)
199 #define STORAGE_FUNCTION(NAME) (ramdisk_## NAME) 204 #define STORAGE_FUNCTION(NAME) (ramdisk_## NAME)
200 #define storage_spindown ramdisk_spindown 205 #define storage_spindown ramdisk_spindown
@@ -220,6 +225,7 @@ static inline void stub_storage_spindown(int timeout) { (void)timeout; }
220 #define storage_removable(drive) ramdisk_removable(IF_MD(drive)) 225 #define storage_removable(drive) ramdisk_removable(IF_MD(drive))
221 #define storage_present(drive) ramdisk_present(IF_MD(drive)) 226 #define storage_present(drive) ramdisk_present(IF_MD(drive))
222 #endif 227 #endif
228 #define storage_driver_type(drive) (STORAGE_RAMDISK_NUM)
223 #else 229 #else
224 //#error No storage driver! 230 //#error No storage driver!
225 #endif 231 #endif
@@ -246,6 +252,7 @@ void storage_get_info(int drive, struct storage_info *info);
246bool storage_removable(int drive); 252bool storage_removable(int drive);
247bool storage_present(int drive); 253bool storage_present(int drive);
248#endif 254#endif
255int storage_driver_type(int drive);
249 256
250#endif /* NOT CONFIG_STORAGE_MULTI and NOT SIMULATOR*/ 257#endif /* NOT CONFIG_STORAGE_MULTI and NOT SIMULATOR*/
251 258
diff --git a/firmware/export/system.h b/firmware/export/system.h
index 4442eb96d7..47dd858d81 100644
--- a/firmware/export/system.h
+++ b/firmware/export/system.h
@@ -104,6 +104,10 @@ int get_cpu_boost_counter(void);
104/* return number of elements in array a */ 104/* return number of elements in array a */
105#define ARRAYLEN(a) (sizeof(a)/sizeof((a)[0])) 105#define ARRAYLEN(a) (sizeof(a)/sizeof((a)[0]))
106 106
107/* is the given pointer "p" inside the said bounds of array "a"? */
108#define PTR_IN_ARRAY(a, p, numelem) \
109 ((uintptr_t)(p) - (uintptr_t)(a) < (uintptr_t)(numelem)*sizeof ((a)[0]))
110
107/* return p incremented by specified number of bytes */ 111/* return p incremented by specified number of bytes */
108#define SKIPBYTES(p, count) ((typeof (p))((char *)(p) + (count))) 112#define SKIPBYTES(p, count) ((typeof (p))((char *)(p) + (count)))
109 113
@@ -188,9 +192,13 @@ enum {
188#include "system-target.h" 192#include "system-target.h"
189#elif defined(HAVE_SDL) /* SDL build */ 193#elif defined(HAVE_SDL) /* SDL build */
190#include "system-sdl.h" 194#include "system-sdl.h"
195#ifdef SIMULATOR
196#include "system-sim.h"
197#endif
191#elif defined(__PCTOOL__) 198#elif defined(__PCTOOL__)
192#include "system-sdl.h" 199#include "system-hosted.h"
193#endif 200#endif
201
194#include "bitswap.h" 202#include "bitswap.h"
195#include "rbendian.h" 203#include "rbendian.h"
196 204
diff --git a/firmware/include/dir.h b/firmware/include/dir.h
index 6e8b70588e..f7719823a9 100644
--- a/firmware/include/dir.h
+++ b/firmware/include/dir.h
@@ -18,46 +18,71 @@
18 * KIND, either express or implied. 18 * KIND, either express or implied.
19 * 19 *
20 ****************************************************************************/ 20 ****************************************************************************/
21
22#ifndef _DIR_H_ 21#ifndef _DIR_H_
23#define _DIR_H_ 22#define _DIR_H_
24 23
24#include <sys/types.h>
25#include <fcntl.h>
26#include <time.h>
25#include "config.h" 27#include "config.h"
28#include "fs_attr.h"
26 29
27#define ATTR_READ_ONLY 0x01 30#if defined (APPLICATION)
28#define ATTR_HIDDEN 0x02 31#include "filesystem-app.h"
29#define ATTR_SYSTEM 0x04 32#elif defined(SIMULATOR) || defined(__PCTOOL__)
30#define ATTR_VOLUME_ID 0x08 33#include "../../uisimulator/common/filesystem-sim.h"
31#define ATTR_DIRECTORY 0x10
32#define ATTR_ARCHIVE 0x20
33#define ATTR_VOLUME 0x40 /* this is a volume, not a real directory */
34#define ATTR_LINK 0x80
35
36#ifdef HAVE_DIRCACHE
37# include "dircache.h"
38# define DIR DIR_CACHED
39# define dirent dirent_cached
40# define opendir opendir_cached
41# define closedir closedir_cached
42# define readdir readdir_cached
43# define closedir closedir_cached
44# define mkdir mkdir_cached
45# define rmdir rmdir_cached
46#else 34#else
47# include "dir_uncached.h" 35#include "filesystem-native.h"
48# define DIR DIR_UNCACHED 36#endif
49# define dirent dirent_uncached 37
50# define opendir opendir_uncached 38#ifndef DIRFUNCTIONS_DEFINED
51# define closedir closedir_uncached 39#ifndef opendir
52# define readdir readdir_uncached 40#define opendir FS_PREFIX(opendir)
53# define closedir closedir_uncached 41#endif
54# define mkdir mkdir_uncached 42#ifndef readdir
55# define rmdir rmdir_uncached 43#define readdir FS_PREFIX(readdir)
44#endif
45#ifndef readdir_r
46#define readdir_r FS_PREFIX(readdir_r)
47#endif
48#ifndef rewinddir
49#define rewinddir FS_PREFIX(rewinddir)
56#endif 50#endif
51#ifndef closedir
52#define closedir FS_PREFIX(closedir)
53#endif
54#ifndef mkdir
55#define mkdir FS_PREFIX(mkdir)
56#endif
57#ifndef rmdir
58#define rmdir FS_PREFIX(rmdir)
59#endif
60#ifndef samedir
61#define samedir FS_PREFIX(samedir)
62#endif
63#ifndef dir_exists
64#define dir_exists FS_PREFIX(dir_exists)
65#endif
66#endif /* !DIRFUNCTIONS_DEFINED */
57 67
68#ifndef DIRENT_DEFINED
69struct DIRENT
70{
71 struct dirinfo_native info; /* platform extra info */
72 char d_name[MAX_PATH]; /* UTF-8 name of entry (last!) */
73};
74#endif /* DIRENT_DEFINED */
58 75
59typedef DIR* (*opendir_func)(const char* name); 76struct dirinfo
60typedef int (*closedir_func)(DIR* dir); 77{
61typedef struct dirent* (*readdir_func)(DIR* dir); 78 unsigned int attribute; /* attribute bits of file */
79 off_t size; /* binary size of file */
80 time_t mtime; /* local file time */
81};
62 82
63#endif 83#ifndef DIRFUNCTIONS_DECLARED
84/* TIP: set errno to zero before calling to see if anything failed */
85struct dirinfo dir_get_info(DIR *dirp, struct DIRENT *entry);
86#endif /* !DIRFUNCTIONS_DECLARED */
87
88#endif /* _DIR_H_ */
diff --git a/firmware/include/dir_uncached.h b/firmware/include/dir_uncached.h
deleted file mode 100644
index 6443d5ba97..0000000000
--- a/firmware/include/dir_uncached.h
+++ /dev/null
@@ -1,107 +0,0 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id: dir.h 13741 2007-06-30 02:08:27Z jethead71 $
9 *
10 * Copyright (C) 2002 by Björn Stenberg
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _DIR_UNCACHED_H_
22#define _DIR_UNCACHED_H_
23
24#include "config.h"
25
26struct dirinfo {
27 int attribute;
28 long size;
29 unsigned short wrtdate;
30 unsigned short wrttime;
31};
32
33#include <stdbool.h>
34#include "file.h"
35
36#if defined(SIMULATOR) || defined(__PCTOOL__)
37# define dirent_uncached sim_dirent
38# define DIR_UNCACHED SIM_DIR
39# define opendir_uncached sim_opendir
40# define readdir_uncached sim_readdir
41# define closedir_uncached sim_closedir
42# define mkdir_uncached sim_mkdir
43# define rmdir_uncached sim_rmdir
44#elif defined(APPLICATION)
45# include "rbpaths.h"
46# define DIRENT_DEFINED
47# define DIR_DEFINED
48# define dirent_uncached dirent
49# define DIR_UNCACHED DIR
50# define opendir_uncached app_opendir
51# define readdir_uncached app_readdir
52# define closedir_uncached app_closedir
53# define mkdir_uncached app_mkdir
54# define rmdir_uncached app_rmdir
55#endif
56
57
58#ifndef DIRENT_DEFINED
59struct dirent_uncached {
60 unsigned char d_name[MAX_PATH];
61 struct dirinfo info;
62 long startcluster;
63};
64#endif
65
66#include "fat.h"
67
68#ifndef DIR_DEFINED
69typedef struct {
70#if (CONFIG_PLATFORM & PLATFORM_NATIVE)
71 struct fat_dir fatdir CACHEALIGN_ATTR;
72 bool busy;
73 long startcluster;
74 struct dirent_uncached theent;
75#ifdef HAVE_MULTIVOLUME
76 int volumecounter; /* running counter for faked volume entries */
77#endif
78#else
79 /* simulator/application: */
80 void *dir; /* actually a DIR* dir */
81 char *name;
82#endif
83} DIR_UNCACHED CACHEALIGN_ATTR;
84#endif
85
86#ifdef HAVE_HOTSWAP
87char *get_volume_name(int volume);
88#endif
89
90#ifdef HAVE_MULTIVOLUME
91 int strip_volume(const char*, char*);
92#endif
93
94#ifndef DIRFUNCTIONS_DEFINED
95
96extern DIR_UNCACHED* opendir_uncached(const char* name);
97extern int closedir_uncached(DIR_UNCACHED* dir);
98extern int mkdir_uncached(const char* name);
99extern int rmdir_uncached(const char* name);
100
101extern struct dirent_uncached* readdir_uncached(DIR_UNCACHED* dir);
102
103extern int release_dirs(int volume);
104
105#endif /* DIRFUNCTIONS_DEFINED */
106
107#endif
diff --git a/firmware/include/dircache.h b/firmware/include/dircache.h
index 019ccf49b7..7e8c764e7f 100644
--- a/firmware/include/dircache.h
+++ b/firmware/include/dircache.h
@@ -8,6 +8,7 @@
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2005 by Miika Pekkarinen 10 * Copyright (C) 2005 by Miika Pekkarinen
11 * Copyright (C) 2014 by Michael Sevakis
11 * 12 *
12 * This program is free software; you can redistribute it and/or 13 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License 14 * modify it under the terms of the GNU General Public License
@@ -21,84 +22,154 @@
21#ifndef _DIRCACHE_H 22#ifndef _DIRCACHE_H
22#define _DIRCACHE_H 23#define _DIRCACHE_H
23 24
24#include "config.h" 25#include "mv.h"
25#include "dir_uncached.h" 26#include <string.h> /* size_t */
26#include <string.h> /* size_t */ 27#include <sys/types.h> /* ssize_t */
27 28
28#ifdef HAVE_DIRCACHE 29#ifdef HAVE_DIRCACHE
29 30
30#define DIRCACHE_RESERVE (1024*64) 31/****************************************************************************
31#define DIRCACHE_LIMIT (1024*1024*6) 32 ** Configurable values
32 33 **/
33#define DIRCACHE_APPFLAG_TAGCACHE 0x0001 34
34#define DIRCACHE_APPFLAG_PLAYLIST 0x0002 35#if 0
35 36/* enable dumping code */
36/* Internal structures. */ 37#define DIRCACHE_DUMPSTER
37struct travel_data { 38#define DIRCACHE_DUMPSTER_BIN "/dircache_dump.bin"
38 struct dircache_entry *first; 39#define DIRCACHE_DUMPSTER_CSV "/dircache_dump.csv"
39 struct dircache_entry *ce;
40 struct dircache_entry *down_entry;
41#if (CONFIG_PLATFORM & PLATFORM_HOSTED)
42 DIR_UNCACHED *dir, *newdir;
43 struct dirent_uncached *entry;
44#else
45 struct fat_dir *dir;
46 struct fat_dir newdir;
47 struct fat_direntry entry;
48#endif 40#endif
49 int pathpos; 41
42/* dircache builds won't search below this but will work down to this point
43 while below it the cache will just pass requests through to the storage;
44 the limiting factor is the scanning thread stack size, not the
45 implementation -- tune the two together */
46#define DIRCACHE_MAX_DEPTH 15
47#define DIRCACHE_STACK_SIZE (DEFAULT_STACK_SIZE + 0x100)
48
49/* memory buffer constants that control allocation */
50#define DIRCACHE_RESERVE (1024*64) /* 64 KB - new entry slack */
51#define DIRCACHE_MIN (1024*1024*1) /* 1 MB - provision min size */
52#define DIRCACHE_LIMIT (1024*1024*6) /* 6 MB - provision max size */
53
54/* make it easy to change serialnumber size without modifying anything else;
55 32 bits allows 21845 builds before wrapping in a 6MB cache that is filled
56 exclusively with entries and nothing else (32 byte entries), making that
57 figure pessimistic */
58typedef uint32_t dc_serial_t;
59
60/**
61 ****************************************************************************/
62
63#if CONFIG_PLATFORM & PLATFORM_NATIVE
64/* native dircache is lower-level than on a hosted target */
65#define DIRCACHE_NATIVE
66#endif
67
68struct dircache_file
69{
70 int idx; /* this file's cache index */
71 dc_serial_t serialnum; /* this file's serial number */
50}; 72};
51 73
52struct dirent_cached { 74enum dircache_status
53 struct dirinfo info; 75{
54 char *d_name; 76 DIRCACHE_IDLE = 0, /* no volume is initialized */
55 long startcluster; 77 DIRCACHE_SCANNING = 1, /* dircache is scanning a volume */
78 DIRCACHE_READY = 2, /* dircache is ready to be used */
79};
80
81/** Dircache control **/
82void dircache_wait(void);
83void dircache_suspend(void);
84int dircache_resume(void);
85int dircache_enable(void);
86void dircache_disable(void);
87void dircache_free_buffer(void);
88
89/** Volume mounting **/
90void dircache_mount(void); /* always tries building everything it can */
91void dircache_unmount(IF_MV_NONVOID(int volume));
92
93
94/** File API service functions **/
95
96/* avoid forcing #include of file_internal.h, fat.h and dir.h */
97struct filestr_base;
98struct file_base_info;
99struct file_base_binding;
100struct dirent;
101struct dirscan_info;
102struct dirinfo_native;
103
104int dircache_readdir_dirent(struct filestr_base *stream,
105 struct dirscan_info *scanp,
106 struct dirent *entry);
107void dircache_rewinddir_dirent(struct dirscan_info *scanp);
108
109#ifdef DIRCACHE_NATIVE
110struct fat_direntry;
111int dircache_readdir_internal(struct filestr_base *stream,
112 struct file_base_info *infop,
113 struct fat_direntry *fatent);
114void dircache_rewinddir_internal(struct file_base_info *info);
115#endif /* DIRCACHE_NATIVE */
116
117
118/** Dircache live updating **/
119
120void dircache_get_rootinfo(struct file_base_info *infop);
121void dircache_bind_file(struct file_base_binding *bindp);
122void dircache_unbind_file(struct file_base_binding *bindp);
123void dircache_fileop_create(struct file_base_info *dirinfop,
124 struct file_base_binding *bindp,
125 const char *basename,
126 const struct dirinfo_native *dinp);
127void dircache_fileop_rename(struct file_base_info *dirinfop,
128 struct file_base_binding *bindp,
129 const char *basename);
130void dircache_fileop_remove(struct file_base_binding *bindp);
131void dircache_fileop_sync(struct file_base_binding *infop,
132 const struct dirinfo_native *dinp);
133
134
135/** Dircache paths and files **/
136ssize_t dircache_get_path(const struct dircache_file *dcfilep, char *buf,
137 size_t size);
138int dircache_get_file(const char *path, struct dircache_file *dcfilep);
139
140
141/** Debug screen/info stuff **/
142
143struct dircache_info
144{
145 enum dircache_status status; /* current composite status value */
146 const char *statusdesc; /* pointer to string describing 'status' */
147 size_t last_size; /* cache size after last build */
148 size_t size; /* total size of entries (with holes) */
149 size_t sizeused; /* bytes of 'size' actually utilized */
150 size_t size_limit; /* maximum possible size */
151 size_t reserve; /* size of reserve area */
152 size_t reserve_used; /* amount of reserve used */
153 unsigned int entry_count; /* number of cache entries */
154 long build_ticks; /* total time used to build cache */
56}; 155};
57 156
58typedef struct { 157void dircache_get_info(struct dircache_info *info);
59 bool busy; 158#ifdef DIRCACHE_DUMPSTER
60 struct dirent_cached theent; /* .attribute is set to -1 on init(opendir) */ 159void dircache_dump(void);
61 int internal_entry; /* the current entry in the directory */ 160#endif /* DIRCACHE_DUMPSTER */
62 DIR_UNCACHED *regulardir; 161
63} DIR_CACHED;
64 162
65void dircache_init(void) INIT_ATTR; 163/** Misc. stuff **/
164void dircache_dcfile_init(struct dircache_file *dcfilep);
165
166#ifdef HAVE_EEPROM_SETTINGS
66int dircache_load(void); 167int dircache_load(void);
67int dircache_save(void); 168int dircache_save(void);
68int dircache_build(int last_size); 169#endif /* HAVE_EEPROM_SETTINGS */
69void* dircache_steal_buffer(size_t *size);
70bool dircache_is_enabled(void);
71bool dircache_is_initializing(void);
72void dircache_set_appflag(long mask);
73bool dircache_get_appflag(long mask);
74int dircache_get_entry_count(void);
75int dircache_get_cache_size(void);
76int dircache_get_reserve_used(void);
77int dircache_get_build_ticks(void);
78void dircache_disable(void);
79void dircache_suspend(void);
80bool dircache_resume(void);
81int dircache_get_entry_id(const char *filename);
82size_t dircache_copy_path(int index, char *buf, size_t size);
83
84/* the next two are internal for file.c */
85long _dircache_get_entry_startcluster(int id);
86struct dirinfo* _dircache_get_entry_dirinfo(int id);
87
88void dircache_bind(int fd, const char *path);
89void dircache_update_filesize(int fd, long newsize, long startcluster);
90void dircache_update_filetime(int fd);
91void dircache_mkdir(const char *path);
92void dircache_rmdir(const char *path);
93void dircache_remove(const char *name);
94void dircache_rename(const char *oldpath, const char *newpath);
95void dircache_add_file(const char *path, long startcluster);
96
97DIR_CACHED* opendir_cached(const char* name);
98struct dirent_cached* readdir_cached(DIR_CACHED* dir);
99int closedir_cached(DIR_CACHED *dir);
100int mkdir_cached(const char *name);
101int rmdir_cached(const char* name);
102#endif /* !HAVE_DIRCACHE */
103 170
104#endif 171void dircache_init(size_t last_size) INIT_ATTR;
172
173#endif /* HAVE_DIRCACHE */
174
175#endif /* _DIRCACHE_H */
diff --git a/firmware/include/dircache_redirect.h b/firmware/include/dircache_redirect.h
new file mode 100644
index 0000000000..15fb4bc38d
--- /dev/null
+++ b/firmware/include/dircache_redirect.h
@@ -0,0 +1,198 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _DIRCACHE_REDIRECT_H_
22
23#include "dir.h"
24
25/***
26 ** Internal redirects that depend upon whether or not dircache is made
27 **/
28
29/** File binding **/
30
31static inline void get_rootinfo_internal(struct file_base_info *infop)
32{
33#ifdef HAVE_DIRCACHE
34 dircache_get_rootinfo(infop);
35#else
36 (void)infop;
37#endif
38}
39
40static inline void fileobj_bind_file(struct file_base_binding *bindp)
41{
42#ifdef HAVE_DIRCACHE
43 dircache_bind_file(bindp);
44#else
45 file_binding_insert_last(bindp);
46#endif
47}
48
49static inline void fileobj_unbind_file(struct file_base_binding *bindp)
50{
51#ifdef HAVE_DIRCACHE
52 dircache_unbind_file(bindp);
53#else
54 file_binding_remove(bindp);
55#endif
56}
57
58
59/** File event handlers **/
60
61static inline void fileop_onopen_internal(struct filestr_base *stream,
62 struct file_base_info *srcinfop,
63 unsigned int callflags)
64{
65 fileobj_fileop_open(stream, srcinfop, callflags);
66}
67
68static inline void fileop_onclose_internal(struct filestr_base *stream)
69{
70 fileobj_fileop_close(stream);
71}
72
73static inline void fileop_oncreate_internal(struct filestr_base *stream,
74 struct file_base_info *srcinfop,
75 unsigned int callflags,
76 struct file_base_info *dirinfop,
77 const char *basename)
78{
79#ifdef HAVE_DIRCACHE
80 dircache_dcfile_init(&srcinfop->dcfile);
81#endif
82 fileobj_fileop_create(stream, srcinfop, callflags);
83#ifdef HAVE_DIRCACHE
84 struct dirinfo_native din;
85 fill_dirinfo_native(&din);
86 dircache_fileop_create(dirinfop, stream->bindp, basename, &din);
87#endif
88 (void)dirinfop; (void)basename;
89}
90
91static inline void fileop_onremove_internal(struct filestr_base *stream,
92 struct file_base_info *oldinfop)
93{
94 fileobj_fileop_remove(stream, oldinfop);
95#ifdef HAVE_DIRCACHE
96 dircache_fileop_remove(stream->bindp);
97#endif
98}
99
100static inline void fileop_onrename_internal(struct filestr_base *stream,
101 struct file_base_info *oldinfop,
102 struct file_base_info *dirinfop,
103 const char *basename)
104{
105 fileobj_fileop_rename(stream, oldinfop);
106#ifdef HAVE_DIRCACHE
107 dircache_fileop_rename(dirinfop, stream->bindp, basename);
108#endif
109 (void)dirinfop; (void)basename;
110}
111
112static inline void fileop_onsync_internal(struct filestr_base *stream)
113{
114 fileobj_fileop_sync(stream);
115#ifdef HAVE_DIRCACHE
116 struct dirinfo_native din;
117 fill_dirinfo_native(&din);
118 dircache_fileop_sync(stream->bindp, &din);
119#endif
120}
121
122static inline void fileop_ontruncate_internal(struct filestr_base *stream)
123{
124 fileobj_fileop_truncate(stream);
125}
126
127static inline void volume_onmount_internal(IF_MV_NONVOID(int volume))
128{
129#ifdef HAVE_DIRCACHE
130 dircache_mount();
131#endif
132 IF_MV( (void)volume; )
133}
134
135static inline void volume_onunmount_internal(IF_MV_NONVOID(int volume))
136{
137 fileobj_mgr_unmount(IF_MV(volume));
138#ifdef HAVE_DIRCACHE
139 dircache_unmount(IF_MV(volume));
140#endif
141}
142
143
144/** Directory reading **/
145
146static inline int readdir_dirent(struct filestr_base *stream,
147 struct dirscan_info *scanp,
148 struct dirent *entry)
149{
150#ifdef HAVE_DIRCACHE
151 return dircache_readdir_dirent(stream, scanp, entry);
152#else
153 return uncached_readdir_dirent(stream, scanp, entry);
154#endif
155}
156
157static inline void rewinddir_dirent(struct dirscan_info *scanp)
158{
159#ifdef HAVE_DIRCACHE
160 dircache_rewinddir_dirent(scanp);
161#else
162 uncached_rewinddir_dirent(scanp);
163#endif
164}
165
166static inline int readdir_internal(struct filestr_base *stream,
167 struct file_base_info *infop,
168 struct fat_direntry *fatent)
169{
170#ifdef HAVE_DIRCACHE
171 return dircache_readdir_internal(stream, infop, fatent);
172#else
173 return uncached_readdir_internal(stream, infop, fatent);
174#endif
175}
176
177static inline void rewinddir_internal(struct file_base_info *infop)
178{
179#ifdef HAVE_DIRCACHE
180 dircache_rewinddir_internal(infop);
181#else
182 uncached_rewinddir_internal(infop);
183#endif
184}
185
186
187/** Misc. stuff **/
188
189static inline struct fat_direntry *get_dir_fatent_dircache(void)
190{
191#ifdef HAVE_DIRCACHE
192 return get_dir_fatent();
193#else
194 return NULL;
195#endif
196}
197
198#endif /* _DIRCACHE_REDIRECT_H_ */
diff --git a/firmware/include/disk_cache.h b/firmware/include/disk_cache.h
new file mode 100644
index 0000000000..725b3778cc
--- /dev/null
+++ b/firmware/include/disk_cache.h
@@ -0,0 +1,83 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef DISK_CACHE_H
22#define DISK_CACHE_H
23
24/* This needs enough for all file handles to have a buffer in the worst case
25 * plus at least one reserved exclusively for the cache client and a couple
26 * for other file system code. The buffers are put to use by the cache if not
27 * taken for another purpose (meaning nothing is wasted sitting fallow).
28 *
29 * One map per volume is maintained in order to avoid collisions between
30 * volumes that would slow cache probing. DC_MAP_NUM_ENTRIES is the number
31 * for each map per volume. The buffers themselves are shared.
32 */
33#if MEMORYSIZE < 8
34#define DC_NUM_ENTRIES 32
35#define DC_MAP_NUM_ENTRIES 128
36#elif MEMORYSIZE <= 32
37#define DC_NUM_ENTRIES 48
38#define DC_MAP_NUM_ENTRIES 128
39#else /* MEMORYSIZE > 32 */
40#define DC_NUM_ENTRIES 64
41#define DC_MAP_NUM_ENTRIES 256
42#endif /* MEMORYSIZE */
43
44/* this _could_ be larger than a sector if that would ever be useful */
45#define DC_CACHE_BUFSIZE SECTOR_SIZE
46
47#include "mutex.h"
48#include "mv.h"
49
50static inline void dc_lock_cache(void)
51{
52 extern struct mutex disk_cache_mutex;
53 mutex_lock(&disk_cache_mutex);
54}
55
56static inline void dc_unlock_cache(void)
57{
58 extern struct mutex disk_cache_mutex;
59 mutex_unlock(&disk_cache_mutex);
60}
61
62void * dc_cache_probe(IF_MV(int volume,) unsigned long secnum,
63 unsigned int *flags);
64void dc_dirty_buf(void *buf);
65void dc_discard_buf(void *buf);
66void dc_commit_all(IF_MV_NONVOID(int volume));
67void dc_discard_all(IF_MV_NONVOID(int volume));
68
69void dc_init(void) INIT_ATTR;
70
71/* in addition to filling, writeback is implemented by the client */
72extern void dc_writeback_callback(IF_MV(int volume, ) unsigned long sector,
73 void *buf);
74
75
76/** These synchronize and can be called by anyone **/
77
78/* expropriate a buffer from the cache of DC_CACHE_BUFSIZE bytes */
79void * dc_get_buffer(void);
80/* return buffer to the cache by buffer */
81void dc_release_buffer(void *buf);
82
83#endif /* DISK_CACHE_H */
diff --git a/firmware/include/file.h b/firmware/include/file.h
index 77930864c7..8e5bacec0e 100644
--- a/firmware/include/file.h
+++ b/firmware/include/file.h
@@ -18,81 +18,88 @@
18 * KIND, either express or implied. 18 * KIND, either express or implied.
19 * 19 *
20 ****************************************************************************/ 20 ****************************************************************************/
21
22#ifndef _FILE_H_ 21#ifndef _FILE_H_
23#define _FILE_H_ 22#define _FILE_H_
24 23
25#include <sys/types.h> 24#include <sys/types.h>
26#include "config.h" 25#include <stdbool.h>
27#include "gcc_extensions.h"
28#include <fcntl.h> 26#include <fcntl.h>
29#ifdef WIN32 27#ifdef WIN32
30/* this has SEEK_SET et al */ 28/* this has SEEK_SET et al */
31#include <stdio.h> 29#include <stdio.h>
32#endif 30#endif
33 31#include "config.h"
32#include "gcc_extensions.h"
34 33
35#undef MAX_PATH /* this avoids problems when building simulator */ 34#undef MAX_PATH /* this avoids problems when building simulator */
36#define MAX_PATH 260 35#define MAX_PATH 260
37#define MAX_OPEN_FILES 11
38 36
39#if !defined(PLUGIN) && !defined(CODEC) 37enum relate_result
40#if defined(APPLICATION) && !defined(__PCTOOL__) 38{
41#include "rbpaths.h" 39 /* < 0 == failure */
42# define open(x, ...) app_open(x, __VA_ARGS__) 40 RELATE_DIFFERENT = 0, /* the two paths are different objects */
43# define creat(x,m) app_creat(x, m) 41 RELATE_SAME, /* the two paths are the same object */
44# define remove(x) app_remove(x) 42 RELATE_PREFIX, /* the path2 contains path1 as a prefix */
45# define rename(x,y) app_rename(x,y) 43};
46# define readlink(x,y,z) app_readlink(x,y,z)
47# if (CONFIG_PLATFORM & (PLATFORM_SDL|PLATFORM_MAEMO|PLATFORM_PANDORA))
48/* SDL overrides a few more */
49# define read(x,y,z) sim_read(x,y,z)
50# define write(x,y,z) sim_write(x,y,z)
51# endif
52#elif defined(SIMULATOR) || defined(DBTOOL)
53# define open(x, ...) sim_open(x, __VA_ARGS__)
54# define creat(x,m) sim_creat(x,m)
55# define remove(x) sim_remove(x)
56# define rename(x,y) sim_rename(x,y)
57# define fsync(x) sim_fsync(x)
58# define ftruncate(x,y) sim_ftruncate(x,y)
59# define lseek(x,y,z) sim_lseek(x,y,z)
60# define read(x,y,z) sim_read(x,y,z)
61# define write(x,y,z) sim_write(x,y,z)
62# define close(x) sim_close(x)
63/* readlink() not used in the sim yet */
64extern int sim_open(const char *name, int o, ...);
65extern int sim_creat(const char *name, mode_t mode);
66#endif
67 44
68typedef int (*open_func)(const char* pathname, int flags, ...); 45#if defined(APPLICATION)
69typedef ssize_t (*read_func)(int fd, void *buf, size_t count); 46#include "filesystem-app.h"
70typedef int (*creat_func)(const char *pathname, mode_t mode); 47#elif defined(SIMULATOR) || defined(__PCTOOL__)
71typedef ssize_t (*write_func)(int fd, const void *buf, size_t count); 48#include "../../uisimulator/common/filesystem-sim.h"
72typedef void (*qsort_func)(void *base, size_t nmemb, size_t size, 49#else
73 int(*_compar)(const void *, const void *)); 50#include "filesystem-native.h"
51#endif
74 52
75extern int file_open(const char* pathname, int flags); 53#ifndef FILEFUNCTIONS_DECLARED
76extern int close(int fd); 54int fdprintf(int fildes, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3);
77extern int fsync(int fd); 55#endif /* FILEFUNCTIONS_DECLARED */
78extern ssize_t read(int fd, void *buf, size_t count);
79extern off_t lseek(int fildes, off_t offset, int whence);
80extern int file_creat(const char *pathname);
81#if ((CONFIG_PLATFORM & PLATFORM_NATIVE) && !defined(__PCTOOL__)) || \
82 defined(TEST_FAT)
83#define creat(x, y) file_creat(x)
84 56
85#if !defined(CODEC) && !defined(PLUGIN) 57#ifndef FILEFUNCTIONS_DEFINED
86#define open(x, y, ...) file_open(x,y) 58#ifndef open
59#define open FS_PREFIX(open)
87#endif 60#endif
61#ifndef creat
62#define creat FS_PREFIX(creat)
88#endif 63#endif
89 64#ifndef close
90extern ssize_t write(int fd, const void *buf, size_t count); 65#define close FS_PREFIX(close)
91extern int remove(const char* pathname); 66#endif
92extern int rename(const char* path, const char* newname); 67#ifndef ftruncate
93extern int ftruncate(int fd, off_t length); 68#define ftruncate FS_PREFIX(ftruncate)
94extern off_t filesize(int fd); 69#endif
95extern int release_files(int volume); 70#ifndef fsync
96int fdprintf (int fd, const char *fmt, ...) ATTRIBUTE_PRINTF(2, 3); 71#define fsync FS_PREFIX(fsync)
97#endif /* !CODEC && !PLUGIN */
98#endif 72#endif
73#ifndef lseek
74#define lseek FS_PREFIX(lseek)
75#endif
76#ifndef read
77#define read FS_PREFIX(read)
78#endif
79#ifndef write
80#define write FS_PREFIX(write)
81#endif
82#ifndef remove
83#define remove FS_PREFIX(remove)
84#endif
85#ifndef rename
86#define rename FS_PREFIX(rename)
87#endif
88#ifndef filesize
89#define filesize FS_PREFIX(filesize)
90#endif
91#ifndef fsamefile
92#define fsamefile FS_PREFIX(fsamefile)
93#endif
94#ifndef file_exists
95#define file_exists FS_PREFIX(file_exists)
96#endif
97#ifndef relate
98#define relate FS_PREFIX(relate)
99#endif
100#ifndef readlink
101#define readlink FS_PREFIX(readlink)
102#endif
103#endif /* FILEFUNCTIONS_DEFINED */
104
105#endif /* _FILE_H_ */
diff --git a/firmware/include/file_internal.h b/firmware/include/file_internal.h
new file mode 100644
index 0000000000..d1bb67406a
--- /dev/null
+++ b/firmware/include/file_internal.h
@@ -0,0 +1,371 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _FILE_INTERNAL_H_
22#define _FILE_INTERNAL_H_
23
24#include <sys/types.h>
25#include <stdlib.h>
26#include "mv.h"
27#include "linked_list.h"
28#include "mutex.h"
29#include "mrsw_lock.h"
30#include "fs_attr.h"
31#include "fat.h"
32#ifdef HAVE_DIRCACHE
33#include "dircache.h"
34#endif
35
36/** Tuneable parameters **/
37
38/* limits for number of open descriptors - if you increase these values, make
39 certain that the disk cache has enough available buffers */
40#define MAX_OPEN_FILES 11
41#define MAX_OPEN_DIRS 12
42#define MAX_OPEN_HANDLES (MAX_OPEN_FILES+MAX_OPEN_DIRS)
43
44/* internal functions open streams as well; make sure they don't fail if all
45 user descs are busy; this needs to be at least the greatest quantity needed
46 at once by all internal functions */
47#ifdef HAVE_DIRCACHE
48#define AUX_FILEOBJS 3
49#else
50#define AUX_FILEOBJS 2
51#endif
52
53/* number of components statically allocated to handle the vast majority
54 of path depths; should maybe be tuned for >= 90th percentile but for now,
55 imma just guessing based on something like:
56 root + 'Music' + 'Artist' + 'Album' + 'Disc N' + filename */
57#define STATIC_PATHCOMP_NUM 6
58
59#define MAX_NAME 255
60
61/* unsigned value that will also hold the off_t range we need without
62 overflow */
63#define file_size_t uint32_t
64
65#ifdef __USE_FILE_OFFSET64
66/* if we want, we can deal with files up to 2^32-1 bytes-- the full FAT16/32
67 range */
68#define FILE_SIZE_MAX (0xffffffffu)
69#else
70/* file contents and size will be preserved by the APIs so long as ftruncate()
71 isn't used; bytes passed 2^31-1 will not accessible nor will writes succeed
72 that would extend the file beyond the max for a 32-bit off_t */
73#define FILE_SIZE_MAX (0x7fffffffu)
74#endif
75
76/* if file is "large(ish)", then get rid of the contents now rather than
77 lazily when the file is synced or closed in order to free-up space */
78#define O_TRUNC_THRESH 65536
79
80/* default attributes when creating new files and directories */
81#define ATTR_NEW_FILE (ATTR_ARCHIVE)
82#define ATTR_NEW_DIRECTORY (ATTR_DIRECTORY)
83
84#define ATTR_MOUNT_POINT (ATTR_VOLUME | ATTR_DIRECTORY)
85
86/** File sector cache **/
87
88enum filestr_cache_flags
89{
90 FSC_DIRTY = 0x1, /* buffer is dirty (needs writeback) */
91 FSC_NEW = 0x2, /* buffer is new (never yet written) */
92};
93
94struct filestr_cache
95{
96 uint8_t *buffer; /* buffer to hold sector */
97 unsigned long sector; /* file sector that is in buffer */
98 unsigned int flags; /* FSC_* bits */
99};
100
101void file_cache_init(struct filestr_cache *cachep);
102void file_cache_reset(struct filestr_cache *cachep);
103void file_cache_alloc(struct filestr_cache *cachep);
104void file_cache_free(struct filestr_cache *cachep);
105
106
107/** Common bitflags used throughout **/
108
109/* bitflags used by open files and descriptors */
110enum fildes_and_obj_flags
111{
112 /* used in descriptor and common */
113 FDO_BUSY = 0x0001, /* descriptor/object is in use */
114 /* only used in individual stream descriptor */
115 FD_WRITE = 0x0002, /* descriptor has write mode */
116 FD_WRONLY = 0x0004, /* descriptor is write mode only */
117 FD_APPEND = 0x0008, /* descriptor is append mode */
118 /* only used as common flags */
119 FO_DIRECTORY = 0x0010, /* fileobj is a directory */
120 FO_TRUNC = 0x0020, /* fileobj is opened to be truncated */
121 FO_REMOVED = 0x0040, /* fileobj was deleted while open */
122 FO_SINGLE = 0x0080, /* fileobj has only one stream open */
123 FDO_MASK = 0x00ff,
124 /* bitflags that instruct various 'open' functions how to behave */
125 FF_FILE = 0x0000, /* expect file; accept file only */
126 FF_DIR = 0x0100, /* expect dir; accept dir only */
127 FF_ANYTYPE = 0x0200, /* succeed if either file or dir */
128 FF_TYPEMASK = 0x0300, /* mask of typeflags */
129 FF_CREAT = 0x0400, /* create if file doesn't exist */
130 FF_EXCL = 0x0800, /* fail if creating and file exists */
131 FF_CHECKPREFIX = 0x1000, /* detect if file is prefix of path */
132 FF_NOISO = 0x2000, /* do not decode ISO filenames to UTF-8 */
133 FF_MASK = 0x3f00,
134 /* special values used in isolation */
135 FV_NONEXIST = 0x8000, /* closed but not freed (unmounted) */
136 FV_OPENSYSROOT = 0xc001, /* open sysroot, volume 0 not mounted */
137};
138
139
140/** Common data structures used throughout **/
141
142/* basic file information about its location */
143struct file_base_info
144{
145 union {
146#ifdef HAVE_MULTIVOLUME
147 int volume; /* file's volume (overlaps fatfile.volume) */
148#endif
149#if CONFIG_PLATFORM & PLATFORM_NATIVE
150 struct fat_file fatfile; /* FS driver file info */
151#endif
152 };
153#ifdef HAVE_DIRCACHE
154 struct dircache_file dcfile; /* dircache file info */
155#endif
156};
157
158#define BASEINFO_VOL(infop) \
159 IF_MV_VOL((infop)->volume)
160
161/* open files binding item */
162struct file_base_binding
163{
164 struct ll_node node; /* list item node (first!) */
165 struct file_base_info info; /* basic file info */
166};
167
168#define BASEBINDING_VOL(bindp) \
169 BASEINFO_VOL(&(bindp)->info)
170
171/* directory scanning position info */
172struct dirscan_info
173{
174#if CONFIG_PLATFORM & PLATFORM_NATIVE
175 struct fat_dirscan_info fatscan; /* FS driver scan info */
176#endif
177#ifdef HAVE_DIRCACHE
178 struct dircache_file dcscan; /* dircache scan info */
179#endif
180};
181
182/* describes the file as an open stream */
183struct filestr_base
184{
185 struct ll_node node; /* list item node (first!) */
186 uint16_t flags; /* FD_* bits of this stream */
187 uint16_t unused; /* not used */
188 struct filestr_cache cache; /* stream-local cache */
189 struct filestr_cache *cachep; /* the cache in use (local or shared) */
190 struct file_base_info *infop; /* base file information */
191 struct fat_filestr fatstr; /* FS driver information */
192 struct file_base_binding *bindp; /* common binding for file/dir */
193 struct mutex *mtx; /* serialization for this stream */
194};
195
196void filestr_base_init(struct filestr_base *stream);
197void filestr_base_destroy(struct filestr_base *stream);
198void filestr_alloc_cache(struct filestr_base *stream);
199void filestr_free_cache(struct filestr_base *stream);
200void filestr_assign_cache(struct filestr_base *stream,
201 struct filestr_cache *cachep);
202void filestr_copy_cache(struct filestr_base *stream,
203 struct filestr_cache *cachep);
204void filestr_discard_cache(struct filestr_base *stream);
205
206/* allocates a cache buffer if needed and returns the cache pointer */
207static inline struct filestr_cache *
208filestr_get_cache(struct filestr_base *stream)
209{
210 struct filestr_cache *cachep = stream->cachep;
211
212 if (!cachep->buffer)
213 filestr_alloc_cache(stream);
214
215 return cachep;
216}
217
218static inline void filestr_lock(struct filestr_base *stream)
219{
220 mutex_lock(stream->mtx);
221}
222
223static inline void filestr_unlock(struct filestr_base *stream)
224{
225 mutex_unlock(stream->mtx);
226}
227
228/* stream lock doesn't have to be used if getting RW lock writer access */
229#define FILESTR_WRITER 0
230#define FILESTR_READER 1
231
232#define FILESTR_LOCK(type, stream) \
233 ({ if (FILESTR_##type) filestr_lock(stream); })
234
235#define FILESTR_UNLOCK(type, stream) \
236 ({ if (FILESTR_##type) filestr_unlock(stream); })
237
238#define ATTR_PREFIX (0x8000) /* out of the way of all ATTR_* bits */
239
240/* structure to return detailed information about what you opened */
241struct path_component_info
242{
243 const char *name; /* pointer to name within 'path' */
244 size_t length; /* length of component within 'path' */
245 file_size_t filesize; /* size of the opened file (0 if dir) */
246 unsigned int attr; /* attributes of this component */
247 struct file_base_info *prefixp; /* base info to check as prefix (IN) */
248 struct file_base_info parentinfo; /* parent directory info of file */
249};
250
251int open_stream_internal(const char *path, unsigned int callflags,
252 struct filestr_base *stream,
253 struct path_component_info *compinfo);
254int close_stream_internal(struct filestr_base *stream);
255int create_stream_internal(struct file_base_info *parentinfop,
256 const char *basename, size_t length,
257 unsigned int attr, unsigned int callflags,
258 struct filestr_base *stream);
259int remove_stream_internal(const char *path, struct filestr_base *stream,
260 unsigned int callflags);
261int test_stream_exists_internal(const char *path, unsigned int callflags);
262
263int open_noiso_internal(const char *path, int oflag); /* file.c */
264
265struct dirent;
266int uncached_readdir_dirent(struct filestr_base *stream,
267 struct dirscan_info *scanp,
268 struct dirent *entry);
269void uncached_rewinddir_dirent(struct dirscan_info *scanp);
270
271int uncached_readdir_internal(struct filestr_base *stream,
272 struct file_base_info *infop,
273 struct fat_direntry *fatent);
274void uncached_rewinddir_internal(struct file_base_info *infop);
275
276int test_dir_empty_internal(struct filestr_base *stream);
277
278struct dirinfo_internal
279{
280 unsigned int attr;
281 file_size_t size;
282 uint16_t wrtdate;
283 uint16_t wrttime;
284};
285
286/** Synchronization used throughout **/
287
288/* acquire the filesystem lock as READER */
289static inline void file_internal_lock_READER(void)
290{
291 extern struct mrsw_lock file_internal_mrsw;
292 mrsw_read_acquire(&file_internal_mrsw);
293}
294
295/* release the filesystem lock as READER */
296static inline void file_internal_unlock_READER(void)
297{
298 extern struct mrsw_lock file_internal_mrsw;
299 mrsw_read_release(&file_internal_mrsw);
300}
301
302/* acquire the filesystem lock as WRITER */
303static inline void file_internal_lock_WRITER(void)
304{
305 extern struct mrsw_lock file_internal_mrsw;
306 mrsw_write_acquire(&file_internal_mrsw);
307}
308
309/* release the filesystem lock as WRITER */
310static inline void file_internal_unlock_WRITER(void)
311{
312 extern struct mrsw_lock file_internal_mrsw;
313 mrsw_write_release(&file_internal_mrsw);
314}
315
316#define ERRNO 0 /* maintain errno value */
317#define RC 0 /* maintain rc value */
318
319/* NOTES: if _errno is a non-constant expression, it must set an error
320 * number and not return the ERRNO constant which will merely set
321 * errno to zero, not preserve the current value; if you must set
322 * errno to zero, set it explicitly, not in the macro
323 *
324 * if _rc is constant-expression evaluation to 'RC', then rc will
325 * NOT be altered; i.e. if you must set rc to zero, set it explicitly,
326 * not in the macro
327 */
328
329/* set errno and rc and proceed to the "file_error:" label */
330#define FILE_ERROR(_errno, _rc) \
331 ({ __builtin_constant_p(_errno) ? \
332 ({ if ((_errno) != ERRNO) errno = (_errno); }) : \
333 ({ errno = (_errno); }); \
334 __builtin_constant_p(_rc) ? \
335 ({ if ((_rc) != RC) rc = (_rc); }) : \
336 ({ rc = (_rc); }); \
337 goto file_error; })
338
339/* set errno and return a value at the point of invocation */
340#define FILE_ERROR_RETURN(_errno, _rc...) \
341 ({ __builtin_constant_p(_errno) ? \
342 ({ if ((_errno) != ERRNO) errno = (_errno); }) : \
343 ({ errno = (_errno); }); \
344 return _rc; })
345
346
347/** Misc. stuff **/
348
349/* iterate through all the volumes if volume < 0, else just the given volume */
350#define FOR_EACH_VOLUME(volume, i) \
351 for (int i = (IF_MV_VOL(volume) >= 0 ? IF_MV_VOL(volume) : 0), \
352 _end = (IF_MV_VOL(volume) >= 0 ? i : NUM_VOLUMES-1); \
353 i <= _end; i++)
354
355/* return a pointer to the static struct fat_direntry */
356static inline struct fat_direntry *get_dir_fatent(void)
357{
358 extern struct fat_direntry dir_fatent;
359 return &dir_fatent;
360}
361
362void iso_decode_d_name(char *d_name);
363
364#ifdef HAVE_DIRCACHE
365void empty_dirent(struct dirent *entry);
366void fill_dirinfo_native(struct dirinfo_native *din);
367#endif /* HAVE_DIRCACHE */
368
369void filesystem_init(void) INIT_ATTR;
370
371#endif /* _FILE_INTERNAL_H_ */
diff --git a/firmware/include/fileobj_mgr.h b/firmware/include/fileobj_mgr.h
new file mode 100644
index 0000000000..c90a59bea0
--- /dev/null
+++ b/firmware/include/fileobj_mgr.h
@@ -0,0 +1,56 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _FILEOBJ_MGR_H_
22#define _FILEOBJ_MGR_H_
23
24#include "file_internal.h"
25
26void file_binding_insert_first(struct file_base_binding *bindp);
27void file_binding_insert_last(struct file_base_binding *bindp);
28void file_binding_remove(struct file_base_binding *bindp);
29void file_binding_remove_next(struct file_base_binding *prevp,
30 struct file_base_binding *bindp);
31
32void fileobj_fileop_open(struct filestr_base *stream,
33 const struct file_base_info *srcinfop,
34 unsigned int callflags);
35void fileobj_fileop_close(struct filestr_base *stream);
36void fileobj_fileop_create(struct filestr_base *stream,
37 const struct file_base_info *srcinfop,
38 unsigned int callflags);
39void fileobj_fileop_rename(struct filestr_base *stream,
40 const struct file_base_info *oldinfop);
41void fileobj_fileop_remove(struct filestr_base *stream,
42 const struct file_base_info *oldinfop);
43void fileobj_fileop_sync(struct filestr_base *stream);
44void fileobj_fileop_truncate(struct filestr_base *stream);
45extern void ftruncate_internal_callback(struct filestr_base *stream,
46 struct filestr_base *s);
47
48file_size_t * fileobj_get_sizep(const struct filestr_base *stream);
49unsigned int fileobj_get_flags(const struct filestr_base *stream);
50void fileobj_change_flags(struct filestr_base *stream,
51 unsigned int flags, unsigned int mask);
52void fileobj_mgr_unmount(IF_MV_NONVOID(int volume));
53
54void fileobj_mgr_init(void) INIT_ATTR;
55
56#endif /* _FILEOBJ_MGR_H_ */
diff --git a/firmware/include/filesystem-native.h b/firmware/include/filesystem-native.h
new file mode 100644
index 0000000000..640e179890
--- /dev/null
+++ b/firmware/include/filesystem-native.h
@@ -0,0 +1,107 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 by Björn Stenberg
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _FILESYSTEM_NATIVE_H_
22#define _FILESYSTEM_NATIVE_H_
23
24#if defined(PLUGIN) || defined(CODEC)
25#define FILEFUNCTIONS_DECLARED
26#define FILEFUNCTIONS_DEFINED
27#define DIRFUNCTIONS_DECLARED
28#define DIRFUNCTIONS_DEFINED
29#endif /* PLUGIN || CODEC */
30
31#define FS_PREFIX(_x_) _x_
32#endif /* _FILESYSTEM_NATIVE_H_ */
33
34#ifdef _FILE_H_
35#ifndef _FILESYSTEM_NATIVE__FILE_H_
36#define _FILESYSTEM_NATIVE__FILE_H_
37
38#ifdef RB_FILESYSTEM_OS
39#define FILEFUNCTIONS_DEFINED
40#endif
41
42#ifndef FILEFUNCTIONS_DECLARED
43#define __OPEN_MODE_ARG
44#define __CREAT_MODE_ARG
45
46int open(const char *name, int oflag);
47int creat(const char *name);
48int close(int fildes);
49int ftruncate(int fildes, off_t length);
50int fsync(int fildes);
51off_t lseek(int fildes, off_t offset, int whence);
52ssize_t read(int fildes, void *buf, size_t nbyte);
53ssize_t write(int fildes, const void *buf, size_t nbyte);
54int remove(const char *path);
55int rename(const char *old, const char *new);
56off_t filesize(int fildes);
57int fsamefile(int fildes1, int fildes2);
58int relate(const char *path1, const char *path2);
59bool file_exists(const char *path);
60#endif /* !FILEFUNCTIONS_DECLARED */
61
62#if !defined(RB_FILESYSTEM_OS) && !defined (FILEFUNCTIONS_DEFINED)
63#define open(path, oflag, ...) open(path, oflag)
64#define creat(path, mode) creat(path)
65#endif /* FILEFUNCTIONS_DEFINED */
66
67#endif /* _FILESYSTEM_NATIVE__FILE_H_ */
68#endif /* _FILE_H_ */
69
70#ifdef _DIR_H_
71#ifndef _FILESYSTEM_NATIVE__DIR_H_
72#define _FILESYSTEM_NATIVE__DIR_H_
73
74#define DIRENT dirent
75struct dirent;
76
77struct dirinfo_native
78{
79 unsigned int attr;
80 off_t size;
81 uint16_t wrtdate;
82 uint16_t wrttime;
83};
84
85typedef struct {} DIR;
86
87#ifndef DIRFUNCTIONS_DECLARED
88#define __MKDIR_MODE_ARG
89
90#ifdef RB_FILESYSTEM_OS
91#define DIRFUNCTIONS_DEFINED
92#endif
93
94DIR * opendir(const char *dirname);
95struct dirent * readdir(DIR *dirp);
96int readdir_r(DIR *dirp, struct dirent *entry,
97 struct dirent **result);
98void rewinddir(DIR *dirp);
99int closedir(DIR *dirp);
100int mkdir(const char *path);
101int rmdir(const char *path);
102int samedir(DIR *dirp1, DIR *dirp2);
103bool dir_exists(const char *dirname);
104#endif /* !DIRFUNCTIONS_DECLARED */
105
106#endif /* _FILESYSTEM_NATIVE__DIR_H_ */
107#endif /* _DIR_H_ */
diff --git a/firmware/include/fs_attr.h b/firmware/include/fs_attr.h
new file mode 100644
index 0000000000..0e1d25e1de
--- /dev/null
+++ b/firmware/include/fs_attr.h
@@ -0,0 +1,39 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2007 by Kévin Ferrare
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _FS_ATTR_H_
22#define _FS_ATTR_H_
23
24#include <stdbool.h>
25
26#undef MAX_PATH /* this avoids problems when building simulator */
27#define MAX_PATH 260
28
29/* also used by fat.c so values must not change */
30#define ATTR_READ_ONLY 0x01
31#define ATTR_HIDDEN 0x02
32#define ATTR_SYSTEM 0x04
33#define ATTR_VOLUME_ID 0x08
34#define ATTR_DIRECTORY 0x10
35#define ATTR_ARCHIVE 0x20
36#define ATTR_VOLUME 0x40 /* this is a volume, not a real directory */
37#define ATTR_LINK 0x80
38
39#endif /* _FS_ATTR_H_ */
diff --git a/firmware/include/rb-loader.h b/firmware/include/rb-loader.h
index e1e756dba9..86c5026af9 100644
--- a/firmware/include/rb-loader.h
+++ b/firmware/include/rb-loader.h
@@ -18,5 +18,4 @@
18 * 18 *
19 ****************************************************************************/ 19 ****************************************************************************/
20 20
21const char *rb_strerror(int8_t errno);
22int load_firmware(unsigned char* buf, const char* firmware, int buffer_size); 21int load_firmware(unsigned char* buf, const char* firmware, int buffer_size);
diff --git a/firmware/include/rbunicode.h b/firmware/include/rbunicode.h
index 3c28b3031e..077029304d 100644
--- a/firmware/include/rbunicode.h
+++ b/firmware/include/rbunicode.h
@@ -51,7 +51,8 @@ enum codepages {
51 KSX_1001, /* Korean */ 51 KSX_1001, /* Korean */
52 BIG_5, /* Trad. Chinese */ 52 BIG_5, /* Trad. Chinese */
53 UTF_8, /* Unicode */ 53 UTF_8, /* Unicode */
54 NUM_CODEPAGES 54 NUM_CODEPAGES,
55 INIT_CODEPAGE = ISO_8859_1,
55}; 56};
56 57
57#else /* !HAVE_LCD_BITMAP, reduced support */ 58#else /* !HAVE_LCD_BITMAP, reduced support */
@@ -65,7 +66,8 @@ enum codepages {
65 WIN_1250, /* Central European */ 66 WIN_1250, /* Central European */
66 WIN_1252, /* Western European */ 67 WIN_1252, /* Western European */
67 UTF_8, /* Unicode */ 68 UTF_8, /* Unicode */
68 NUM_CODEPAGES 69 NUM_CODEPAGES,
70 INIT_CODEPAGE = ISO_8859_1,
69}; 71};
70 72
71#endif 73#endif
@@ -78,9 +80,19 @@ unsigned char* utf16BEdecode(const unsigned char *utf16, unsigned char *utf8, in
78unsigned long utf8length(const unsigned char *utf8); 80unsigned long utf8length(const unsigned char *utf8);
79const unsigned char* utf8decode(const unsigned char *utf8, unsigned short *ucs); 81const unsigned char* utf8decode(const unsigned char *utf8, unsigned short *ucs);
80void set_codepage(int cp); 82void set_codepage(int cp);
83int get_codepage(void);
81int utf8seek(const unsigned char* utf8, int offset); 84int utf8seek(const unsigned char* utf8, int offset);
82const char* get_codepage_name(int cp); 85const char* get_codepage_name(int cp);
83#if defined(APPLICATION) && defined(__linux__) 86#ifdef APPLICATION
87#if defined(__linux__)
84const char *get_current_codepage_name_linux(void); 88const char *get_current_codepage_name_linux(void);
85#endif 89#endif
90#endif /* APPLICATION */
91
92#if 0 /* not needed just now */
93void unicode_init(void);
94#else
95#define unicode_init() do {} while (0)
96#endif
97
86#endif /* _RBUNICODE_H_ */ 98#endif /* _RBUNICODE_H_ */
diff --git a/firmware/kernel/include/mutex.h b/firmware/kernel/include/mutex.h
index b74bfe23f5..4778eb7f11 100644
--- a/firmware/kernel/include/mutex.h
+++ b/firmware/kernel/include/mutex.h
@@ -31,21 +31,12 @@ struct mutex
31 struct blocker blocker; /* priority inheritance info 31 struct blocker blocker; /* priority inheritance info
32 for waiters and owner*/ 32 for waiters and owner*/
33 IF_COP( struct corelock cl; ) /* multiprocessor sync */ 33 IF_COP( struct corelock cl; ) /* multiprocessor sync */
34#ifdef HAVE_PRIORITY_SCHEDULING
35 bool no_preempt;
36#endif
37}; 34};
38 35
39extern void mutex_init(struct mutex *m); 36extern void mutex_init(struct mutex *m);
40extern void mutex_lock(struct mutex *m); 37extern void mutex_lock(struct mutex *m);
41extern void mutex_unlock(struct mutex *m); 38extern void mutex_unlock(struct mutex *m);
42#ifdef HAVE_PRIORITY_SCHEDULING 39#ifndef HAVE_PRIORITY_SCHEDULING
43/* Deprecated temporary function to disable mutex preempting a thread on
44 * unlock - firmware/drivers/fat.c and a couple places in apps/buffering.c -
45 * reliance on it is a bug! */
46static inline void mutex_set_preempt(struct mutex *m, bool preempt)
47 { m->no_preempt = !preempt; }
48#else
49/* Deprecated but needed for now - firmware/drivers/ata_mmc.c */ 40/* Deprecated but needed for now - firmware/drivers/ata_mmc.c */
50static inline bool mutex_test(const struct mutex *m) 41static inline bool mutex_test(const struct mutex *m)
51 { return m->blocker.thread != NULL; } 42 { return m->blocker.thread != NULL; }
diff --git a/firmware/kernel/mutex.c b/firmware/kernel/mutex.c
index 876b704b51..cb9e6816b8 100644
--- a/firmware/kernel/mutex.c
+++ b/firmware/kernel/mutex.c
@@ -33,9 +33,6 @@ void mutex_init(struct mutex *m)
33 wait_queue_init(&m->queue); 33 wait_queue_init(&m->queue);
34 m->recursion = 0; 34 m->recursion = 0;
35 blocker_init(&m->blocker); 35 blocker_init(&m->blocker);
36#ifdef HAVE_PRIORITY_SCHEDULING
37 m->no_preempt = false;
38#endif
39 corelock_init(&m->cl); 36 corelock_init(&m->cl);
40} 37}
41 38
@@ -115,7 +112,7 @@ void mutex_unlock(struct mutex *m)
115 corelock_unlock(&m->cl); 112 corelock_unlock(&m->cl);
116 113
117#ifdef HAVE_PRIORITY_SCHEDULING 114#ifdef HAVE_PRIORITY_SCHEDULING
118 if((result & THREAD_SWITCH) && !m->no_preempt) 115 if(result & THREAD_SWITCH)
119 switch_thread(); 116 switch_thread();
120#endif 117#endif
121 (void)result; 118 (void)result;
diff --git a/firmware/libc/include/errno.h b/firmware/libc/include/errno.h
index 9df261db9f..2ddf036c92 100644
--- a/firmware/libc/include/errno.h
+++ b/firmware/libc/include/errno.h
@@ -88,6 +88,7 @@ extern int * __errno(void);
88#define ELBIN 75 /* Inode is remote (not really error) */ 88#define ELBIN 75 /* Inode is remote (not really error) */
89#define EDOTDOT 76 /* Cross mount point (not really error) */ 89#define EDOTDOT 76 /* Cross mount point (not really error) */
90#define EBADMSG 77 /* Trying to read unreadable message */ 90#define EBADMSG 77 /* Trying to read unreadable message */
91#define EOVERFLOW 78 /* Value too large to be stored in data type */
91#define ENOTUNIQ 80 /* Given log. name not unique */ 92#define ENOTUNIQ 80 /* Given log. name not unique */
92#define EBADFD 81 /* f.d. invalid for this operation */ 93#define EBADFD 81 /* f.d. invalid for this operation */
93#define EREMCHG 82 /* Remote address changed */ 94#define EREMCHG 82 /* Remote address changed */
diff --git a/firmware/libc/include/fcntl.h b/firmware/libc/include/fcntl.h
index 34740c9ca2..ec53d728cf 100644
--- a/firmware/libc/include/fcntl.h
+++ b/firmware/libc/include/fcntl.h
@@ -23,18 +23,20 @@
23#define __FCNTL_H__ 23#define __FCNTL_H__
24 24
25#ifndef O_RDONLY 25#ifndef O_RDONLY
26#define O_RDONLY 0 26#define O_RDONLY 0x0000 /* open for reading only */
27#define O_WRONLY 1 27#define O_WRONLY 0x0001 /* open for writing only */
28#define O_RDWR 2 28#define O_RDWR 0x0002 /* open for reading and writing */
29#define O_CREAT 4 29#define O_ACCMODE 0x0003 /* mask for above modes */
30#define O_APPEND 8 30#define O_APPEND 0x0008 /* set append mode */
31#define O_TRUNC 0x10 31#define O_CREAT 0x0200 /* create if nonexistent */
32#define O_TRUNC 0x0400 /* truncate to zero length */
33#define O_EXCL 0x0800 /* error if already exists */
32#endif 34#endif
33 35
34#ifndef SEEK_SET 36#ifndef SEEK_SET
35#define SEEK_SET 0 37#define SEEK_SET 0 /* set file offset to offset */
36#define SEEK_CUR 1 38#define SEEK_CUR 1 /* set file offset to current plus offset */
37#define SEEK_END 2 39#define SEEK_END 2 /* set file offset to EOF plus offset */
38#endif 40#endif
39 41
40#endif /* __FCNTL_H__ */ 42#endif /* __FCNTL_H__ */
diff --git a/firmware/libc/mktime.c b/firmware/libc/mktime.c
index a52381ede5..eaa017122a 100644
--- a/firmware/libc/mktime.c
+++ b/firmware/libc/mktime.c
@@ -23,7 +23,6 @@
23#include <time.h> 23#include <time.h>
24#include "config.h" 24#include "config.h"
25 25
26#if CONFIG_RTC
27/* mktime() code taken from lynx-2.8.5 source, written 26/* mktime() code taken from lynx-2.8.5 source, written
28 by Philippe De Muyter <phdm@macqel.be> */ 27 by Philippe De Muyter <phdm@macqel.be> */
29time_t mktime(struct tm *t) 28time_t mktime(struct tm *t)
@@ -58,4 +57,3 @@ time_t mktime(struct tm *t)
58 result += t->tm_sec; 57 result += t->tm_sec;
59 return(result); 58 return(result);
60} 59}
61#endif
diff --git a/firmware/storage.c b/firmware/storage.c
index 8740e4ebd2..7ef7428854 100644
--- a/firmware/storage.c
+++ b/firmware/storage.c
@@ -190,6 +190,15 @@ int storage_num_drives(void)
190 return num_drives; 190 return num_drives;
191} 191}
192 192
193int storage_driver_type(int drive)
194{
195 if (drive >= num_drives)
196 return -1;
197
198 unsigned int bit = (storage_drivers[drive] & DRIVER_MASK)>>DRIVER_OFFSET;
199 return bit ? find_first_set_bit(bit) : -1;
200}
201
193int storage_init(void) 202int storage_init(void)
194{ 203{
195 int rc=0; 204 int rc=0;
diff --git a/firmware/target/arm/as3525/sd-as3525.c b/firmware/target/arm/as3525/sd-as3525.c
index c80c7f7491..ead40eac3c 100644
--- a/firmware/target/arm/as3525/sd-as3525.c
+++ b/firmware/target/arm/as3525/sd-as3525.c
@@ -449,21 +449,12 @@ static void sd_thread(void)
449 { 449 {
450#ifdef HAVE_HOTSWAP 450#ifdef HAVE_HOTSWAP
451 case SYS_HOTSWAP_INSERTED: 451 case SYS_HOTSWAP_INSERTED:
452 case SYS_HOTSWAP_EXTRACTED: 452 case SYS_HOTSWAP_EXTRACTED:;
453 { 453 int success = 1;
454 int microsd_init = 1;
455 fat_lock(); /* lock-out FAT activity first -
456 prevent deadlocking via disk_mount that
457 would cause a reverse-order attempt with
458 another thread */
459 mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
460 into driver that bypass the fat cache */
461 454
462 /* We now have exclusive control of fat cache and ata */ 455 disk_unmount(SD_SLOT_AS3525); /* release "by force" */
463 456
464 disk_unmount(SD_SLOT_AS3525); /* release "by force", ensure file 457 mutex_lock(&sd_mtx); /* lock-out card activity */
465 descriptors aren't leaked and any busy
466 ones are invalid if mounting */
467 458
468 /* Force card init for new card, re-init for re-inserted one or 459 /* Force card init for new card, re-init for re-inserted one or
469 * clear if the last attempt to init failed with an error. */ 460 * clear if the last attempt to init failed with an error. */
@@ -471,29 +462,32 @@ static void sd_thread(void)
471 462
472 if (ev.id == SYS_HOTSWAP_INSERTED) 463 if (ev.id == SYS_HOTSWAP_INSERTED)
473 { 464 {
465 success = 0;
474 sd_enable(true); 466 sd_enable(true);
475 init_pl180_controller(SD_SLOT_AS3525); 467 init_pl180_controller(SD_SLOT_AS3525);
476 microsd_init = sd_init_card(SD_SLOT_AS3525); 468 int rc = sd_init_card(SD_SLOT_AS3525);
477 if (microsd_init < 0) /* initialisation failed */ 469 sd_enable(false);
478 panicf("microSD init failed : %d", microsd_init); 470 if (rc >= 0)
479 471 success = 2;
480 microsd_init = disk_mount(SD_SLOT_AS3525); /* 0 if fail */ 472 else /* initialisation failed */
473 panicf("microSD init failed : %d", rc);
481 } 474 }
482 475
476 mutex_unlock(&sd_mtx);
477
478 if (success > 1)
479 success = disk_mount(SD_SLOT_AS3525); /* 0 if fail */
480
483 /* 481 /*
484 * Mount succeeded, or this was an EXTRACTED event, 482 * Mount succeeded, or this was an EXTRACTED event,
485 * in both cases notify the system about the changed filesystems 483 * in both cases notify the system about the changed filesystems
486 */ 484 */
487 if (microsd_init) 485 if (success)
488 queue_broadcast(SYS_FS_CHANGED, 0); 486 queue_broadcast(SYS_FS_CHANGED, 0);
489 487
490 /* Access is now safe */
491 mutex_unlock(&sd_mtx);
492 fat_unlock();
493 sd_enable(false);
494 }
495 break; 488 break;
496#endif 489#endif /* HAVE_HOTSWAP */
490
497 case SYS_TIMEOUT: 491 case SYS_TIMEOUT:
498 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ))) 492 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
499 { 493 {
diff --git a/firmware/target/arm/as3525/sd-as3525v2.c b/firmware/target/arm/as3525/sd-as3525v2.c
index ae3dde4495..b4ac40152b 100644
--- a/firmware/target/arm/as3525/sd-as3525v2.c
+++ b/firmware/target/arm/as3525/sd-as3525v2.c
@@ -598,21 +598,13 @@ static void sd_thread(void)
598 { 598 {
599#ifdef HAVE_HOTSWAP 599#ifdef HAVE_HOTSWAP
600 case SYS_HOTSWAP_INSERTED: 600 case SYS_HOTSWAP_INSERTED:
601 case SYS_HOTSWAP_EXTRACTED: 601 case SYS_HOTSWAP_EXTRACTED:;
602 { 602 int success = 1;
603 int changed = 1; 603
604 fat_lock(); /* lock-out FAT activity first - 604 disk_unmount(SD_SLOT_AS3525); /* release "by force" */
605 prevent deadlocking via disk_mount that 605
606 would cause a reverse-order attempt with 606 mutex_lock(&sd_mtx); /* lock-out card activity */
607 another thread */ 607
608 mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
609 into driver that bypass the fat cache */
610
611 /* We now have exclusive control of fat cache and ata */
612
613 disk_unmount(SD_SLOT_AS3525); /* release "by force", ensure file
614 descriptors aren't leaked and any busy
615 ones are invalid if mounting */
616 /* Force card init for new card, re-init for re-inserted one or 608 /* Force card init for new card, re-init for re-inserted one or
617 * clear if the last attempt to init failed with an error. */ 609 * clear if the last attempt to init failed with an error. */
618 card_info[SD_SLOT_AS3525].initialized = 0; 610 card_info[SD_SLOT_AS3525].initialized = 0;
@@ -620,24 +612,25 @@ static void sd_thread(void)
620 if (ev.id == SYS_HOTSWAP_INSERTED) 612 if (ev.id == SYS_HOTSWAP_INSERTED)
621 { 613 {
622 sd_enable(true); 614 sd_enable(true);
623 changed = (sd_init_card(SD_SLOT_AS3525) == 0) && disk_mount(SD_SLOT_AS3525); /* 0 if fail */ 615 success = sd_init_card(SD_SLOT_AS3525) == 0 ? 2 : 0;
616 sd_enable(false);
624 } 617 }
625 618
619 mutex_unlock(&sd_mtx);
620
621 if (success > 1)
622 success = disk_mount(SD_SLOT_AS3525); /* 0 if fail */
623
626 /* 624 /*
627 * Mount succeeded, or this was an EXTRACTED event, 625 * Mount succeeded, or this was an EXTRACTED event,
628 * in both cases notify the system about the changed filesystems 626 * in both cases notify the system about the changed filesystems
629 */ 627 */
630 if (changed) 628 if (success)
631 queue_broadcast(SYS_FS_CHANGED, 0); 629 queue_broadcast(SYS_FS_CHANGED, 0);
632 630
633 sd_enable(false);
634
635 /* Access is now safe */
636 mutex_unlock(&sd_mtx);
637 fat_unlock();
638 }
639 break; 631 break;
640#endif 632#endif /* HAVE_HOTSWAP */
633
641 case SYS_TIMEOUT: 634 case SYS_TIMEOUT:
642 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ))) 635 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
643 { 636 {
diff --git a/firmware/target/arm/imx233/sdmmc-imx233.c b/firmware/target/arm/imx233/sdmmc-imx233.c
index 8f293543ab..87548aef53 100644
--- a/firmware/target/arm/imx233/sdmmc-imx233.c
+++ b/firmware/target/arm/imx233/sdmmc-imx233.c
@@ -766,14 +766,7 @@ static void sdmmc_thread(void)
766 case SYS_HOTSWAP_INSERTED: 766 case SYS_HOTSWAP_INSERTED:
767 case SYS_HOTSWAP_EXTRACTED: 767 case SYS_HOTSWAP_EXTRACTED:
768 { 768 {
769 int microsd_init = 1; 769 int microsd_init = ev.id == SYS_HOTSWAP_INSERTED ? 0 : 1;
770 /* lock-out FAT activity first -
771 * prevent deadlocking via disk_mount that
772 * would cause a reverse-order attempt with
773 * another thread */
774#ifdef HAVE_HOTSWAP
775 fat_lock();
776#endif
777 770
778 /* We now have exclusive control of fat cache and sd. 771 /* We now have exclusive control of fat cache and sd.
779 * Release "by force", ensure file 772 * Release "by force", ensure file
@@ -785,35 +778,37 @@ static void sdmmc_thread(void)
785 /* Skip non-removable drivers */ 778 /* Skip non-removable drivers */
786 if(!sdmmc_removable(drive)) 779 if(!sdmmc_removable(drive))
787 continue; 780 continue;
788 /* lock-out card activity - direct calls 781
789 * into driver that bypass the fat cache */
790 mutex_lock(&mutex[drive]);
791 disk_unmount(sd_first_drive + sd_drive); 782 disk_unmount(sd_first_drive + sd_drive);
783
784 mutex_lock(&mutex[drive]); /* lock-out card activity */
785
792 /* Force card init for new card, re-init for re-inserted one or 786 /* Force card init for new card, re-init for re-inserted one or
793 * clear if the last attempt to init failed with an error. */ 787 * clear if the last attempt to init failed with an error. */
794 SDMMC_INFO(sd_map[sd_drive]).initialized = 0; 788 SDMMC_INFO(sd_map[sd_drive]).initialized = 0;
795 789
790 int rc = -1;
796 if(ev.id == SYS_HOTSWAP_INSERTED) 791 if(ev.id == SYS_HOTSWAP_INSERTED)
797 { 792 {
798 microsd_init = init_drive(drive); 793 rc = init_drive(drive);
799 if(microsd_init < 0) /* initialisation failed */ 794 if(rc < 0) /* initialisation failed */
800 panicf("%s init failed : %d", SDMMC_CONF(sd_map[sd_drive]).name, microsd_init); 795 panicf("%s init failed : %d", SDMMC_CONF(sd_map[sd_drive]).name, rc);
801
802 microsd_init = disk_mount(sd_first_drive + sd_drive); /* 0 if fail */
803 } 796 }
804 /* 797
805 * Mount succeeded, or this was an EXTRACTED event,
806 * in both cases notify the system about the changed filesystems
807 */
808 if(microsd_init)
809 queue_broadcast(SYS_FS_CHANGED, 0);
810 /* unlock card */ 798 /* unlock card */
811 mutex_unlock(&mutex[drive]); 799 mutex_unlock(&mutex[drive]);
800
801 if (rc >= 0)
802 microsd_init += disk_mount(sd_first_drive + sd_drive); /* 0 if fail */
812 } 803 }
813 /* Access is now safe */ 804 /* Access is now safe */
814#ifdef HAVE_HOTSWAP 805 /*
815 fat_unlock(); 806 * One or more mounts succeeded, or this was an EXTRACTED event,
816#endif 807 * in both cases notify the system about the changed filesystems
808 */
809 if(microsd_init)
810 queue_broadcast(SYS_FS_CHANGED, 0);
811
817 break; 812 break;
818 } 813 }
819#endif 814#endif
diff --git a/firmware/target/arm/pp/ata-sd-pp.c b/firmware/target/arm/pp/ata-sd-pp.c
index bcf8a660c2..2a11b40fee 100644
--- a/firmware/target/arm/pp/ata-sd-pp.c
+++ b/firmware/target/arm/pp/ata-sd-pp.c
@@ -19,7 +19,6 @@
19 * 19 *
20 ****************************************************************************/ 20 ****************************************************************************/
21#include "config.h" /* for HAVE_MULTIDRIVE */ 21#include "config.h" /* for HAVE_MULTIDRIVE */
22#include "fat.h"
23#include "sdmmc.h" 22#include "sdmmc.h"
24#include "gcc_extensions.h" 23#include "gcc_extensions.h"
25#ifdef HAVE_HOTSWAP 24#ifdef HAVE_HOTSWAP
@@ -1125,35 +1124,28 @@ static void sd_thread(void)
1125 { 1124 {
1126#ifdef HAVE_HOTSWAP 1125#ifdef HAVE_HOTSWAP
1127 case SYS_HOTSWAP_INSERTED: 1126 case SYS_HOTSWAP_INSERTED:
1128 case SYS_HOTSWAP_EXTRACTED: 1127 case SYS_HOTSWAP_EXTRACTED:;
1129 fat_lock(); /* lock-out FAT activity first - 1128 int success = 1;
1130 prevent deadlocking via disk_mount that
1131 would cause a reverse-order attempt with
1132 another thread */
1133 mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
1134 into driver that bypass the fat cache */
1135 1129
1136 /* We now have exclusive control of fat cache and ata */ 1130 disk_unmount(sd_first_drive+1); /* release "by force" */
1137 1131
1138 disk_unmount(sd_first_drive+1); /* release "by force", ensure file 1132 mutex_lock(&sd_mtx); /* lock-out card activity */
1139 descriptors aren't leaked and any busy
1140 ones are invalid if mounting */
1141 1133
1142 /* Force card init for new card, re-init for re-inserted one or 1134 /* Force card init for new card, re-init for re-inserted one or
1143 * clear if the last attempt to init failed with an error. */ 1135 * clear if the last attempt to init failed with an error. */
1144 card_info[1].initialized = 0; 1136 card_info[1].initialized = 0;
1145 sd_status[1].retry = 0; 1137 sd_status[1].retry = 0;
1146 1138
1147 if (ev.id == SYS_HOTSWAP_INSERTED)
1148 disk_mount(sd_first_drive+1);
1149
1150 queue_broadcast(SYS_FS_CHANGED, 0);
1151
1152 /* Access is now safe */ 1139 /* Access is now safe */
1153 mutex_unlock(&sd_mtx); 1140 mutex_unlock(&sd_mtx);
1154 fat_unlock(); 1141
1142 if (ev.id == SYS_HOTSWAP_INSERTED)
1143 success = disk_mount(sd_first_drive+1); /* 0 if fail */
1144
1145 if (success)
1146 queue_broadcast(SYS_FS_CHANGED, 0);
1155 break; 1147 break;
1156#endif 1148#endif /* HAVE_HOTSWAP */
1157 case SYS_TIMEOUT: 1149 case SYS_TIMEOUT:
1158 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ))) 1150 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
1159 { 1151 {
diff --git a/firmware/target/arm/rk27xx/sd-rk27xx.c b/firmware/target/arm/rk27xx/sd-rk27xx.c
index deca8a1fa7..9d6821ee38 100644
--- a/firmware/target/arm/rk27xx/sd-rk27xx.c
+++ b/firmware/target/arm/rk27xx/sd-rk27xx.c
@@ -22,7 +22,6 @@
22 ****************************************************************************/ 22 ****************************************************************************/
23 23
24#include "config.h" /* for HAVE_MULTIVOLUME */ 24#include "config.h" /* for HAVE_MULTIVOLUME */
25#include "fat.h"
26#include "thread.h" 25#include "thread.h"
27#include "gcc_extensions.h" 26#include "gcc_extensions.h"
28#include "led.h" 27#include "led.h"
@@ -331,50 +330,45 @@ static void sd_thread(void)
331 { 330 {
332#ifdef HAVE_HOTSWAP 331#ifdef HAVE_HOTSWAP
333 case SYS_HOTSWAP_INSERTED: 332 case SYS_HOTSWAP_INSERTED:
334 case SYS_HOTSWAP_EXTRACTED: 333 case SYS_HOTSWAP_EXTRACTED:;
335 { 334 int success = 1;
336 int microsd_init = 1; 335
337 fat_lock(); /* lock-out FAT activity first - 336 disk_unmount(sd_first_drive); /* release "by force" */
338 prevent deadlocking via disk_mount that 337
339 would cause a reverse-order attempt with 338 mutex_lock(&sd_mtx); /* lock-out card activity */
340 another thread */ 339
341 mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
342 into driver that bypass the fat cache */
343
344 /* We now have exclusive control of fat cache and ata */
345
346 disk_unmount(sd_first_drive); /* release "by force", ensure file
347 descriptors aren't leaked and any busy
348 ones are invalid if mounting */
349 /* Force card init for new card, re-init for re-inserted one or 340 /* Force card init for new card, re-init for re-inserted one or
350 * clear if the last attempt to init failed with an error. */ 341 * clear if the last attempt to init failed with an error. */
351 card_info.initialized = 0; 342 card_info.initialized = 0;
352 343
353 if (ev.id == SYS_HOTSWAP_INSERTED) 344 if (ev.id == SYS_HOTSWAP_INSERTED)
354 { 345 {
346 success = 0;
355 sd_enable(true); 347 sd_enable(true);
356 microsd_init = sd_init_card(sd_first_drive); 348 int rc = sd_init_card(sd_first_drive);
357 if (microsd_init < 0) /* initialisation failed */ 349 sd_enable(false);
358 panicf("microSD init failed : %d", microsd_init); 350 if (rc >= 0)
359 351 success = 2;
360 microsd_init = disk_mount(sd_first_drive); /* 0 if fail */ 352 else /* initialisation failed */
353 panicf("microSD init failed : %d", rc);
361 } 354 }
362 355
356 /* Access is now safe */
357 mutex_unlock(&sd_mtx);
358
359 if (success > 1)
360 success = disk_mount(sd_first_drive); /* 0 if fail */
361
363 /* 362 /*
364 * Mount succeeded, or this was an EXTRACTED event, 363 * Mount succeeded, or this was an EXTRACTED event,
365 * in both cases notify the system about the changed filesystems 364 * in both cases notify the system about the changed filesystems
366 */ 365 */
367 if (microsd_init) 366 if (success)
368 queue_broadcast(SYS_FS_CHANGED, 0); 367 queue_broadcast(SYS_FS_CHANGED, 0);
369 368
370 sd_enable(false);
371
372 /* Access is now safe */
373 mutex_unlock(&sd_mtx);
374 fat_unlock();
375 }
376 break; 369 break;
377#endif 370#endif /* HAVE_HOTSWAP */
371
378 case SYS_TIMEOUT: 372 case SYS_TIMEOUT:
379 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ))) 373 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
380 { 374 {
diff --git a/firmware/target/arm/s3c2440/sd-s3c2440.c b/firmware/target/arm/s3c2440/sd-s3c2440.c
index 6658fa1515..e8de3ac78d 100644
--- a/firmware/target/arm/s3c2440/sd-s3c2440.c
+++ b/firmware/target/arm/s3c2440/sd-s3c2440.c
@@ -34,7 +34,6 @@
34#ifdef HAVE_HOTSWAP 34#ifdef HAVE_HOTSWAP
35#include "sdmmc.h" 35#include "sdmmc.h"
36#include "disk.h" 36#include "disk.h"
37#include "fat.h"
38#endif 37#endif
39#include "dma-target.h" 38#include "dma-target.h"
40#include "system-target.h" 39#include "system-target.h"
@@ -585,48 +584,29 @@ static void sd_thread(void)
585 { 584 {
586#ifdef HAVE_HOTSWAP 585#ifdef HAVE_HOTSWAP
587 case SYS_HOTSWAP_INSERTED: 586 case SYS_HOTSWAP_INSERTED:
588 case SYS_HOTSWAP_EXTRACTED: 587 case SYS_HOTSWAP_EXTRACTED:;
589 {
590 int success = 1; 588 int success = 1;
591 fat_lock(); /* lock-out FAT activity first -
592 prevent deadlocking via disk_mount that
593 would cause a reverse-order attempt with
594 another thread */
595 mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
596 into driver that bypass the fat cache */
597 589
598 /* We now have exclusive control of fat cache and ata */ 590 disk_unmount(0); /* release "by force" */
599 591
600 disk_unmount(0); /* release "by force", ensure file 592 mutex_lock(&sd_mtx); /* lock-out card activity */
601 descriptors aren't leaked and any busy
602 ones are invalid if mounting */
603 593
604 /* Force card init for new card, re-init for re-inserted one or 594 /* Force card init for new card, re-init for re-inserted one or
605 * clear if the last attempt to init failed with an error. */ 595 * clear if the last attempt to init failed with an error. */
606 card_info[0].initialized = 0; 596 card_info[0].initialized = 0;
607 597
598 /* Access is now safe */
599 mutex_unlock(&sd_mtx);
600
608 if (ev.id == SYS_HOTSWAP_INSERTED) 601 if (ev.id == SYS_HOTSWAP_INSERTED)
609 {
610 /* FIXME: once sd_enabled is implement properly,
611 * reinitializing the controllers might be needed */
612 sd_enable(true);
613 if (success < 0) /* initialisation failed */
614 panicf("SD init failed : %d", success);
615 success = disk_mount(0); /* 0 if fail */ 602 success = disk_mount(0); /* 0 if fail */
616 }
617 603
618 /* notify the system about the changed filesystems 604 /* notify the system about the changed filesystems
619 */ 605 */
620 if (success) 606 if (success)
621 queue_broadcast(SYS_FS_CHANGED, 0); 607 queue_broadcast(SYS_FS_CHANGED, 0);
622
623 /* Access is now safe */
624 mutex_unlock(&sd_mtx);
625 fat_unlock();
626 sd_enable(false);
627 }
628 break; 608 break;
629#endif 609#endif /* HAVE_HOTSWAP */
630 } 610 }
631 } 611 }
632} 612}
diff --git a/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c b/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c
index 395c0f49e6..0fd74787d1 100644
--- a/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c
+++ b/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c
@@ -32,7 +32,7 @@
32#include "s5l8702.h" 32#include "s5l8702.h"
33#include "led.h" 33#include "led.h"
34#include "ata_idle_notify.h" 34#include "ata_idle_notify.h"
35#include "fat.h" 35#include "disk_cache.h"
36#include "splash.h" 36#include "splash.h"
37 37
38 38
@@ -68,6 +68,7 @@ static struct semaphore mmc_wakeup;
68static struct semaphore mmc_comp_wakeup; 68static struct semaphore mmc_comp_wakeup;
69static int spinup_time = 0; 69static int spinup_time = 0;
70static int dma_mode = 0; 70static int dma_mode = 0;
71static char aligned_buffer[SECTOR_SIZE] __attribute__((aligned(0x10)));
71 72
72 73
73#ifdef ATA_HAVE_BBT 74#ifdef ATA_HAVE_BBT
@@ -857,8 +858,25 @@ int ata_bbt_translate(uint64_t sector, uint32_t count, uint64_t* phys, uint32_t*
857static int ata_rw_sectors(uint64_t sector, uint32_t count, void* buffer, bool write) 858static int ata_rw_sectors(uint64_t sector, uint32_t count, void* buffer, bool write)
858{ 859{
859 if (((uint32_t)buffer) & 0xf) 860 if (((uint32_t)buffer) & 0xf)
860 panicf("ATA: Misaligned data buffer at %08X (sector %lu, count %lu)", 861 {
861 (unsigned int)buffer, (long unsigned int)sector, (long unsigned int)count); 862 while (count)
863 {
864 if (write)
865 memcpy(aligned_buffer, buffer, SECTOR_SIZE);
866
867 PASS_RC(ata_rw_sectors(sector, 1, aligned_buffer, write), 0, 0);
868
869 if (!write)
870 memcpy(buffer, aligned_buffer, SECTOR_SIZE);
871
872 buffer += SECTOR_SIZE;
873 sector++;
874 count--;
875 }
876
877 return 0;
878 }
879
862#ifdef ATA_HAVE_BBT 880#ifdef ATA_HAVE_BBT
863 if (sector + count > ata_virtual_sectors) RET_ERR(0); 881 if (sector + count > ata_virtual_sectors) RET_ERR(0);
864 if (ata_bbt) 882 if (ata_bbt)
@@ -1117,14 +1135,13 @@ int ata_init(void)
1117 -- Michael Sparmann (theseven), 2011-10-22 */ 1135 -- Michael Sparmann (theseven), 2011-10-22 */
1118 if (!ceata) 1136 if (!ceata)
1119 { 1137 {
1120 unsigned char* sector = fat_get_sector_buffer(); 1138 unsigned char* sector = aligned_buffer;
1121 ata_rw_sectors(0, 1, sector, false); 1139 ata_rw_sectors(0, 1, sector, false);
1122 if (sector[510] == 0xaa && sector[511] == 0x55) 1140 if (sector[510] == 0xaa && sector[511] == 0x55)
1123 { 1141 {
1124 ata_swap = true; 1142 ata_swap = true;
1125 splashf(5000, "Wrong HDD endianness, please update your emCORE version!"); 1143 splashf(5000, "Wrong HDD endianness, please update your emCORE version!");
1126 } 1144 }
1127 fat_release_sector_buffer();
1128 } 1145 }
1129 1146
1130 create_thread(ata_thread, ata_stack, 1147 create_thread(ata_thread, ata_stack,
diff --git a/firmware/target/arm/tcc780x/sd-tcc780x.c b/firmware/target/arm/tcc780x/sd-tcc780x.c
index 55ae4e7c70..b7abea8be4 100644
--- a/firmware/target/arm/tcc780x/sd-tcc780x.c
+++ b/firmware/target/arm/tcc780x/sd-tcc780x.c
@@ -28,7 +28,6 @@
28#include "led.h" 28#include "led.h"
29#include "thread.h" 29#include "thread.h"
30#include "disk.h" 30#include "disk.h"
31#include "fat.h"
32#include "ata_idle_notify.h" 31#include "ata_idle_notify.h"
33#include "usb.h" 32#include "usb.h"
34 33
@@ -657,35 +656,30 @@ static void sd_thread(void)
657 { 656 {
658#ifdef HAVE_HOTSWAP 657#ifdef HAVE_HOTSWAP
659 case SYS_HOTSWAP_INSERTED: 658 case SYS_HOTSWAP_INSERTED:
660 case SYS_HOTSWAP_EXTRACTED: 659 case SYS_HOTSWAP_EXTRACTED:;
661 fat_lock(); /* lock-out FAT activity first - 660 int success = 1;
662 prevent deadlocking via disk_mount that 661
663 would cause a reverse-order attempt with 662 /* Release "by force" */
664 another thread */
665 mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
666 into driver that bypass the fat cache */
667
668 /* We now have exclusive control of fat cache and ata */
669
670 /* Release "by force", ensure file descriptors aren't leaked and
671 any busy ones are invalid if mounting */
672 disk_unmount(sd_first_drive + CARD_NUM_SLOT); 663 disk_unmount(sd_first_drive + CARD_NUM_SLOT);
673 664
665 mutex_lock(&sd_mtx); /* lock-out card activity */
666
674 /* Force card init for new card, re-init for re-inserted one or 667 /* Force card init for new card, re-init for re-inserted one or
675 * clear if the last attempt to init failed with an error. */ 668 * clear if the last attempt to init failed with an error. */
676 card_info[CARD_NUM_SLOT].initialized = 0; 669 card_info[CARD_NUM_SLOT].initialized = 0;
677 sd_status[CARD_NUM_SLOT].retry = 0; 670 sd_status[CARD_NUM_SLOT].retry = 0;
678 671
672 mutex_unlock(&sd_mtx);
673
679 if (ev.id == SYS_HOTSWAP_INSERTED) 674 if (ev.id == SYS_HOTSWAP_INSERTED)
680 disk_mount(sd_first_drive + CARD_NUM_SLOT); 675 success = disk_mount(sd_first_drive + CARD_NUM_SLOT);
681 676
682 queue_broadcast(SYS_FS_CHANGED, 0); 677 if (success)
678 queue_broadcast(SYS_FS_CHANGED, 0);
683 679
684 /* Access is now safe */
685 mutex_unlock(&sd_mtx);
686 fat_unlock();
687 break; 680 break;
688#endif 681#endif /* HAVE_HOTSWAP */
682
689 case SYS_TIMEOUT: 683 case SYS_TIMEOUT:
690 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ))) 684 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
691 { 685 {
diff --git a/firmware/target/arm/tms320dm320/sdmmc-dm320.c b/firmware/target/arm/tms320dm320/sdmmc-dm320.c
index 284061e1ad..d46dbf5e40 100644
--- a/firmware/target/arm/tms320dm320/sdmmc-dm320.c
+++ b/firmware/target/arm/tms320dm320/sdmmc-dm320.c
@@ -592,48 +592,29 @@ static void sd_thread(void)
592 { 592 {
593#ifdef HAVE_HOTSWAP 593#ifdef HAVE_HOTSWAP
594 case SYS_HOTSWAP_INSERTED: 594 case SYS_HOTSWAP_INSERTED:
595 case SYS_HOTSWAP_EXTRACTED: 595 case SYS_HOTSWAP_EXTRACTED:;
596 {
597 int success = 1; 596 int success = 1;
598 fat_lock(); /* lock-out FAT activity first -
599 prevent deadlocking via disk_mount that
600 would cause a reverse-order attempt with
601 another thread */
602 mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
603 into driver that bypass the fat cache */
604 597
605 /* We now have exclusive control of fat cache and ata */ 598 disk_unmount(0); /* release "by force" */
606 599
607 disk_unmount(0); /* release "by force", ensure file 600 mutex_lock(&sd_mtx); /* lock-out card activity */
608 descriptors aren't leaked and any busy
609 ones are invalid if mounting */
610 601
611 /* Force card init for new card, re-init for re-inserted one or 602 /* Force card init for new card, re-init for re-inserted one or
612 * clear if the last attempt to init failed with an error. */ 603 * clear if the last attempt to init failed with an error. */
613 card_info[0].initialized = 0; 604 card_info[0].initialized = 0;
614 605
606 mutex_unlock(&sd_mtx);
607
615 if (ev.id == SYS_HOTSWAP_INSERTED) 608 if (ev.id == SYS_HOTSWAP_INSERTED)
616 {
617 /* FIXME: once sd_enabled is implement properly,
618 * reinitializing the controllers might be needed */
619 sd_enable(true);
620 if (success < 0) /* initialisation failed */
621 panicf("SD init failed : %d", success);
622 success = disk_mount(0); /* 0 if fail */ 609 success = disk_mount(0); /* 0 if fail */
623 }
624 610
625 /* notify the system about the changed filesystems 611 /* notify the system about the changed filesystems */
626 */
627 if (success) 612 if (success)
628 queue_broadcast(SYS_FS_CHANGED, 0); 613 queue_broadcast(SYS_FS_CHANGED, 0);
629 614
630 /* Access is now safe */
631 mutex_unlock(&sd_mtx);
632 fat_unlock();
633 sd_enable(false);
634 }
635 break; 615 break;
636#endif 616#endif /* HAVE_HOTSWAP */
617
637 case SYS_TIMEOUT: 618 case SYS_TIMEOUT:
638 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ))) 619 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
639 { 620 {
diff --git a/firmware/target/hosted/filesystem-app.c b/firmware/target/hosted/filesystem-app.c
new file mode 100644
index 0000000000..7ef8d3109b
--- /dev/null
+++ b/firmware/target/hosted/filesystem-app.c
@@ -0,0 +1,562 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2010 Thomas Martitz
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#define RB_FILESYSTEM_OS
22#include <stdio.h> /* snprintf */
23#include <stdlib.h>
24#include <stdarg.h>
25#include <time.h>
26#include <errno.h>
27#include <string.h>
28#include <limits.h>
29#include "config.h"
30#include "system.h"
31#include "file.h"
32#include "dir.h"
33#include "file_internal.h"
34#include "pathfuncs.h"
35#include "string-extra.h"
36#include "rbpaths.h"
37#include "logf.h"
38
39
40#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
41static const char rbhome[] = "/sdcard";
42#elif (CONFIG_PLATFORM & (PLATFORM_SDL|PLATFORM_MAEMO|PLATFORM_PANDORA)) \
43 && !defined(__PCTOOL__)
44const char *rbhome;
45#else
46/* YPR0, YPR1 */
47static const char rbhome[] = HOME_DIR;
48#endif
49
50#if !(defined(SAMSUNG_YPR0) || defined(SAMSUNG_YPR1)) && !defined(__PCTOOL__)
51/* Special dirs are user-accessible (and user-writable) dirs which take priority
52 * over the ones where Rockbox is installed to. Classic example would be
53 * $HOME/.config/rockbox.org vs /usr/share/rockbox */
54#define HAVE_SPECIAL_DIRS
55#endif
56
57#ifdef HAVE_MULTIDRIVE
58/* This is to compare any opened directories with the home directory so that
59 the special drive links may be returned for it only */
60static int rbhome_fildes = -1;
61
62/* A special link is created under e.g. HOME_DIR/<microSD1>, e.g. to access
63 * external storage in a convenient location, much similar to the mount
64 * point on our native targets. Here they are treated as symlink (one which
65 * doesn't actually exist in the filesystem and therefore we have to override
66 * readlink() */
67static const char *handle_special_links(const char* link, unsigned flags,
68 char *buf, const size_t bufsize)
69{
70 (void) flags;
71 char vol_string[VOL_MAX_LEN + 1];
72 get_volume_name(-1, vol_string);
73
74 /* link might be passed with or without HOME_DIR expanded. To handle
75 * both perform substring matching (VOL_NAMES is unique enough) */
76 const char *begin = strstr(link, vol_string);
77 if (begin)
78 {
79 /* begin now points to the start of vol_string within link,
80 * we want to copy the remainder of the paths, prefixed by
81 * the actual mount point (the remainder might be "") */
82 snprintf(buf, bufsize, MULTIDRIVE_DIR"%s", begin + len);
83 return buf;
84 }
85
86 return link;
87}
88#endif
89
90#ifdef HAVE_MULTIDRIVE
91/* we keep an open descriptor of the home directory to detect when it has been
92 opened by opendir() so that its "symlinks" may be enumerated */
93static void cleanup_rbhome(void)
94{
95 os_close(rbhome_fildes);
96 rbhome_fildes = -1;
97}
98#endif /* HAVE_MULTIDRIVE */
99
100void paths_init(void)
101{
102#ifdef HAVE_SPECIAL_DIRS
103 /* make sure $HOME/.config/rockbox.org exists, it's needed for config.cfg */
104#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
105 os_mkdir("/sdcard/rockbox" __MKDIR_MODE_ARG);
106 os_mkdir("/sdcard/rockbox/rocks.data" __MKDIR_MODE_ARG);
107#else
108 char config_dir[MAX_PATH];
109
110 const char *home = getenv("RBROOT");
111 if (!home)
112 {
113 home = getenv("HOME");
114 }
115
116 if (!home)
117 {
118 logf("HOME environment var not set. Can't write config");
119 return;
120 }
121
122 rbhome = home;
123 snprintf(config_dir, sizeof(config_dir), "%s/.config", home);
124 os_mkdir(config_dir __MKDIR_MODE_ARG);
125 snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org", home);
126 os_mkdir(config_dir __MKDIR_MODE_ARG);
127 /* Plugin data directory */
128 snprintf(config_dir, sizeof(config_dir), "%s/.config/rockbox.org/rocks.data", home);
129 os_mkdir(config_dir __MKDIR_MODE_ARG);
130#endif
131#endif /* HAVE_SPECIAL_DIRS */
132
133#ifdef HAVE_MULTIDRIVE
134 /* if this fails then alternate volumes will not work, but this function
135 cannot return that fact */
136 rbhome_fildes = os_opendirfd(rbhome);
137 if (rbhome_fildes >= 0)
138 atexit(cleanup_rbhome);
139#endif /* HAVE_MULTIDRIVE */
140}
141
142#ifdef HAVE_SPECIAL_DIRS
143static const char* _get_user_file_path(const char *path,
144 unsigned flags,
145 char* buf,
146 const size_t bufsize)
147{
148 const char *ret = path;
149 const char *pos = path;
150 /* replace ROCKBOX_DIR in path with $HOME/.config/rockbox.org */
151 pos += ROCKBOX_DIR_LEN;
152 if (*pos == '/') pos += 1;
153
154#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
155 if (path_append(buf, "/sdcard/rockbox", pos, bufsize) >= bufsize)
156 return NULL;
157#else
158 if (path_append(buf, rbhome, ".config/rockbox.org", bufsize) >= bufsize ||
159 path_append(buf, PA_SEP_SOFT, pos, bufsize) >= bufsize)
160 return NULL;
161#endif
162
163 /* always return the replacement buffer (pointing to $HOME) if
164 * write access is needed */
165 if (flags & NEED_WRITE)
166 ret = buf;
167 else if (os_file_exists(buf))
168 ret = buf;
169
170 if (ret != buf) /* not found in $HOME, try ROCKBOX_BASE_DIR, !NEED_WRITE only */
171 {
172 if (path_append(buf, ROCKBOX_SHARE_PATH, pos, bufsize) >= bufsize)
173 return NULL;
174
175 if (os_file_exists(buf))
176 ret = buf;
177 }
178
179 return ret;
180}
181
182#endif
183
184const char * handle_special_dirs(const char *dir, unsigned flags,
185 char *buf, const size_t bufsize)
186{
187 (void) flags; (void) buf; (void) bufsize;
188#ifdef HAVE_SPECIAL_DIRS
189 if (!strncmp(HOME_DIR, dir, HOME_DIR_LEN))
190 {
191 const char *p = dir + HOME_DIR_LEN;
192 while (*p == '/') p++;
193 snprintf(buf, bufsize, "%s/%s", rbhome, p);
194 dir = buf;
195 }
196 else if (!strncmp(ROCKBOX_DIR, dir, ROCKBOX_DIR_LEN))
197 dir = _get_user_file_path(dir, flags, buf, bufsize);
198#endif
199#ifdef HAVE_MULTIDRIVE
200 dir = handle_special_links(dir, flags, buf, bufsize);
201#endif
202 return dir;
203}
204
205int app_open(const char *path, int oflag, ...)
206{
207 int flags = IS_FILE;
208 if (oflag & O_ACCMODE)
209 flags |= NEED_WRITE;
210
211 char realpath[MAX_PATH];
212 const char *fpath = handle_special_dirs(path, flags, realpath,
213 sizeof (realpath));
214 if (!fpath)
215 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
216
217 return os_open(fpath, oflag __OPEN_MODE_ARG);
218}
219
220int app_creat(const char *path, mode_t mode)
221{
222 return app_open(path, O_CREAT|O_WRONLY|O_TRUNC, mode);
223}
224
225int app_remove(const char *path)
226{
227 char realpath[MAX_PATH];
228 const char *fpath = handle_special_dirs(path, NEED_WRITE, realpath,
229 sizeof (realpath));
230 if (!fpath)
231 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
232
233 return os_remove(fpath);
234}
235
236int app_rename(const char *old, const char *new)
237{
238 char realpath_old[MAX_PATH], realpath_new[MAX_PATH];
239 const char *fold = handle_special_dirs(old, NEED_WRITE, realpath_old,
240 sizeof (realpath_old));
241 const char *fnew = handle_special_dirs(new, NEED_WRITE, realpath_new,
242 sizeof (realpath_new));
243 if (!fold || !fnew)
244 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
245
246 return os_rename(fold, fnew);
247}
248
249#ifdef HAVE_SDL_THREADS
250ssize_t app_read(int fd, void *buf, size_t nbyte)
251{
252 return os_read(fd, buf, nbyte);
253}
254
255ssize_t app_write(int fd, const void *buf, size_t nbyte)
256{
257 return os_write(fd, buf, nbyte);
258}
259#endif /* HAVE_SDL_THREADS */
260
261int app_relate(const char *path1, const char *path2)
262{
263 char realpath_1[MAX_PATH], realpath_2[MAX_PATH];
264 const char *fpath1 = handle_special_dirs(path1, 0, realpath_1,
265 sizeof (realpath_1));
266 const char *fpath2 = handle_special_dirs(path2, 0, realpath_2,
267 sizeof (realpath_2));
268
269 if (!fpath1 || !fpath2)
270 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
271
272 return os_relate(fpath1, fpath2);
273}
274
275bool app_file_exists(const char *path)
276{
277 char realpath[MAX_PATH];
278 const char *fpath = handle_special_dirs(path, NEED_WRITE, realpath,
279 sizeof (realpath));
280 if (!fpath)
281 FILE_ERROR_RETURN(ENAMETOOLONG, false);
282
283 return os_file_exists(fpath);
284}
285
286/* need to wrap around DIR* because we need to save the parent's directory
287 * path in order to determine dirinfo for volumes or convert the path to UTF-8;
288 * also is required to implement get_dir_info() */
289struct __dir
290{
291 OS_DIR_T *osdirp;
292#ifdef HAVE_MULTIDRIVE
293 int volumes_returned;
294#endif
295 int osfd;
296 bool osfd_is_opened;
297#if defined(OS_DIRENT_CONVERT) || defined (HAVE_MULTIDRIVE)
298 #define USE_DIRENTP
299 struct dirent *direntp;
300 size_t d_name_size;
301#endif
302 char path[];
303};
304
305static void __dir_free(struct __dir *this)
306{
307 if (!this)
308 return;
309
310#ifdef USE_DIRENTP
311 free(this->direntp);
312#endif
313
314 if (this->osfd_is_opened)
315 os_close(this->osfd);
316
317 free(this);
318}
319
320DIR * app_opendir(const char *dirname)
321{
322 int rc;
323 char realpath[MAX_PATH];
324 const char *fname = handle_special_dirs(dirname, 0, realpath,
325 sizeof (realpath));
326 if (!fname)
327 FILE_ERROR_RETURN(ENAMETOOLONG, NULL);
328
329 size_t name_len = path_strip_trailing_separators(fname, &fname);
330 struct __dir *this = calloc(1, sizeof (*this) + name_len + 1);
331 if (!this)
332 FILE_ERROR(ENOMEM, RC);
333
334#ifdef USE_DIRENTP
335 /* allocate what we're really going to return to callers, making certain
336 it has at least the d_name size we want */
337 this->d_name_size = MAX(MAX_PATH, sizeof (this->direntp->d_name));
338 this->direntp = calloc(1, offsetof(typeof (*this->direntp), d_name) +
339 this->d_name_size);
340 if (!this->direntp)
341 FILE_ERROR(ENOMEM, RC);
342
343 /* only the d_name field will be valid but that is all that anyone may
344 truely count on portably existing */
345#endif /* USE_DIRENTP */
346
347 strmemcpy(this->path, fname, name_len);
348
349 rc = os_opendir_and_fd(this->path, &this->osdirp, &this->osfd);
350 if (rc < 0)
351 FILE_ERROR(ERRNO, RC);
352
353 this->osfd_is_opened = rc > 0;
354
355#ifdef HAVE_MULTIDRIVE
356 this->volumes_returned = INT_MAX; /* assume NOT $HOME */
357 if (rbhome_fildes >= 0 && os_samefile(rbhome_fildes, fd) > 0)
358 this->volumes_returned = 0; /* there's no place like $HOME */
359#endif /* HAVE_MULTIDRIVE */
360
361 return (DIR *)this;
362file_error:
363 __dir_free(this);
364 return NULL;
365}
366
367int app_closedir(DIR *dirp)
368{
369 struct __dir *this = (struct __dir *)dirp;
370 if (!this)
371 FILE_ERROR_RETURN(EBADF, -1);
372
373 OS_DIR_T *osdirp = this->osdirp;
374 __dir_free(this);
375
376 return os_closedir(osdirp);
377}
378
379struct dirent * app_readdir(DIR *dirp)
380{
381 struct __dir *this = (struct __dir *)dirp;
382 if (!this)
383 FILE_ERROR_RETURN(EBADF, NULL);
384
385#ifdef HAVE_MULTIDRIVE
386 if (this->volumes_returned < NUM_VOLUMES)
387 {
388 while (++this->volumes_returned < NUM_VOLUMES)
389 {
390 if (!volume_present(this->volumes_returned))
391 continue;
392
393 get_volume_name(this->volumes_returned, this->direntp->d_name);
394 return this->direntp;
395 }
396 }
397 /* do normal directory reads */
398#endif /* HAVE_MULTIDRIVE */
399
400 OS_DIRENT_T *osdirent = os_readdir(this->osdirp);
401
402#ifdef OS_DIRENT_CONVERT
403 if (strlcpy_from_os(this->direntp->d_name, osdirent->d_name,
404 this->d_name_size) >= this->d_name_size)
405 {
406 this->direntp->d_name[0] = '\0';
407 errno = EOVERFLOW;
408 return NULL;
409 }
410
411 osdirent = (OS_DIRENT_T *)this->direntp;
412#endif /* OS_DIRENT_CONVERT */
413
414 return (struct dirent *)osdirent;
415}
416
417int app_mkdir(const char *path)
418{
419 char realpath[MAX_PATH];
420 const char *fname = handle_special_dirs(path, NEED_WRITE, realpath,
421 sizeof (realpath));
422 if (!fname)
423 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
424
425 return os_mkdir(fname __MKDIR_MODE_ARG);
426}
427
428int app_rmdir(const char *path)
429{
430 char realpath[MAX_PATH];
431 const char *fname = handle_special_dirs(path, NEED_WRITE, realpath,
432 sizeof (realpath));
433 if (!fname)
434 FILE_ERROR_RETURN(ENAMETOOLONG, -1);
435
436 return os_rmdir(fname);
437}
438
439int app_samedir(DIR *dirp1, DIR *dirp2)
440{
441 struct __dir *this1 = (struct __dir *)dirp1;
442 struct __dir *this2 = (struct __dir *)dirp2;
443
444 if (!this1 || !this2)
445 {
446 errno = EBADF;
447 return -1;
448 }
449
450 return os_fsamefile(this1->osfd, this2->osfd);
451}
452
453bool app_dir_exists(const char *dirname)
454{
455 char realpath[MAX_PATH];
456 const char *fname = handle_special_dirs(dirname, 0, realpath,
457 sizeof (realpath));
458 if (!fname)
459 FILE_ERROR_RETURN(ENAMETOOLONG, false);
460
461 OS_DIR_T *osdirp = os_opendir(fname);
462 if (!osdirp)
463 return false;
464
465 os_closedir(osdirp);
466 return true;
467}
468
469struct dirinfo dir_get_info(DIR *dirp, struct dirent *entry)
470{
471 struct __dir *this = (struct __dir *)dirp;
472 struct dirinfo ret = { .mtime = 0 };
473
474 if (!this)
475 FILE_ERROR_RETURN(EBADF, ret);
476
477 if (!entry || entry->d_name[0] == '\0')
478 FILE_ERROR_RETURN(ENOENT, ret);
479
480 char path[MAX_PATH];
481
482#ifdef HAVE_MULTIDRIVE
483 if (this->volumes_returned < NUM_VOLUMES)
484 {
485 /* last thing read was a "symlink" */
486 ret.attribute = ATTR_LINK;
487 strcpy(path, MULTIDRIVE_DIR);
488 }
489 else
490#endif
491 if (path_append(path, this->path, entry->d_name, sizeof (path))
492 >= sizeof (path))
493 {
494 FILE_ERROR_RETURN(ENAMETOOLONG, ret);
495 }
496
497 struct stat s;
498 if (os_lstat(path, &s) < 0)
499 FILE_ERROR_RETURN(ERRNO, ret);
500
501 int err = 0;
502 if (S_ISLNK(s.st_mode))
503 {
504 ret.attribute |= ATTR_LINK;
505 err = os_stat(path, &s);
506 }
507
508 if (err < 0)
509 FILE_ERROR_RETURN(ERRNO, ret);
510
511 if (S_ISDIR(s.st_mode))
512 ret.attribute |= ATTR_DIRECTORY;
513
514 ret.size = s.st_size;
515
516 struct tm tm;
517 if (!localtime_r(&s.st_mtime, &tm))
518 FILE_ERROR_RETURN(ERRNO, ret);
519
520 ret.mtime = mktime(&tm);
521 return ret;
522}
523
524/* On MD we create a virtual symlink for the external drive,
525 * for this we need to override readlink(). */
526ssize_t app_readlink(const char *path, char *buf, size_t bufsiz)
527{
528 char _buf[MAX_PATH];
529 path = handle_special_dirs(path, 0, _buf, sizeof(_buf));
530#ifdef HAVE_MULTIDRIVE
531 /* if path == _buf then we can be sure handle_special_dir() did something
532 * and path is not an ordinary directory */
533 if (path == _buf && !strncmp(path, MULTIDRIVE_DIR, sizeof(MULTIDRIVE_DIR)-1))
534 {
535 /* copying NUL is not required as per readlink specification */
536 ssize_t len = strlen(path);
537 memcpy(buf, path, len);
538 return len;
539 }
540#endif
541 /* does not append NUL !! */
542 return os_readlink(path, buf, bufsiz);
543 (void) path; (void) buf; (void) bufsiz;
544}
545
546int os_volume_path(IF_MV(int volume, ) char *buffer, size_t bufsize)
547{
548#ifdef HAVE_MULTIVOLUME
549 char volname[VOL_MAX_LEN + 1];
550 get_volume_name(volume, volname);
551#else
552 const char *volname = "/";
553#endif
554
555 if (!handle_special_dirs(volname, NEED_WRITE, buffer, bufsize))
556 {
557 errno = ENAMETOOLONG;
558 return -1;
559 }
560
561 return 0;
562}
diff --git a/firmware/target/hosted/filesystem-app.h b/firmware/target/hosted/filesystem-app.h
new file mode 100644
index 0000000000..68b3f13b6e
--- /dev/null
+++ b/firmware/target/hosted/filesystem-app.h
@@ -0,0 +1,117 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#ifndef _FILESYSTEM_APP_H_
23#define _FILESYSTEM_APP_H_
24
25#if defined(PLUGIN) || defined(CODEC)
26/* Prevent often-problematic plugin namespace pollution */
27#define FILEFUNCTIONS_DECLARED
28#define FILEFUNCTIONS_DEFINED
29#define DIRFUNCTIONS_DECLARED
30#define DIRFUNCTIONS_DEFINED
31#define OSFUNCTIONS_DECLARED
32#endif /* PLUGIN || CODEC */
33
34/* flags for get_user_file_path() */
35/* whether you need write access to that file/dir, especially true
36 * for runtime generated files (config.cfg) */
37#define NEED_WRITE (1<<0)
38/* file or directory? */
39#define IS_FILE (1<<1)
40
41#ifndef OSFUNCTIONS_DECLARED
42#define FS_PREFIX(_x_) app_ ## _x_
43
44void paths_init(void);
45const char * handle_special_dirs(const char *dir, unsigned flags,
46 char *buf, const size_t bufsize);
47
48#endif /* !OSFUNCTIONS_DECLARED */
49#endif /* _FILESYSTEM_APP_H_ */
50
51#ifdef HAVE_SDL
52#include "filesystem-sdl.h"
53#endif /* HAVE_SDL */
54#ifdef WIN32
55#include "filesystem-win32.h"
56#else /* !WIN32 */
57#include "filesystem-unix.h"
58#endif /* WIN32 */
59#include "filesystem-hosted.h"
60
61#ifdef _FILE_H_
62#ifndef _FILESYSTEM_APP__FILE_H_
63#define _FILESYSTEM_APP__FILE_H_
64
65#ifdef RB_FILESYSTEM_OS
66#define FILEFUNCTIONS_DEFINED
67#endif
68
69#ifndef FILEFUNCTIONS_DECLARED
70int app_open(const char *name, int oflag, ...);
71int app_creat(const char *name, mode_t mode);
72#define app_close os_close
73#define app_ftruncate os_ftruncate
74#define app_fsync os_fsync
75#define app_lseek os_lseek
76#ifdef HAVE_SDL_THREADS
77ssize_t app_read(int fildes, void *buf, size_t nbyte);
78ssize_t app_write(int fildes, const void *buf, size_t nbyte);
79#else
80#define app_read os_read
81#define app_write os_write
82#endif /* HAVE_SDL_THREADS */
83int app_remove(const char *path);
84int app_rename(const char *old, const char *new);
85#define app_filesize os_filesize
86#define app_fsamefile os_fsamefile
87int app_relate(const char *path1, const char *path2);
88bool app_file_exists(const char *path);
89ssize_t app_readlink(const char *path, char *buf, size_t bufsize);
90#endif /* !FILEFUNCTIONS_DECLARED */
91
92#endif /* _FILESYSTEM_APP__FILE_H_ */
93#endif /* _FILE_H_ */
94
95#ifdef _DIR_H_
96#ifndef _FILESYSTEM_APP__DIR_H_
97#define _FILESYSTEM_APP__DIR_H_
98
99#ifdef RB_FILESYSTEM_OS
100#define DIRFUNCTIONS_DEFINED
101#endif
102
103#define DIRENT dirent
104#define DIRENT_DEFINED
105
106#ifndef DIRFUNCTIONS_DECLARED
107DIR * app_opendir(const char *dirname);
108struct dirent * app_readdir(DIR *dirp);
109int app_closedir(DIR *dirp);
110int app_mkdir(const char *path);
111int app_rmdir(const char *path);
112int app_samedir(DIR *dirp1, DIR *dirp2);
113bool app_dir_exists(const char *dirname);
114#endif /* DIRFUNCTIONS_DECLARED */
115
116#endif /* _FILESYSTEM_APP__DIR_H_ */
117#endif /* _DIR_H_ */
diff --git a/firmware/target/hosted/filesystem-hosted.h b/firmware/target/hosted/filesystem-hosted.h
new file mode 100644
index 0000000000..3d979eb19d
--- /dev/null
+++ b/firmware/target/hosted/filesystem-hosted.h
@@ -0,0 +1,74 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _FILESYSTEM_HOSTED_H_
22#define _FILESYSTEM_HOSTED_H_
23
24#include "mv.h"
25
26int os_volume_path(IF_MV(int volume, ) char *buffer, size_t bufsize);
27void * os_lc_open(const char *ospath);
28
29#endif /* _FILESYSTEM_HOSTED_H_ */
30
31#ifdef _FILE_H_
32#ifndef _FILESYSTEM_HOSTED__FILE_H_
33#define _FILESYSTEM_HOSTED__FILE_H_
34
35#ifndef OSFUNCTIONS_DECLARED
36off_t os_filesize(int osfd);
37int os_fsamefile(int osfd1, int osfd2);
38int os_relate(const char *path1, const char *path2);
39bool os_file_exists(const char *ospath);
40
41#define __OPEN_MODE_ARG \
42 , ({ \
43 mode_t mode = 0; \
44 if (oflag & O_CREAT) \
45 { \
46 va_list ap; \
47 va_start(ap, oflag); \
48 mode = va_arg(ap, unsigned int); \
49 va_end(ap); \
50 } \
51 mode; })
52
53#define __CREAT_MODE_ARG \
54 , mode
55
56#endif /* OSFUNCTIONS_DECLARED */
57
58#endif /* _FILESYSTEM_HOSTED__FILE_H_ */
59#endif /* _FILE_H_ */
60
61#ifdef _DIR_H_
62#ifndef _FILESYSTEM_HOSTED__DIR_H_
63#define _FILESYSTEM_HOSTED__DIR_H_
64
65#ifndef OSFUNCTIONS_DECLARED
66int os_opendir_and_fd(const char *osdirname, OS_DIR_T **osdirpp,
67 int *osfdp);
68
69#define __MKDIR_MODE_ARG \
70 , 0777
71#endif /* OSFUNCTIONS_DECLARED */
72
73#endif /* _FILESYSTEM_HOSTED__DIR_H_ */
74#endif /* _DIR_H_ */
diff --git a/firmware/target/hosted/filesystem-unix.c b/firmware/target/hosted/filesystem-unix.c
index 8ac1d4ada9..907d6ab14e 100644
--- a/firmware/target/hosted/filesystem-unix.c
+++ b/firmware/target/hosted/filesystem-unix.c
@@ -18,24 +18,204 @@
18 * KIND, either express or implied. 18 * KIND, either express or implied.
19 * 19 *
20 ****************************************************************************/ 20 ****************************************************************************/
21#define RB_FILESYSTEM_OS
22#include <sys/statfs.h> /* lowest common denominator */
23#include <sys/stat.h>
24#include <string.h>
25#include <errno.h>
26#include "config.h"
27#include "system.h"
28#include "file.h"
29#include "dir.h"
30#include "mv.h"
31#include "debug.h"
32#include "pathfuncs.h"
33#include "string-extra.h"
21 34
22#include <sys/stat.h> /* stat() */ 35#define SAME_FILE_INFO(sb1p, sb2p) \
23#include "mv.h" /* stat() */ 36 ((sb1p)->st_dev == (sb2p)->st_dev && (sb1p)->st_ino == (sb2p)->st_ino)
24 37
25 38off_t os_filesize(int osfd)
26long filesize(int fd)
27{ 39{
28 struct stat buf; 40 struct stat sb;
29 41
30 if (!fstat(fd, &buf)) 42 if (!os_fstat(osfd, &sb))
31 return buf.st_size; 43 return sb.st_size;
32 else 44 else
33 return -1; 45 return -1;
34} 46}
35 47
36/* do we really need this in the app? */ 48int os_fsamefile(int osfd1, int osfd2)
37void fat_size(IF_MV(int volume,) unsigned long* size, unsigned long* free) 49{
50 struct stat sb1, sb2;
51
52 if (os_fstat(osfd1, &sb1))
53 return -1;
54
55 if (os_fstat(osfd2, &sb2))
56 return -1;
57
58 return SAME_FILE_INFO(&sb1, &sb2);
59}
60
61int os_relate(const char *ospath1, const char *ospath2)
62{
63 DEBUGF("\"%s\" : \"%s\"\n", ospath1, ospath2);
64
65 if (!ospath2 || !*ospath2)
66 {
67 errno = ospath2 ? ENOENT : EFAULT;
68 return -1;
69 }
70
71 /* First file must stay open for duration so that its stats don't change */
72 int fd1 = os_open(ospath1, O_RDONLY);
73 if (fd1 < 0)
74 return -2;
75
76 struct stat sb1;
77 if (os_fstat(fd1, &sb1))
78 {
79 os_close(fd1);
80 return -3;
81 }
82
83 char path2buf[strlen(ospath2) + 1];
84 *path2buf = 0;
85
86 ssize_t len = 0;
87 const char *p = ospath2;
88 const char *sepmo = path_is_absolute(ospath2) ? PA_SEP_HARD : PA_SEP_SOFT;
89
90 int rc = RELATE_DIFFERENT;
91
92 while (1)
93 {
94 if (sepmo != PA_SEP_HARD &&
95 !(len = parse_path_component(&ospath2, &p)))
96 {
97 break;
98 }
99
100 char compname[len + 1];
101 strmemcpy(compname, p, len);
102
103 path_append(path2buf, sepmo, compname, sizeof (path2buf));
104 sepmo = PA_SEP_SOFT;
105
106 int errnum = errno; /* save and restore if not actually failing */
107 struct stat sb2;
108
109 if (!os_stat(path2buf, &sb2))
110 {
111 if (SAME_FILE_INFO(&sb1, &sb2))
112 {
113 rc = RELATE_SAME;
114 }
115 else if (rc == RELATE_SAME)
116 {
117 if (name_is_dot_dot(compname))
118 rc = RELATE_DIFFERENT;
119 else if (!name_is_dot(compname))
120 rc = RELATE_PREFIX;
121 }
122 }
123 else if (errno == ENOENT && !*GOBBLE_PATH_SEPCH(ospath2) &&
124 !name_is_dot_dot(compname))
125 {
126 if (rc == RELATE_SAME)
127 rc = RELATE_PREFIX;
128
129 errno = errnum;
130 break;
131 }
132 else
133 {
134 rc = -4;
135 break;
136 }
137 }
138
139 if (os_close(fd1) && rc >= 0)
140 rc = -5;
141
142 return rc;
143}
144
145bool os_file_exists(const char *ospath)
146{
147 int sim_fd = os_open(ospath, O_RDONLY, 0);
148 if (sim_fd < 0)
149 return false;
150
151 int errnum = errno;
152 os_close(sim_fd);
153 errno = errnum;
154
155 return true;
156}
157
158int os_opendirfd(const char *osdirname)
159{
160 return os_open(osdirname, O_RDONLY);
161}
162
163int os_opendir_and_fd(const char *osdirname, DIR **osdirpp, int *osfdp)
164{
165 /* another possible way is to use open() then fdopendir() */
166 *osdirpp = NULL;
167 *osfdp = -1;
168
169 DIR *dirp = os_opendir(osdirname);
170 if (!dirp)
171 return -1;
172
173 int rc = 0;
174 int errnum = errno;
175
176 int fd = os_dirfd(dirp);
177 if (fd < 0)
178 {
179 fd = os_opendirfd(osdirname);
180 rc = 1;
181 }
182
183 if (fd < 0)
184 {
185 os_closedir(dirp);
186 return -2;
187 }
188
189 errno = errnum;
190
191 *osdirpp = dirp;
192 *osfdp = fd;
193
194 return rc;
195}
196
197/* do we really need this in the app? (in the sim, yes) */
198void volume_size(IF_MV(int volume,) unsigned long *sizep, unsigned long *freep)
38{ 199{
39 IF_MV((void) volume); 200 unsigned long size = 0, free = 0;
40 *size = *free = 0; 201 char volpath[MAX_PATH];
202 struct statfs fs;
203
204 if (os_volume_path(IF_MV(volume,) volpath, sizeof (volpath)) >= 0
205 && !statfs(volpath, &fs))
206 {
207 DEBUGF("statvfs: frsize=%d blocks=%ld bfree=%ld\n",
208 (int)fs.f_frsize, (long)fs.f_blocks, (long)fs.f_bfree);
209 if (sizep)
210 size = (fs.f_blocks / 2) * (fs.f_frsize / 512);
211
212 if (freep)
213 free = (fs.f_bfree / 2) * (fs.f_frsize / 512);
214 }
215
216 if (sizep)
217 *sizep = size;
218
219 if (freep)
220 *freep = free;
41} 221}
diff --git a/firmware/target/hosted/filesystem-unix.h b/firmware/target/hosted/filesystem-unix.h
new file mode 100644
index 0000000000..a09712d8b0
--- /dev/null
+++ b/firmware/target/hosted/filesystem-unix.h
@@ -0,0 +1,82 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _FILESYSTEM_UNIX_H_
22#define _FILESYSTEM_UNIX_H_
23
24/* Include for file.h and dir.h because mkdir and friends may be here */
25#include <sys/stat.h>
26
27#define strlcpy_from_os strlcpy
28#endif
29
30#ifdef _FILE_H_
31#ifndef _FILESYSTEM_UNIX__FILE_H_
32#define _FILESYSTEM_UNIX__FILE_H_
33
34#include <unistd.h>
35
36#define OS_STAT_T struct stat
37
38#ifndef OSFUNCTIONS_DECLARED
39#define os_open open
40#define os_creat creat
41#define os_close close
42#define os_lseek lseek
43#define os_stat stat
44#define os_fstat fstat
45#define os_fstatat fstatat
46#define os_lstat lstat
47#define os_fsync fsync
48#define os_ftruncate ftruncate
49#define os_remove remove
50#define os_rename rename
51#define os_readlink readlink
52#ifndef os_read
53#define os_read read
54#endif
55#ifndef os_write
56#define os_write write
57#endif
58#endif /* !OSFUNCTIONS_DECLARED */
59
60#endif /* _FILESYSTEM_UNIX__FILE_H_ */
61#endif /* _FILE_H_ */
62
63#ifdef _DIR_H_
64#ifndef _FILESYSTEM_UNIX__DIR_H_
65#define _FILESYSTEM_UNIX__DIR_H_
66
67#include <dirent.h>
68
69#define OS_DIR_T DIR
70#define OS_DIRENT_T struct dirent
71
72#ifndef OSFUNCTIONS_DECLARED
73#define os_opendir opendir
74#define os_readdir readdir
75#define os_closedir closedir
76#define os_mkdir mkdir
77#define os_rmdir rmdir
78#define os_dirfd dirfd /* NOTE: might have to wrap on some platforms */
79#endif /* !OSFUNCTIONS_DECLARED */
80
81#endif /* _FILESYSTEM_UNIX__DIR_H_ */
82#endif /* _DIR_H_ */
diff --git a/firmware/target/hosted/filesystem-win32.c b/firmware/target/hosted/filesystem-win32.c
new file mode 100644
index 0000000000..19ef1c0aa7
--- /dev/null
+++ b/firmware/target/hosted/filesystem-win32.c
@@ -0,0 +1,479 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#define RB_FILESYSTEM_OS
22#include <stdio.h>
23#include <errno.h>
24#include <ctype.h>
25#include <stdlib.h>
26#include "config.h"
27#include "system.h"
28#include "file.h"
29#include "dir.h"
30#include "debug.h"
31#include "pathfuncs.h"
32#include "string-extra.h"
33
34#define SAME_FILE_INFO(lpInfo1, lpInfo2) \
35 ((lpInfo1)->dwVolumeSerialNumber == (lpInfo2)->dwVolumeSerialNumber && \
36 (lpInfo1)->nFileIndexHigh == (lpInfo2)->nFileIndexHigh && \
37 (lpInfo1)->nFileIndexLow == (lpInfo2)->nFileIndexLow)
38
39#define WIN32_LEAN_AND_MEAN
40#include <windows.h>
41
42static void win32_last_error_errno(void)
43{
44 switch (GetLastError())
45 {
46 case ERROR_FILE_NOT_FOUND:
47 case ERROR_PATH_NOT_FOUND:
48 errno = ENOENT;
49 break;
50 case ERROR_DIR_NOT_EMPTY:
51 errno = ENOTEMPTY;
52 break;
53 default:
54 errno = EIO;
55 }
56}
57
58#ifdef __MINGW32__
59#include <wchar.h>
60#include "rbunicode.h"
61
62static HANDLE win32_open(const char *ospath);
63static int win32_stat(const char *ospath, LPBY_HANDLE_FILE_INFORMATION lpInfo);
64
65unsigned short * strcpy_utf8ucs2(unsigned short *buffer,
66 const unsigned char *utf8)
67{
68 for (wchar_t *ucs2 = buffer;
69 ((utf8 = utf8decode(utf8, ucs2)), *ucs2); ucs2++);
70 return buffer;
71}
72
73#if 0
74unsigned char * strcpy_ucs2utf8(unsigned char *buffer,
75 const unsigned short *ucs2)
76{
77 for (unsigned char *utf8 = buffer;
78 ((utf8 = utf8encode(*ucs2, utf8)), *ucs2); ucs2++);
79 return buffer;
80}
81
82size_t strlen_utf8ucs2(const unsigned char *utf8)
83{
84 /* This won't properly count multiword ucs2 so use the alternative
85 below for now which doesn't either */
86 size_t length = 0;
87 unsigned short ucschar[2];
88 for (unsigned char c = *utf8; c;
89 ((utf8 = utf8decode(utf8, ucschar)), c = *utf8))
90 length++;
91
92 return length;
93}
94#endif /* 0 */
95
96size_t strlen_utf8ucs2(const unsigned char *utf8)
97{
98 return utf8length(utf8);
99}
100
101size_t strlen_ucs2utf8(const unsigned short *ucs2)
102{
103 size_t length = 0;
104 unsigned char utf8char[4];
105
106 for (unsigned short c = *ucs2; c; (c = *++ucs2))
107 length += utf8encode(c, utf8char) - utf8char;
108
109 return length;
110}
111
112size_t strlcpy_ucs2utf8(char *buffer, const unsigned short *ucs2,
113 size_t bufsize)
114{
115 if (!buffer)
116 bufsize = 0;
117
118 size_t length = 0;
119 unsigned char utf8char[4];
120
121 for (unsigned short c = *ucs2; c; (c = *++ucs2))
122 {
123 /* If the last character won't fit, this won't split it */
124 size_t utf8size = utf8encode(c, utf8char) - utf8char;
125 if ((length += utf8size) < bufsize)
126 buffer = mempcpy(buffer, utf8char, utf8size);
127 }
128
129 /* Above won't ever copy to very end */
130 if (bufsize)
131 *buffer = '\0';
132
133 return length;
134}
135
136#define _toucs2(utf8) \
137 ({ const char *_utf8 = (utf8); \
138 size_t _l = strlen_utf8ucs2(_utf8); \
139 void *_buffer = alloca((_l + 1)*2); \
140 strcpy_utf8ucs2(_buffer, _utf8); })
141
142#define _toutf8(ucs2) \
143 ({ const char *_ucs2 = (ucs2); \
144 size_t _l = strlen_ucs2utf8(_ucs2); \
145 void *_buffer = alloca(_l + 1); \
146 strcpy_ucs2utf8(_buffer, _ucs2); })
147
148int os_open(const char *ospath, int oflag, ...)
149{
150 return _wopen(_toucs2(ospath), oflag __OPEN_MODE_ARG);
151}
152
153int os_creat(const char *ospath, mode_t mode)
154{
155 return _wcreat(_toucs2(ospath), mode);
156}
157
158int os_stat(const char *ospath, struct _stat *s)
159{
160 return _wstat(_toucs2(ospath), s);
161}
162
163int os_remove(const char *ospath)
164{
165 return _wremove(_toucs2(ospath));
166}
167
168int os_rename(const char *osold, const char *osnew)
169{
170 int errnum = errno;
171
172 const wchar_t *wchosold = _toucs2(osold);
173 const wchar_t *wchosnew = _toucs2(osnew);
174
175 int rc = _wrename(wchosold, wchosnew);
176 if (rc < 0 && errno == EEXIST)
177 {
178 /* That didn't work; do cheap POSIX mimic */
179 BY_HANDLE_FILE_INFORMATION info;
180 if (win32_stat(osold, &info))
181 return -1;
182
183 if ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
184 !RemoveDirectoryW(wchosnew))
185 {
186 win32_last_error_errno();
187 return -1;
188 }
189
190 if (MoveFileExW(wchosold, wchosnew, MOVEFILE_REPLACE_EXISTING |
191 MOVEFILE_WRITE_THROUGH))
192 {
193 errno = errnum;
194 return 0;
195 }
196
197 errno = EIO;
198 }
199
200 return rc;
201}
202
203bool os_file_exists(const char *ospath)
204{
205 HANDLE h = win32_open(ospath);
206 if (h == INVALID_HANDLE_VALUE)
207 return false;
208
209 CloseHandle(h);
210 return true;
211}
212
213_WDIR * os_opendir(const char *osdirname)
214{
215 return _wopendir(_toucs2(osdirname));
216}
217
218int os_mkdir(const char *ospath, mode_t mode)
219{
220 return _wmkdir(_toucs2(ospath));
221 (void)mode;
222}
223
224int os_rmdir(const char *ospath)
225{
226 return _wrmdir(_toucs2(ospath));
227}
228
229int os_dirfd(_WDIR *osdirp)
230{
231#ifdef ENOTSUP
232 errno = ENOTSUP
233#else
234 errno = ENOSYS;
235#endif
236 return -1;
237 (void)osdirp;
238}
239
240int os_opendirfd(const char *osdirname)
241{
242 HANDLE h = win32_open(osdirname);
243 if (h == INVALID_HANDLE_VALUE)
244 return -1;
245
246 BY_HANDLE_FILE_INFORMATION info;
247 if (!GetFileInformationByHandle(h, &info))
248 errno = EIO;
249 else if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
250 errno = ENOTDIR;
251 else
252 {
253 /* Convert OS handle to fd; the fd now owns it */
254 int osfd = _open_osfhandle((long)h, O_RDONLY);
255 if (osfd >= 0)
256 return osfd;
257 }
258
259 CloseHandle(h);
260 return -2;
261}
262#endif /* __MINGW32__ */
263
264static size_t win32_path_strip_root(const char *ospath)
265{
266 const char *p = ospath;
267 int c = toupper(*p);
268
269 if (c >= 'A' && c <= 'Z')
270 {
271 /* drive */
272 if ((c = *++p) == ':')
273 return 2;
274 }
275
276 if (c == '\\' && *++p == '\\')
277 {
278 /* UNC */
279 while ((c = *++p) && c != '/' && c != '\\');
280 return p - ospath;
281 }
282
283 return 0;
284}
285
286static HANDLE win32_open(const char *ospath)
287{
288 /* FILE_FLAG_BACKUP_SEMANTICS is required for this to succeed at opening
289 a directory */
290 HANDLE h = CreateFileW(_toucs2(ospath), GENERIC_READ,
291 FILE_SHARE_READ | FILE_SHARE_WRITE |
292 FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
293 FILE_FLAG_BACKUP_SEMANTICS, NULL);
294
295 if (h == INVALID_HANDLE_VALUE)
296 win32_last_error_errno();
297
298 return h;
299}
300
301static int win32_fstat(int osfd, HANDLE hFile,
302 LPBY_HANDLE_FILE_INFORMATION lpInfo)
303{
304 /* The file descriptor takes precedence over the win32 file handle */
305 if (osfd >= 0)
306 hFile = (HANDLE)_get_osfhandle(osfd);
307
308 int rc = GetFileInformationByHandle(hFile, lpInfo) ? 0 : -1;
309 if (rc < 0)
310 win32_last_error_errno();
311
312 return rc;
313}
314
315static int win32_stat(const char *ospath, LPBY_HANDLE_FILE_INFORMATION lpInfo)
316{
317 HANDLE h = win32_open(ospath);
318 if (h == INVALID_HANDLE_VALUE)
319 return -1;
320
321 int rc = win32_fstat(-1, h, lpInfo);
322
323 CloseHandle(h);
324
325 return rc;
326}
327
328int os_opendir_and_fd(const char *osdirname, _WDIR **osdirpp,
329 int *osfdp)
330{
331 /* another possible way is to use open() then fdopendir() */
332 *osdirpp = NULL;
333 *osfdp = -1;
334
335 _WDIR *dirp = os_opendir(osdirname);
336 if (!dirp)
337 return -1;
338
339 int rc = 0;
340 int errnum = errno;
341
342 int fd = os_dirfd(dirp);
343 if (fd < 0)
344 {
345 fd = os_opendirfd(osdirname);
346 rc = 1;
347 }
348
349 if (fd < 0)
350 {
351 os_closedir(dirp);
352 return -2;
353 }
354
355 errno = errnum;
356
357 *osdirpp = dirp;
358 *osfdp = fd;
359
360 return rc;
361}
362
363int os_fsamefile(int osfd1, int osfd2)
364{
365 BY_HANDLE_FILE_INFORMATION info1, info2;
366
367 if (!win32_fstat(osfd1, INVALID_HANDLE_VALUE, &info1) ||
368 !win32_fstat(osfd2, INVALID_HANDLE_VALUE, &info2))
369 return -1;
370
371 return SAME_FILE_INFO(&info1, &info2) ? 1 : 0;
372}
373
374int os_relate(const char *ospath1, const char *ospath2)
375{
376 DEBUGF("\"%s\" : \"%s\"\n", ospath1, ospath2);
377
378 if (!ospath2 || !*ospath2)
379 {
380 errno = ospath2 ? ENOENT : EFAULT;
381 return -1;
382 }
383
384 /* First file must stay open for duration so that its stats don't change */
385 HANDLE h1 = win32_open(ospath1);
386 if (h1 == INVALID_HANDLE_VALUE)
387 return -2;
388
389 BY_HANDLE_FILE_INFORMATION info1;
390 if (win32_fstat(-1, h1, &info1))
391 {
392 CloseHandle(h1);
393 return -3;
394 }
395
396 char path2buf[strlen(ospath2) + 1];
397 *path2buf = 0;
398
399 ssize_t len = 0;
400 const char *p = ospath2;
401 size_t rootlen = win32_path_strip_root(ospath2);
402 const char *sepmo = PA_SEP_SOFT;
403
404 if (rootlen)
405 {
406 strmemcpy(path2buf, ospath2, rootlen);
407 ospath2 += rootlen;
408 sepmo = PA_SEP_HARD;
409 }
410
411 int rc = RELATE_DIFFERENT;
412
413 while (1)
414 {
415 if (sepmo != PA_SEP_HARD &&
416 !(len = parse_path_component(&ospath2, &p)))
417 {
418 break;
419 }
420
421 char compname[len + 1];
422 strmemcpy(compname, p, len);
423
424 path_append(path2buf, sepmo, compname, sizeof (path2buf));
425 sepmo = PA_SEP_SOFT;
426
427 int errnum = errno; /* save and restore if not actually failing */
428 BY_HANDLE_FILE_INFORMATION info2;
429
430 if (!win32_stat(path2buf, &info2))
431 {
432 if (SAME_FILE_INFO(&info1, &info2))
433 {
434 rc = RELATE_SAME;
435 }
436 else if (rc == RELATE_SAME)
437 {
438 if (name_is_dot_dot(compname))
439 rc = RELATE_DIFFERENT;
440 else if (!name_is_dot(compname))
441 rc = RELATE_PREFIX;
442 }
443 }
444 else if (errno == ENOENT && !*GOBBLE_PATH_SEPCH(ospath2) &&
445 !name_is_dot_dot(compname))
446 {
447 if (rc == RELATE_SAME)
448 rc = RELATE_PREFIX;
449
450 errno = errnum;
451 break;
452 }
453 else
454 {
455 rc = -4;
456 break;
457 }
458 }
459
460 CloseHandle(h1);
461
462 return rc;
463}
464
465void volume_size(IF_MV(int volume,) unsigned long *sizep, unsigned long *freep)
466{
467 ULARGE_INTEGER free = { .QuadPart = 0 },
468 size = { .QuadPart = 0 };
469
470 char volpath[MAX_PATH];
471 if (os_volume_path(IF_MV(volume, ) volpath, sizeof (volpath)) >= 0)
472 GetDiskFreeSpaceExW(_toucs2(volpath), &free, &size, NULL);
473
474 if (sizep)
475 *sizep = size.QuadPart / 1024;
476
477 if (freep)
478 *freep = free.QuadPart / 1024;
479}
diff --git a/firmware/target/hosted/filesystem-win32.h b/firmware/target/hosted/filesystem-win32.h
new file mode 100644
index 0000000000..1d8f2749f9
--- /dev/null
+++ b/firmware/target/hosted/filesystem-win32.h
@@ -0,0 +1,111 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _FILESYSTEM_WIN32_H_
22#define _FILESYSTEM_WIN32_H_
23
24#ifndef OSFUNCTIONS_DECLARED
25
26#ifdef __MINGW32__
27/* filesystem-win32.c contains some string functions that could be useful
28 * elsewhere; just move them away to unicode.c or something if they prove
29 * so. */
30size_t strlcpy_ucs2utf8(char *buffer, const unsigned short *ucs,
31 size_t bufsize);
32
33#define strlcpy_from_os strlcpy_ucs2utf8
34#endif /* __MINGW32__ */
35
36#endif /* !OSFUNCTIONS_DECLARED */
37
38#endif /* _FILESYSTEM_WIN32_H_ */
39
40#ifdef __MINGW32__
41
42#ifdef _FILE_H_
43#ifndef _FILESYSTEM_WIN32__FILE_H_
44#define _FILESYSTEM_WIN32__FILE_H_
45
46#include <unistd.h>
47#include <sys/stat.h>
48
49#define OS_STAT_T struct _stat
50
51#ifndef OSFUNCTIONS_DECLARED
52/* Wrap for off_t <=> long conversions */
53static inline off_t os_filesize_(int osfd)
54 { return _filelength(osfd); }
55static inline int os_ftruncate_(int osfd, off_t length)
56 { return _chsize(osfd, length); }
57
58#define os_filesize os_filesize_
59#define os_ftruncate os_ftruncate_
60#define os_fsync _commit
61#define os_fstat _fstat
62#define os_close close
63#define os_lseek lseek
64#ifndef os_read
65#define os_read read
66#endif
67#ifndef os_write
68#define os_write write
69#endif
70
71/* These need string type conversion from utf8 to ucs2; that's done inside */
72int os_open(const char *ospath, int oflag, ...);
73int os_creat(const char *ospath, mode_t mode);
74int os_stat(const char *ospath, struct _stat *s);
75int os_remove(const char *ospath);
76int os_rename(const char *osold, const char *osnew);
77
78#endif /* !OSFUNCTIONS_DECLARED */
79
80#endif /* _FILESYSTEM_WIN32__FILE_H_ */
81#endif /* _FILE_H_ */
82
83#ifdef _DIR_H_
84#ifndef _FILESYSTEM_WIN32__DIR_H_
85#define _FILESYSTEM_WIN32__DIR_H_
86
87#include <dirent.h>
88
89#define OS_DIRENT_CONVERT /* needs string conversion */
90#define OS_DIR_T _WDIR
91#define OS_DIRENT_T struct _wdirent
92
93#ifndef OSFUNCTIONS_DECLARED
94
95_WDIR * os_opendir(const char *osdirname);
96int os_opendirfd(const char *osdirname);
97#define os_readdir _wreaddir
98#define os_closedir _wclosedir
99int os_mkdir(const char *ospath, mode_t mode);
100int os_rmdir(const char *ospath);
101
102#endif /* OSFUNCTIONS_DECLARED */
103
104#endif /* _FILESYSTEM_WIN32__DIR_H_ */
105#endif /* _DIR_H_ */
106
107#else /* !__MINGW32__ */
108
109#include "filesystem-unix.h"
110
111#endif /* __MINGW32__ */
diff --git a/firmware/target/hosted/lc-unix.c b/firmware/target/hosted/lc-unix.c
index 6e5f15ec99..810dc9f92c 100644
--- a/firmware/target/hosted/lc-unix.c
+++ b/firmware/target/hosted/lc-unix.c
@@ -50,14 +50,3 @@ void lc_close(void *handle)
50{ 50{
51 dlclose(handle); 51 dlclose(handle);
52} 52}
53
54void *lc_open_from_mem(void *addr, size_t blob_size)
55{
56 (void)addr;
57 (void)blob_size;
58 /* we don't support loading code from memory on application builds,
59 * it doesn't make sense (since it means writing the blob to disk again and
60 * then falling back to load from disk) and requires the ability to write
61 * to an executable directory */
62 return NULL;
63}
diff --git a/firmware/export/filefuncs.h b/firmware/target/hosted/sdl/app/load_code-sdl-app.c
index 92e97f65b0..686944343f 100644
--- a/firmware/export/filefuncs.h
+++ b/firmware/target/hosted/sdl/app/load_code-sdl-app.c
@@ -7,7 +7,7 @@
7 * \/ \/ \/ \/ \/ 7 * \/ \/ \/ \/ \/
8 * $Id$ 8 * $Id$
9 * 9 *
10 * Copyright (C) 2009 by Maurus Cuelenaere 10 * Copyright (C) 2010 Thomas Martitz
11 * 11 *
12 * This program is free software; you can redistribute it and/or 12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License 13 * modify it under the terms of the GNU General Public License
@@ -18,22 +18,19 @@
18 * KIND, either express or implied. 18 * KIND, either express or implied.
19 * 19 *
20 ****************************************************************************/ 20 ****************************************************************************/
21 21#define RB_FILESYSTEM_OS
22#ifndef __INCLUDE_FILEFUNCS_H_ 22#include "system.h"
23#define __INCLUDE_FILEFUNCS_H_
24
25#include <stdbool.h>
26#include "config.h"
27#include "file.h" 23#include "file.h"
28#include "dir.h" 24#include "load_code.h"
29
30#ifdef HAVE_MULTIVOLUME
31int strip_volume(const char* name, char* namecopy);
32#endif
33
34bool file_exists(const char *file);
35bool dir_exists(const char *path);
36 25
37extern struct dirinfo dir_get_info(DIR* parent, struct dirent *entry); 26void *lc_open(const char *filename, unsigned char *buf, size_t buf_size)
27{
28 char realpath[MAX_PATH];
29 const char *fpath = handle_special_dirs(filename, 0, realpath,
30 sizeof (realpath));
31 if (!fpath)
32 return NULL;
38 33
39#endif /* __INCLUDE_FILEFUNCS_H_ */ 34 return os_lc_open(fpath);
35 (void)buf; (void)buf_size;
36}
diff --git a/firmware/target/hosted/sdl/filesystem-sdl.c b/firmware/target/hosted/sdl/filesystem-sdl.c
new file mode 100644
index 0000000000..5a8e2c417a
--- /dev/null
+++ b/firmware/target/hosted/sdl/filesystem-sdl.c
@@ -0,0 +1,55 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#define RB_FILESYSTEM_OS
22#include "config.h"
23#include "system.h"
24#include "thread-sdl.h"
25#include "mutex.h"
26#include "file.h"
27
28#ifdef HAVE_SDL_THREADS
29#define YIELD_THRESHOLD 512
30static bool initialized = false;
31static struct mutex readwrite_mtx;
32
33/* Allow other threads to run while performing I/O */
34ssize_t os_sdl_readwrite(int osfd, void *buf, size_t nbyte, bool dowrite)
35{
36 if (!initialized)
37 {
38 mutex_init(&readwrite_mtx);
39 initialized = true;
40 }
41
42 mutex_lock(&readwrite_mtx);
43
44 void *mythread = nbyte > YIELD_THRESHOLD ? sim_thread_unlock() : NULL;
45
46 ssize_t rc = dowrite ? write(osfd, buf, nbyte) : read(osfd, buf, nbyte);
47
48 if (mythread)
49 sim_thread_lock(mythread);
50
51 mutex_unlock(&readwrite_mtx);
52 return rc;
53}
54
55#endif /* HAVE_SDL_THREADS */
diff --git a/firmware/target/hosted/sdl/filesystem-sdl.h b/firmware/target/hosted/sdl/filesystem-sdl.h
new file mode 100644
index 0000000000..934b43b34b
--- /dev/null
+++ b/firmware/target/hosted/sdl/filesystem-sdl.h
@@ -0,0 +1,37 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _FILESYSTEM_SDL_H_
22#define _FILESYSTEM_SDL_H_
23
24#ifdef HAVE_SDL_THREADS
25#undef os_read
26#undef os_write
27
28ssize_t os_sdl_readwrite(int osfd, void *buf, size_t nbyte, bool dowrite);
29
30#define os_read(osfd, buf, nbyte) \
31 os_sdl_readwrite((osfd), (buf), (nbyte), false)
32#define os_write(osfd, buf, nbyte) \
33 os_sdl_readwrite((osfd), (void *)(buf), (nbyte), true)
34
35#endif /* HAVE_SDL_THREADS */
36
37#endif /* _FILESYSTEM_SDL_H_ */
diff --git a/firmware/target/hosted/sdl/load_code-sdl.c b/firmware/target/hosted/sdl/load_code-sdl.c
new file mode 100644
index 0000000000..ee29853ab5
--- /dev/null
+++ b/firmware/target/hosted/sdl/load_code-sdl.c
@@ -0,0 +1,52 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Daniel Stenberg
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#define RB_FILESYSTEM_OS
22#include <SDL_loadso.h>
23#include "system.h"
24#include "load_code.h"
25#include "filesystem-sdl.h"
26#include "debug.h"
27
28void * os_lc_open(const char *ospath)
29{
30 void *handle = SDL_LoadObject(ospath);
31 if (handle == NULL)
32 {
33 DEBUGF("%s(\"%s\") failed\n", __func__, ospath);
34 DEBUGF(" SDL error '%s'\n", SDL_GetError());
35 }
36
37 return handle;
38}
39
40void * lc_get_header(void *handle)
41{
42 char *ret = SDL_LoadFunction(handle, "__header");
43 if (ret == NULL)
44 ret = SDL_LoadFunction(handle, "___header");
45
46 return ret;
47}
48
49void lc_close(void *handle)
50{
51 SDL_UnloadObject(handle);
52}
diff --git a/firmware/target/hosted/sdl/system-sdl.c b/firmware/target/hosted/sdl/system-sdl.c
index fdf79d9333..aa322ddf3a 100644
--- a/firmware/target/hosted/sdl/system-sdl.c
+++ b/firmware/target/hosted/sdl/system-sdl.c
@@ -51,6 +51,8 @@
51 51
52#endif 52#endif
53 53
54#define SIMULATOR_DEFAULT_ROOT "simdisk"
55
54SDL_Surface *gui_surface; 56SDL_Surface *gui_surface;
55 57
56bool background = true; /* use backgrounds by default */ 58bool background = true; /* use backgrounds by default */
@@ -63,7 +65,7 @@ bool debug_buttons = false;
63bool lcd_display_redraw = true; /* Used for player simulator */ 65bool lcd_display_redraw = true; /* Used for player simulator */
64char having_new_lcd = true; /* Used for player simulator */ 66char having_new_lcd = true; /* Used for player simulator */
65bool sim_alarm_wakeup = false; 67bool sim_alarm_wakeup = false;
66const char *sim_root_dir = NULL; 68const char *sim_root_dir = SIMULATOR_DEFAULT_ROOT;
67 69
68static SDL_Thread *evt_thread = NULL; 70static SDL_Thread *evt_thread = NULL;
69 71
diff --git a/firmware/target/hosted/sdl/system-sim.h b/firmware/target/hosted/sdl/system-sim.h
new file mode 100644
index 0000000000..16c0cdde52
--- /dev/null
+++ b/firmware/target/hosted/sdl/system-sim.h
@@ -0,0 +1,32 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _SYSTEM_SIM_H_
22#define _SYSTEM_SIM_H_
23
24#ifdef WIN32
25#include <time.h>
26struct tm * localtime_r(const time_t *restrict timer,
27 struct tm *restrict result);
28struct tm * gmtime_r(const time_t *restrict timer,
29 struct tm *restrict result);
30#endif /* WIN32 */
31
32#endif /* _SYSTEM_SIM_H_ */
diff --git a/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c b/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c
index 9c0d1982ad..0e74444cf3 100644
--- a/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c
+++ b/firmware/target/mips/ingenic_jz47xx/ata-sd-jz4740.c
@@ -26,7 +26,6 @@
26#include "ata_idle_notify.h" 26#include "ata_idle_notify.h"
27#include "ata-sd-target.h" 27#include "ata-sd-target.h"
28#include "disk.h" 28#include "disk.h"
29#include "fat.h"
30#include "led.h" 29#include "led.h"
31#include "sdmmc.h" 30#include "sdmmc.h"
32#include "logf.h" 31#include "logf.h"
@@ -1467,34 +1466,28 @@ static void sd_thread(void)
1467 { 1466 {
1468#ifdef HAVE_HOTSWAP 1467#ifdef HAVE_HOTSWAP
1469 case SYS_HOTSWAP_INSERTED: 1468 case SYS_HOTSWAP_INSERTED:
1470 case SYS_HOTSWAP_EXTRACTED: 1469 case SYS_HOTSWAP_EXTRACTED:;
1471 fat_lock(); /* lock-out FAT activity first - 1470 int success = 1;
1472 prevent deadlocking via disk_mount that
1473 would cause a reverse-order attempt with
1474 another thread */
1475 mutex_lock(&sd_mtx); /* lock-out card activity - direct calls
1476 into driver that bypass the fat cache */
1477 1471
1478 /* We now have exclusive control of fat cache and ata */ 1472 disk_unmount(sd_drive_nr); /* release "by force" */
1479 1473
1480 disk_unmount(sd_drive_nr); /* release "by force", ensure file 1474 mutex_lock(&sd_mtx); /* lock-out card activity */
1481 descriptors aren't leaked and any busy
1482 ones are invalid if mounting */
1483 1475
1484 /* Force card init for new card, re-init for re-inserted one or 1476 /* Force card init for new card, re-init for re-inserted one or
1485 * clear if the last attempt to init failed with an error. */ 1477 * clear if the last attempt to init failed with an error. */
1486 card.initialized = 0; 1478 card.initialized = 0;
1487 1479
1480 mutex_unlock(&sd_mtx);
1481
1488 if(ev.id == SYS_HOTSWAP_INSERTED) 1482 if(ev.id == SYS_HOTSWAP_INSERTED)
1489 disk_mount(sd_drive_nr); 1483 success = disk_mount(sd_drive_nr); /* 0 if fail */
1490 1484
1491 queue_broadcast(SYS_FS_CHANGED, 0); 1485 if(success)
1486 queue_broadcast(SYS_FS_CHANGED, 0);
1492 1487
1493 /* Access is now safe */
1494 mutex_unlock(&sd_mtx);
1495 fat_unlock();
1496 break; 1488 break;
1497#endif 1489#endif /* HAVE_HOTSWAP */
1490
1498 case SYS_TIMEOUT: 1491 case SYS_TIMEOUT:
1499 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ))) 1492 if (TIME_BEFORE(current_tick, last_disk_activity+(3*HZ)))
1500 idle_notified = false; 1493 idle_notified = false;
diff --git a/firmware/usbstack/usb_storage.c b/firmware/usbstack/usb_storage.c
index 59ada3bdd8..48e9442420 100644
--- a/firmware/usbstack/usb_storage.c
+++ b/firmware/usbstack/usb_storage.c
@@ -355,10 +355,7 @@ static bool check_disk_present(IF_MD_NONVOID(int volume))
355#ifdef USB_USE_RAMDISK 355#ifdef USB_USE_RAMDISK
356 return true; 356 return true;
357#else 357#else
358 unsigned char* sector = fat_get_sector_buffer(); 358 return disk_present(IF_MD(volume));
359 bool success = storage_read_sectors(IF_MD(volume,)0,1,sector) == 0;
360 fat_release_sector_buffer();
361 return success;
362#endif 359#endif
363} 360}
364 361
diff --git a/tools/checkwps/SOURCES b/tools/checkwps/SOURCES
index 828b7965ea..425e8de7b9 100644
--- a/tools/checkwps/SOURCES
+++ b/tools/checkwps/SOURCES
@@ -1,13 +1,21 @@
1#undef unix /* messes up filesystem-unix.c below */
1../../apps/gui/skin_engine/skin_parser.c 2../../apps/gui/skin_engine/skin_parser.c
2../../apps/gui/skin_engine/skin_backdrops.c 3../../apps/gui/skin_engine/skin_backdrops.c
3../../apps/gui/viewport.c 4../../apps/gui/viewport.c
4../../apps/misc.c 5../../apps/misc.c
5../../firmware/common/strlcpy.c 6../../firmware/common/strlcpy.c
6checkwps.c 7../../firmware/common/pathfuncs.c
7 8../../firmware/asm/mempcpy.c
9../../firmware/target/hosted/filesystem-unix.c
8#ifdef APPLICATION 10#ifdef APPLICATION
9../../firmware/common/rbpaths.c 11../../firmware/target/hosted/filesystem-app.c
12#else
13../../uisimulator/common/filesystem-sim.c
10#endif 14#endif
15#ifdef DEBUG
16../../firmware/debug.c
17#endif
18checkwps.c
11 19
12#ifdef HAVE_LCD_BITMAP 20#ifdef HAVE_LCD_BITMAP
13../../apps/recorder/bmp.c 21../../apps/recorder/bmp.c
diff --git a/tools/checkwps/checkwps.c b/tools/checkwps/checkwps.c
index 10e505f006..c2cadc7444 100644
--- a/tools/checkwps/checkwps.c
+++ b/tools/checkwps/checkwps.c
@@ -39,11 +39,14 @@ bool debug_wps = true;
39int wps_verbose_level = 0; 39int wps_verbose_level = 0;
40char *skin_buffer; 40char *skin_buffer;
41 41
42int errno; 42const char *sim_root_dir = ".";
43
44const struct settings_list *settings; 43const struct settings_list *settings;
45const int nb_settings = 0; 44const int nb_settings = 0;
46 45
46#ifdef SIMULATOR
47#error beep beep
48#endif
49
47/* static endianness conversion */ 50/* static endianness conversion */
48#define SWAP_16(x) ((typeof(x))(unsigned short)(((unsigned short)(x) >> 8) | \ 51#define SWAP_16(x) ((typeof(x))(unsigned short)(((unsigned short)(x) >> 8) | \
49 ((unsigned short)(x) << 8))) 52 ((unsigned short)(x) << 8)))
diff --git a/tools/checkwps/file.h b/tools/checkwps/file.h
deleted file mode 100644
index a14d7f5b91..0000000000
--- a/tools/checkwps/file.h
+++ /dev/null
@@ -1,16 +0,0 @@
1#ifndef MAX_PATH
2#define MAX_PATH 260
3#endif
4
5/* Wrapper - required for O_RDONLY */
6
7#include <fcntl.h>
8
9extern ssize_t read(int fd, void *buf, size_t count);
10extern ssize_t write(int fd, const void *buf, size_t count);
11extern off_t lseek(int fildes, off_t offset, int whence);
12extern int close(int fd);
13
14/* strlcpy doesn't belong here (it's in string.h in the rockbox sources),
15 * but this avoids complicated magic to override the system string.h */
16size_t strlcpy(char *dst, const char *src, size_t siz);
diff --git a/tools/database/SOURCES b/tools/database/SOURCES
index 5c9b971583..71593bba11 100644
--- a/tools/database/SOURCES
+++ b/tools/database/SOURCES
@@ -1,15 +1,21 @@
1#undef unix /* messes up filesystem-unix.c below */
1database.c 2database.c
2../../apps/misc.c 3../../apps/misc.c
3../../apps/tagcache.c 4../../apps/tagcache.c
4../../firmware/common/crc32.c 5../../firmware/common/crc32.c
5../../firmware/common/filefuncs.c 6../../firmware/common/pathfuncs.c
6../../firmware/common/strlcpy.c 7../../firmware/common/strlcpy.c
7../../firmware/common/strcasestr.c 8../../firmware/common/strcasestr.c
8../../firmware/common/structec.c 9../../firmware/common/structec.c
9../../firmware/common/unicode.c 10../../firmware/common/unicode.c
10../../firmware/target/hosted/debug-hosted.c 11../../firmware/target/hosted/debug-hosted.c
11../../firmware/logf.c 12../../firmware/logf.c
12../../uisimulator/common/io.c 13../../firmware/target/hosted/filesystem-unix.c
14#ifdef APPLICATION
15../../firmware/target/hosted/filesystem-app.c
16#else /* !APPLICATION */
17../../uisimulator/common/filesystem-sim.c
18#endif /* APPLICATION */
13#if CONFIG_CODEC != SWCODEC 19#if CONFIG_CODEC != SWCODEC
14../../lib/rbcodec/metadata/id3tags.c 20../../lib/rbcodec/metadata/id3tags.c
15../../lib/rbcodec/metadata/metadata.c 21../../lib/rbcodec/metadata/metadata.c
diff --git a/tools/root.make b/tools/root.make
index 4d58e26e8e..4e0ca7c4f0 100644
--- a/tools/root.make
+++ b/tools/root.make
@@ -356,22 +356,22 @@ endif
356 356
357ifeq (,$(findstring android, $(APP_TYPE))) 357ifeq (,$(findstring android, $(APP_TYPE)))
358 358
359simext: 359simext1:
360 $(SILENT)mkdir -p $@ 360 $(SILENT)mkdir -p $@
361 361
362bininstall: $(BUILDDIR)/$(BINARY) 362bininstall: $(BUILDDIR)/$(BINARY)
363 @echo "Installing your rockbox binary in your '$(RBPREFIX)' dir" 363 @echo "Installing your rockbox binary in your '$(RBPREFIX)' dir"
364 $(SILENT)cp $(BINARY) "$(RBPREFIX)/.rockbox/" 364 $(SILENT)cp $(BINARY) "$(RBPREFIX)/.rockbox/"
365 365
366install: simext 366install: simext1
367 @echo "Installing your build in your '$(RBPREFIX)' dir" 367 @echo "Installing your build in your '$(RBPREFIX)' dir"
368 $(SILENT)$(TOOLSDIR)/buildzip.pl $(VERBOSEOPT) --app=$(APPLICATION) -m "$(MODELNAME)" -i "$(TARGET_ID)" $(INSTALL) -z "zip -r0" -r "$(ROOTDIR)" --rbdir="$(RBDIR)" -f 0 $(TARGET) $(BINARY) 368 $(SILENT)$(TOOLSDIR)/buildzip.pl $(VERBOSEOPT) --app=$(APPLICATION) -m "$(MODELNAME)" -i "$(TARGET_ID)" $(INSTALL) -z "zip -r0" -r "$(ROOTDIR)" --rbdir="$(RBDIR)" -f 0 $(TARGET) $(BINARY)
369 369
370fullinstall: simext 370fullinstall: simext1
371 @echo "Installing a full setup in your '$(RBPREFIX)' dir" 371 @echo "Installing a full setup in your '$(RBPREFIX)' dir"
372 $(SILENT)$(TOOLSDIR)/buildzip.pl $(VERBOSEOPT) --app=$(APPLICATION) -m "$(MODELNAME)" -i "$(TARGET_ID)" $(INSTALL) -z "zip -r0" -r "$(ROOTDIR)" --rbdir="$(RBDIR)" -f 2 $(TARGET) $(BINARY) 372 $(SILENT)$(TOOLSDIR)/buildzip.pl $(VERBOSEOPT) --app=$(APPLICATION) -m "$(MODELNAME)" -i "$(TARGET_ID)" $(INSTALL) -z "zip -r0" -r "$(ROOTDIR)" --rbdir="$(RBDIR)" -f 2 $(TARGET) $(BINARY)
373 373
374symlinkinstall: simext 374symlinkinstall: simext1
375 @echo "Installing a full setup with links in your '$(RBPREFIX)' dir" 375 @echo "Installing a full setup with links in your '$(RBPREFIX)' dir"
376 $(SILENT)$(TOOLSDIR)/buildzip.pl $(VERBOSEOPT) --app=$(APPLICATION) -m "$(MODELNAME)" -i "$(TARGET_ID)" $(INSTALL) -z "zip -r0" -r "$(ROOTDIR)" --rbdir="$(RBDIR)" -f 2 $(TARGET) $(BINARY) -l 376 $(SILENT)$(TOOLSDIR)/buildzip.pl $(VERBOSEOPT) --app=$(APPLICATION) -m "$(MODELNAME)" -i "$(TARGET_ID)" $(INSTALL) -z "zip -r0" -r "$(ROOTDIR)" --rbdir="$(RBDIR)" -f 2 $(TARGET) $(BINARY) -l
377endif 377endif
diff --git a/uisimulator/common/SOURCES b/uisimulator/common/SOURCES
index 939f1638c3..9833753236 100644
--- a/uisimulator/common/SOURCES
+++ b/uisimulator/common/SOURCES
@@ -1,4 +1,5 @@
1#ifdef SIMULATOR 1#ifdef SIMULATOR
2
2lcd-common.c 3lcd-common.c
3#ifdef HAVE_LCD_CHARCELLS 4#ifdef HAVE_LCD_CHARCELLS
4font-player.c 5font-player.c
@@ -8,8 +9,20 @@ sim_icons.c
8sim_tasks.c 9sim_tasks.c
9fmradio.c 10fmradio.c
10backlight-sim.c 11backlight-sim.c
11stubs.c
12powermgmt-sim.c 12powermgmt-sim.c
13filesystem-sim.c
14
15#ifdef WIN32
16time-win32.c
17#endif
18
19#ifndef __PCTOOL__
20load_code-sim.c
13#endif 21#endif
14 22
15io.c 23stubs.c
24
25#else
26dummylib.c /* for now, so the lib actually builds */
27#endif /* SIMULATOR */
28
diff --git a/uisimulator/common/dummylib.c b/uisimulator/common/dummylib.c
new file mode 100644
index 0000000000..9747edf3a1
--- /dev/null
+++ b/uisimulator/common/dummylib.c
@@ -0,0 +1 @@
/* this exists to get libuisimulator to make so that the sdl app may link */
diff --git a/uisimulator/common/filesystem-sim.c b/uisimulator/common/filesystem-sim.c
new file mode 100644
index 0000000000..766beb3fda
--- /dev/null
+++ b/uisimulator/common/filesystem-sim.c
@@ -0,0 +1,833 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Daniel Stenberg
11 * Copyright (C) 2014 Michael Sevakis
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22#define RB_FILESYSTEM_OS
23#include <stdio.h>
24#include <stdlib.h>
25#include <stdarg.h>
26#include <time.h>
27#include <errno.h>
28#include <limits.h>
29#include "config.h"
30#include "system.h"
31#include "file.h"
32#include "dir.h"
33#include "file_internal.h"
34#include "pathfuncs.h"
35#include "string-extra.h"
36#include "debug.h"
37
38#ifndef os_fstatat
39#define USE_OSDIRNAME /* we need to remember the open directory path */
40#endif
41
42extern const char *sim_root_dir;
43
44/* Windows (and potentially other OSes) distinguish binary and text files.
45 * Define a dummy for the others. */
46#ifndef O_BINARY
47#define O_BINARY 0
48#endif
49
50struct filestr_desc
51{
52 int osfd; /* The host OS file descriptor */
53 bool mounted; /* Is host volume still mounted? */
54#ifdef HAVE_MULTIVOLUME
55 int volume; /* The virtual volume number */
56#endif
57} openfiles[MAX_OPEN_FILES] =
58{
59 [0 ... MAX_OPEN_FILES-1] = { .osfd = -1 }
60};
61
62static struct filestr_desc * alloc_filestr(int *fildesp)
63{
64 for (unsigned int i = 0; i < MAX_OPEN_FILES; i++)
65 {
66 struct filestr_desc *filestr = &openfiles[i];
67 if (filestr->osfd < 0)
68 {
69 *fildesp = i;
70 return filestr;
71 }
72 }
73
74 return NULL;
75}
76
77static struct filestr_desc * get_filestr(int fildes)
78{
79 struct filestr_desc *filestr = &openfiles[fildes];
80
81 if ((unsigned int)fildes >= MAX_OPEN_FILES || filestr->osfd < 0)
82 filestr = NULL;
83 else if (filestr->mounted)
84 return filestr;
85
86 errno = filestr ? ENXIO : EBADF;
87 DEBUGF("fildes %d: %s\n", fildes, strerror(errno));
88 return NULL;
89}
90
91struct dirstr_desc
92{
93 int osfd; /* Host OS directory file descriptor */
94 bool osfd_opened; /* Host fd is another open file */
95 OS_DIR_T *osdirp; /* Host OS directory stream */
96#ifdef USE_OSDIRNAME
97 char *osdirname; /* Host OS directory path */
98#endif
99 struct sim_dirent entry; /* Rockbox directory entry */
100#ifdef HAVE_MULTIVOLUME
101 int volume; /* Virtual volume number */
102 int volumecounter; /* Counter for root volume entries */
103#endif
104 bool mounted; /* Is the virtual volume still mounted? */
105} opendirs[MAX_OPEN_DIRS];
106
107static struct dirstr_desc * alloc_dirstr(void)
108{
109 for (unsigned int i = 0; i < MAX_OPEN_DIRS; i++)
110 {
111 struct dirstr_desc *dirstr = &opendirs[i];
112 if (dirstr->osdirp == NULL)
113 return dirstr;
114 }
115
116 return NULL;
117}
118
119static struct dirstr_desc * get_dirstr(DIR *dirp)
120{
121 struct dirstr_desc *dirstr = (struct dirstr_desc *)dirp;
122
123 if (!PTR_IN_ARRAY(opendirs, dirstr, MAX_OPEN_DIRS) || !dirstr->osdirp)
124 dirstr = NULL;
125 else if (dirstr->mounted)
126 return dirstr;
127
128 errno = dirstr ? ENXIO : EBADF;
129 DEBUGF("dir #%d: %s\n", (int)(dirstr - opendirs), strerror(errno));
130 return NULL;
131}
132
133static int close_dirstr(struct dirstr_desc *dirstr)
134{
135 OS_DIR_T *osdirp = dirstr->osdirp;
136
137 dirstr->mounted = false;
138
139#ifdef USE_OSDIRNAME
140 free(dirstr->osdirname);
141#endif
142 if (dirstr->osfd_opened)
143 {
144 os_close(dirstr->osfd);
145 dirstr->osfd_opened = false;
146 }
147
148 int rc = os_closedir(osdirp);
149 dirstr->osdirp = NULL;
150
151 return rc;
152}
153
154#ifdef HAVE_MULTIVOLUME
155static int readdir_volume_inner(struct dirstr_desc *dirstr,
156 struct sim_dirent *entry)
157{
158 /* Volumes (secondary file systems) get inserted into the system root
159 * directory. If the path specified volume 0, enumeration will not
160 * include other volumes, but just its own files and directories.
161 *
162 * Fake special directories, which don't really exist, that will get
163 * redirected upon opendir()
164 */
165 while (++dirstr->volumecounter < NUM_VOLUMES)
166 {
167 /* on the system root */
168 if (!volume_present(dirstr->volumecounter))
169 continue;
170
171 get_volume_name(dirstr->volumecounter, entry->d_name);
172 return 1;
173 }
174
175 /* do normal directory entry fetching */
176 return 0;
177}
178#endif /* HAVE_MULTIVOLUME */
179
180static inline int readdir_volume(struct dirstr_desc *dirstr,
181 struct sim_dirent *entry)
182{
183#ifdef HAVE_MULTIVOLUME
184 if (dirstr->volumecounter < NUM_VOLUMES)
185 return readdir_volume_inner(dirstr, entry);
186#endif /* HAVE_MULTIVOLUME */
187
188 /* do normal directory entry fetching */
189 return 0;
190 (void)dirstr; (void)entry;
191}
192
193
194/** Internal functions **/
195
196#ifdef HAVE_MULTIDRIVE
197/**
198 * Handle drive extraction by pretending the files' volumes no longer exist
199 * and invalidating their I/O for the remainder of their lifetimes as would
200 * happen on a native target
201 */
202void sim_ext_extracted(int drive)
203{
204 for (unsigned int i = 0; i < MAX_OPEN_FILES; i++)
205 {
206 struct filestr_desc *filestr = &openfiles[i];
207 if (filestr->osfd >= 0 && volume_drive(filestr->volume) == drive)
208 filestr->mounted = false;
209 }
210
211 for (unsigned int i = 0; i < MAX_OPEN_DIRS; i++)
212 {
213 struct dirstr_desc *dirstr = &opendirs[i];
214 if (dirstr->osdirp && volume_drive(dirstr->volume) == drive)
215 dirstr->mounted = false;
216 }
217
218 (void)drive;
219}
220#endif /* HAVE_MULTIDRIVE */
221
222/**
223 * Provides target-like path parsing behavior with single and multiple volumes
224 * while performing minimal transforming of the input.
225 *
226 * Paths are sandboxed to the simulated namespace:
227 * e.g. "{simdir}/foo/../../../../bar" becomes "{simdir}/foo/../bar"
228 */
229int sim_get_os_path(char *buffer, const char *path, size_t bufsize)
230{
231 #define ADVBUF(amt) \
232 ({ buffer += (amt); bufsize -= (amt); })
233
234 #define PPP_SHOWPATHS 0
235
236 if (!path_is_absolute(path))
237 {
238 DEBUGF("ERROR: path is not absolute: \"%s\"\n", path);
239 errno = ENOENT;
240 return -1;
241 }
242
243#if PPP_SHOWPATHS
244 const char *const buffer0 = buffer;
245 DEBUGF("PPP (pre): \"%s\"\n", path);
246#endif
247
248 /* Prepend sim root */
249 size_t size = strlcpy(buffer, sim_root_dir, bufsize);
250 if (size >= bufsize)
251 {
252 errno = ENAMETOOLONG;
253 return -1;
254 }
255 ADVBUF(size);
256
257#ifdef HAVE_MULTIVOLUME
258 /* Track the last valid volume spec encountered */
259 int volume = -1;
260 bool sysroot = true;
261
262 /* Basename of sim dir to switch back to simdisk from ext */
263 #define DIRBASE_FMT ".." PATH_SEPSTR "%s"
264 ssize_t dirbase_len = 0;
265 char dirbase[size + 3 + 1];
266
267 /* Basename of ext directory to switch to alternate volume */
268 #define SIMEXT_FMT ".." PATH_SEPSTR "simext%d"
269 char extbuf[sizeof (SIMEXT_FMT) + 20 + 1];
270#endif /* HAVE_MULTIVOLUME */
271
272 int level = 0;
273 bool done = false;
274 while (!done)
275 {
276 const char *p;
277 ssize_t len = parse_path_component(&path, &p);
278
279
280 switch (len)
281 {
282 case 0:
283 done = true;
284 if (path[-1] != PATH_SEPCH)
285 continue;
286
287 /* Path ends with a separator; preserve that */
288 p = &path[-1];
289 len = 1;
290 break;
291
292 case 1:
293 case 2:
294 if (p[0] == '.')
295 {
296 if (len == 1)
297 break;
298
299 if (p[1] == '.')
300 goto is_dot_dot;
301 }
302
303 default:
304 level++;
305
306 #ifdef HAVE_MULTIVOLUME
307 if (level != 1)
308 break; /* Volume spec only valid @ root level */
309
310 const char *next;
311 volume = path_strip_volume(p, &next, true);
312
313 if (next > p)
314 {
315 #ifdef HAVE_MULTIDRIVE
316 /* Feign failure if the volume isn't "mounted" */
317 if (!volume_present(volume))
318 {
319 errno = ENXIO;
320 return -1;
321 }
322 #endif /* HAVE_MULTIDRIVE */
323
324 sysroot = false;
325
326 if (volume == 0)
327 continue;
328
329 p = extbuf;
330 len = snprintf(extbuf, sizeof (extbuf), SIMEXT_FMT, volume);
331 }
332 #endif /* HAVE_MULTIVOLUME */
333 break;
334
335 is_dot_dot:
336 if (level <= 0)
337 continue; /* Can't go above root; erase */
338
339 level--;
340
341 #ifdef HAVE_MULTIVOLUME
342 if (level == 0)
343 {
344 int v = volume;
345 bool sr = sysroot;
346 volume = -1;
347 sysroot = true;
348
349 if (v <= 0)
350 {
351 if (sr)
352 break;
353
354 continue;
355 }
356
357 /* Going up from a volume root and back down to the sys root */
358 if (dirbase_len == 0)
359 {
360 /* Get the main simdisk directory so it can be reentered */
361 char tmpbuf[sizeof (dirbase)];
362 #ifdef WIN32
363 path_correct_separators(tmpbuf, sim_root_dir);
364 path_strip_drive(tmpbuf, &p, false);
365 #else
366 p = tmpbuf;
367 strcpy(tmpbuf, sim_root_dir);
368 #endif
369 size = path_basename(p, &p);
370 ((char *)p)[size] = '\0';
371
372 if (size == 0 || is_dotdir_name(p))
373 {
374 /* This is nonsense and won't work */
375 DEBUGF("ERROR: sim root dir basname is dotdir or"
376 " empty: \"%s\"\n", sim_root_dir);
377 errno = ENOENT;
378 return -1;
379 }
380
381 dirbase_len = snprintf(dirbase, sizeof (dirbase),
382 DIRBASE_FMT, p);
383 }
384
385 p = dirbase;
386 len = dirbase_len;
387 break;
388 }
389 #endif /* HAVE_MULTIVOLUME */
390 break;
391 } /* end switch */
392
393 char compname[len + 1];
394 strmemcpy(compname, p, len);
395
396 size = path_append(buffer, PA_SEP_HARD, compname, bufsize);
397 if (size >= bufsize)
398 {
399 errno = ENAMETOOLONG;
400 return -1;
401 }
402 ADVBUF(size);
403 }
404
405#if PPP_SHOWPATHS
406 DEBUGF("PPP (post): \"%s\"" IF_MV(" vol:%d") "\n",
407 buffer0 IF_MV(, volume));
408#endif
409
410 return IF_MV(volume) +1;
411}
412
413
414/** File functions **/
415
416int sim_open(const char *path, int oflag, ...)
417{
418 int fildes;
419 struct filestr_desc *filestr = alloc_filestr(&fildes);
420 if (!filestr)
421 {
422 errno = EMFILE;
423 return -1;
424 }
425
426 char ospath[SIM_TMPBUF_MAX_PATH];
427 int pprc = sim_get_os_path(ospath, path, sizeof (ospath));
428 if (pprc < 0)
429 return -2;
430
431 filestr->osfd = os_open(ospath, oflag | O_BINARY __OPEN_MODE_ARG);
432 if (filestr->osfd < 0)
433 return -3;
434
435#ifdef HAVE_MULTIVOLUME
436 filestr->volume = MAX(pprc - 1, 0);
437#endif
438 filestr->mounted = true;
439 return fildes;
440}
441
442int sim_creat(const char *path, mode_t mode)
443{
444 return sim_open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
445}
446
447int sim_close(int fildes)
448{
449 struct filestr_desc *filestr = &openfiles[fildes];
450 if ((unsigned int)fildes >= MAX_OPEN_FILES || filestr->osfd < 0)
451 {
452 errno = EBADF;
453 return -1;
454 }
455
456 int osfd = filestr->osfd;
457 filestr->osfd = -1;
458 return os_close(osfd);
459}
460
461int sim_ftruncate(int fildes, off_t length)
462{
463 struct filestr_desc *filestr = get_filestr(fildes);
464 if (!filestr)
465 return -1;
466
467 off_t size = os_filesize(filestr->osfd);
468 if (size < 0)
469 return -1;
470
471 if (length >= size)
472 return 0;
473
474 int rc = os_ftruncate(filestr->osfd, length);
475
476#ifdef HAVE_DIRCACHE
477 if (rc >= 0)
478 dircache_ftruncate(xxxxxx);
479#endif
480
481 return rc;
482}
483
484int sim_fsync(int fildes)
485{
486 struct filestr_desc *filestr = get_filestr(fildes);
487 if (!filestr)
488 return -1;
489
490 int rc = os_fsync(filestr->osfd);
491
492#ifdef HAVE_DIRCACHE
493 if (rc >= 0)
494 dircache_fsync(xxxxxx);
495#endif
496
497 return rc;
498}
499
500off_t sim_lseek(int fildes, off_t offset, int whence)
501{
502 struct filestr_desc *filestr = get_filestr(fildes);
503 if (!filestr)
504 return -1;
505
506 return os_lseek(filestr->osfd, offset, whence);
507}
508
509ssize_t sim_read(int fildes, void *buf, size_t nbyte)
510{
511 struct filestr_desc *filestr = get_filestr(fildes);
512 if (!filestr)
513 return -1;
514
515 return os_read(filestr->osfd, buf, nbyte);
516}
517
518ssize_t sim_write(int fildes, const void *buf, size_t nbyte)
519{
520 struct filestr_desc *filestr = get_filestr(fildes);
521 if (!filestr)
522 return -1;
523
524 return os_write(filestr->osfd, buf, nbyte);
525}
526
527int sim_remove(const char *path)
528{
529 char ospath[SIM_TMPBUF_MAX_PATH];
530 if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0)
531 return -1;
532
533 int rc = os_remove(ospath);
534
535#ifdef HAVE_DIRCACHE
536 if (rc >= 0)
537 dircache_remove(xxxxxx);
538#endif
539
540 return rc;
541}
542
543int sim_rename(const char *old, const char *new)
544{
545 char osold[SIM_TMPBUF_MAX_PATH];
546 int pprc1 = sim_get_os_path(osold, old, sizeof (osold));
547 if (pprc1 < 0)
548 return -1;
549
550 char osnew[SIM_TMPBUF_MAX_PATH];
551 int pprc2 = sim_get_os_path(osnew, new, sizeof (osnew));
552 if (pprc2 < 0)
553 return -1;
554
555 if (MAX(pprc1 - 1, 0) != MAX(pprc2 - 1, 0))
556 {
557 /* Pretend they're different devices */
558 errno = EXDEV;
559 return -1;
560 }
561
562 int rc = os_rename(osold, osnew);
563
564#ifdef HAVE_DIRCACHE
565 if (rc >= 0)
566 dircache_rename(xxxxxx);
567#endif
568
569 return rc;
570}
571
572off_t sim_filesize(int fildes)
573{
574 struct filestr_desc *filestr = get_filestr(fildes);
575 if (!filestr)
576 return -1;
577
578 return os_filesize(filestr->osfd);
579}
580
581int sim_fsamefile(int fildes1, int fildes2)
582{
583 struct filestr_desc *filestr1 = get_filestr(fildes1);
584 if (!filestr1)
585 return -1;
586
587 struct filestr_desc *filestr2 = get_filestr(fildes2);
588 if (!filestr2)
589 return -1;
590
591 if (filestr1 == filestr2)
592 return 1;
593
594 return os_fsamefile(filestr1->osfd, filestr2->osfd);
595}
596
597int sim_relate(const char *path1, const char *path2)
598{
599 char ospath1[SIM_TMPBUF_MAX_PATH];
600 if (sim_get_os_path(ospath1, path1, sizeof (ospath1)) < 0)
601 return -1;
602
603 char ospath2[SIM_TMPBUF_MAX_PATH];
604 if (sim_get_os_path(ospath2, path2, sizeof (ospath2)) < 0)
605 return -1;
606
607 return os_relate(ospath1, ospath2);
608}
609
610bool sim_file_exists(const char *path)
611{
612 char ospath[SIM_TMPBUF_MAX_PATH];
613 if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0)
614 return false;
615
616 return os_file_exists(ospath);
617}
618
619
620/** Directory functions **/
621DIR * sim_opendir(const char *dirname)
622{
623 struct dirstr_desc *dirstr = alloc_dirstr();
624 if (!dirstr)
625 {
626 errno = EMFILE;
627 return NULL;
628 }
629
630 char osdirname[SIM_TMPBUF_MAX_PATH];
631 int pprc = sim_get_os_path(osdirname, dirname, sizeof (osdirname));
632 if (pprc < 0)
633 return NULL;
634
635 int rc = os_opendir_and_fd(osdirname, &dirstr->osdirp, &dirstr->osfd);
636 if (rc < 0)
637 return NULL;
638
639 dirstr->osfd_opened = rc > 0;
640
641#ifdef USE_OSDIRNAME
642 dirstr->osdirname = strdup(osdirname);
643 if (!dirstr->osdirname)
644 {
645 close_dirstr(dirstr);
646 return NULL;
647 }
648#endif
649
650 dirstr->entry.d_name[0] = 0; /* Mark as invalid */
651#ifdef HAVE_MULTIVOLUME
652 dirstr->volume = MAX(pprc - 1, 0);
653 dirstr->volumecounter = pprc == 0 ? 0 : INT_MAX;
654#endif
655 dirstr->mounted = true;
656 return (DIR *)dirstr; /* A-Okay */
657}
658
659int sim_closedir(DIR *dirp)
660{
661 struct dirstr_desc *dirstr = (struct dirstr_desc *)dirp;
662 if (!PTR_IN_ARRAY(opendirs, dirstr, MAX_OPEN_DIRS) || !dirstr->osdirp)
663 {
664 errno = EBADF;
665 return -1;
666 }
667
668 return close_dirstr(dirstr);
669}
670
671struct sim_dirent * sim_readdir(DIR *dirp)
672{
673 struct dirstr_desc *dirstr = get_dirstr(dirp);
674 if (!dirstr)
675 return NULL;
676
677 struct sim_dirent *entry = &dirstr->entry;
678 entry->info.osdirent = NULL;
679
680 if (readdir_volume(dirstr, entry))
681 return entry;
682
683 OS_DIRENT_T *osdirent = os_readdir(dirstr->osdirp);
684 if (!osdirent)
685 return NULL;
686
687 size_t size = sizeof (entry->d_name);
688 if (strlcpy_from_os(entry->d_name, osdirent->d_name, size) >= size)
689 FILE_ERROR_RETURN(ENAMETOOLONG, NULL);
690
691 entry->info.osdirent = osdirent;
692 return entry;
693}
694
695int sim_mkdir(const char *path)
696{
697 char ospath[SIM_TMPBUF_MAX_PATH];
698 if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0)
699 return -1;
700
701 int rc = os_mkdir(ospath __MKDIR_MODE_ARG);
702
703#ifdef HAVE_DIRCACHE
704 if (rc >= 0)
705 dircache_mkdir(xxxxxx);
706#endif
707
708 return rc;
709}
710
711int sim_rmdir(const char *path)
712{
713 char ospath[SIM_TMPBUF_MAX_PATH];
714 if (sim_get_os_path(ospath, path, sizeof (ospath)) < 0)
715 return -1;
716
717 int rc = os_rmdir(ospath);
718
719#ifdef HAVE_DIRCACHE
720 if (rc >= 0)
721 dircache_rmdir(xxxxxx);
722#endif
723
724 return rc;
725}
726
727int sim_samedir(DIR *dirp1, DIR *dirp2)
728{
729 struct dirstr_desc *dirstr1 = get_dirstr(dirp1);
730 if (!dirstr1)
731 return -1;
732
733 struct dirstr_desc *dirstr2 = get_dirstr(dirp2);
734 if (!dirstr2)
735 return -1;
736
737 return os_fsamefile(dirstr1->osfd, dirstr2->osfd);
738}
739
740bool sim_dir_exists(const char *dirname)
741{
742 char osdirname[SIM_TMPBUF_MAX_PATH];
743 if (sim_get_os_path(osdirname, dirname, sizeof (osdirname)) < 0)
744 return false;
745
746 OS_DIR_T *dirp = os_opendir(osdirname);
747 if (!dirp)
748 return false;
749
750 os_closedir(dirp);
751 return true;
752}
753
754struct dirinfo dir_get_info(DIR *dirp, struct sim_dirent *entry)
755{
756 int rc;
757 struct dirstr_desc *dirstr = get_dirstr(dirp);
758 if (!dirstr)
759 FILE_ERROR(ERRNO, RC);
760
761 if (entry->d_name[0] == 0)
762 FILE_ERROR(ENOENT, RC);
763
764 OS_DIRENT_T *osdirent = entry->info.osdirent;
765 if (osdirent == NULL)
766 return (struct dirinfo){ .attribute = ATTR_MOUNT_POINT };
767
768 struct dirinfo info;
769 info.attribute = 0;
770
771 OS_STAT_T s;
772
773#ifdef os_fstatat
774 if (os_fstatat(dirstr->osfd, entry->d_name, &s, 0))
775#else /* no fstatat; build file name for stat() */
776 char statpath[SIM_TMPBUF_MAX_PATH];
777 if (path_append(statpath, dirstr->osdirname, entry->d_name,
778 sizeof (statpath)) >= sizeof (statpath))
779 {
780 FILE_ERROR(ENAMETOOLONG, RC);
781 }
782
783 if (os_stat(statpath, &s)) /* get info */
784#endif /* os_fstatat */
785 {
786 /* File size larger than 2 GB? */
787 #ifdef EOVERFLOW
788 if (errno == EOVERFLOW)
789 DEBUGF("stat overflow for \"%s\"\n", entry->d_name);
790 #endif
791 FILE_ERROR(ERRNO, RC);
792 }
793
794 if (S_ISDIR(s.st_mode))
795 info.attribute |= ATTR_DIRECTORY;
796
797 info.size = s.st_size;
798
799 struct tm tm;
800 if (localtime_r(&s.st_mtime, &tm) == NULL)
801 FILE_ERROR(ERRNO, RC);
802
803 info.mtime = mktime(&tm);
804
805 return info;
806
807file_error:
808 return (struct dirinfo){ .size = 0 };
809}
810
811int os_volume_path(IF_MV(int volume, ) char *buffer, size_t bufsize)
812{
813 if (!buffer || !bufsize IF_MV( || !volume_present(volume) ))
814 return -1;
815
816 char tmpbuf[SIM_TMPBUF_MAX_PATH];
817 tmpbuf[0] = '\0';
818
819#ifdef HAVE_MULTIVOLUME
820 char volname[VOL_MAX_LEN + 1];
821 get_volume_name(volume, volname);
822
823 if (path_append(tmpbuf, PA_SEP_HARD, volname, sizeof (volname))
824 >= sizeof (volname))
825 return -1;
826#endif /* HAVE_MULTIVOLUME */
827
828 if (path_append(tmpbuf, PA_SEP_HARD, ".", sizeof (tmpbuf))
829 >= sizeof (tmpbuf))
830 return -1;
831
832 return sim_get_os_path(buffer, tmpbuf, bufsize);
833}
diff --git a/uisimulator/common/filesystem-sim.h b/uisimulator/common/filesystem-sim.h
new file mode 100644
index 0000000000..7c46c449d8
--- /dev/null
+++ b/uisimulator/common/filesystem-sim.h
@@ -0,0 +1,108 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#ifndef _FILESYSTEM_SIM_H_
22#define _FILESYSTEM_SIM_H_
23
24#if defined(PLUGIN) || defined(CODEC)
25/* Prevent often-problematic plugin namespace pollution */
26#define FILEFUNCTIONS_DECLARED
27#define FILEFUNCTIONS_DEFINED
28#define DIRFUNCTIONS_DECLARED
29#define DIRFUNCTIONS_DEFINED
30#define OSFUNCTIONS_DECLARED
31#endif /* PLUGIN || CODEC */
32
33#ifndef OSFUNCTIONS_DECLARED
34#define FS_PREFIX(_x_) sim_ ## _x_
35
36/* path sandboxing and volume redirector */
37int sim_get_os_path(char *buffer, const char *path, size_t bufsize);
38#define SIM_TMPBUF_MAX_PATH (MAX_PATH+1)
39
40#endif /* !OSFUNCTIONS_DECLARED */
41
42#endif /* _FILESYSTEM_SIM_H_ */
43
44#include "filesystem-sdl.h"
45#ifdef WIN32
46#include "filesystem-win32.h"
47#else /* !WIN32 */
48#include "filesystem-unix.h"
49#endif /* WIN32 */
50#include "filesystem-hosted.h"
51
52#ifdef _FILE_H_
53#ifndef _FILESYSTEM_SIM_H__FILE_H_
54#define _FILESYSTEM_SIM_H__FILE_H_
55
56#ifdef RB_FILESYSTEM_OS
57#define FILEFUNCTIONS_DEFINED
58#endif
59
60#ifndef FILEFUNCTIONS_DECLARED
61int sim_open(const char *name, int oflag, ...);
62int sim_creat(const char *name, mode_t mode);
63int sim_close(int fildes);
64int sim_ftruncate(int fildes, off_t length);
65int sim_fsync(int fildes);
66off_t sim_lseek(int fildes, off_t offset, int whence);
67ssize_t sim_read(int fildes, void *buf, size_t nbyte);
68ssize_t sim_write(int fildes, const void *buf, size_t nbyte);
69int sim_remove(const char *path);
70int sim_rename(const char *old, const char *new);
71off_t sim_filesize(int fildes);
72int sim_fsamefile(int fildes1, int fildes2);
73int sim_relate(const char *path1, const char *path2);
74bool sim_file_exists(const char *path);
75#endif /* !FILEFUNCTIONS_DECLARED */
76
77#endif /* _FILESYSTEM_SIM_H__FILE_H_ */
78#endif /* _FILE_H_ */
79
80#ifdef _DIR_H_
81#ifndef _FILESYSTEM_SIM_H__DIR_H_
82#define _FILESYSTEM_SIM_H__DIR_H_
83
84#ifdef RB_FILESYSTEM_OS
85#define DIRFUNCTIONS_DEFINED
86#else
87#define dirent DIRENT
88#endif
89
90#define DIRENT sim_dirent
91
92struct dirinfo_native
93{
94 void *osdirent;
95};
96
97#ifndef DIRFUNCTIONS_DECLARED
98DIR * sim_opendir(const char *dirname);
99struct sim_dirent * sim_readdir(DIR *dirp);
100int sim_closedir(DIR *dirp);
101int sim_mkdir(const char *path);
102int sim_rmdir(const char *path);
103int sim_samedir(DIR *dirp1, DIR *dirp2);
104bool sim_dir_exists(const char *dirname);
105#endif /* !DIRFUNCTIONS_DECLARED */
106
107#endif /* _FILESYSTEM_SIM_H__DIR_H_ */
108#endif /* _DIR_H_ */
diff --git a/uisimulator/common/io.c b/uisimulator/common/io.c
deleted file mode 100644
index 6662e9ffda..0000000000
--- a/uisimulator/common/io.c
+++ /dev/null
@@ -1,729 +0,0 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Daniel Stenberg
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <stdarg.h>
26#include <sys/stat.h>
27#include <time.h>
28#include <errno.h>
29#include "config.h"
30#include "system.h"
31#include "ata_idle_notify.h"
32#include "mv.h"
33
34#define HAVE_STATVFS (!defined(WIN32))
35#define HAVE_LSTAT (!defined(WIN32))
36
37#if HAVE_STATVFS
38#include <sys/statvfs.h>
39#endif
40
41#ifdef WIN32
42#include <windows.h>
43#endif
44
45#ifndef _MSC_VER
46#include <dirent.h>
47#include <unistd.h>
48#else
49#include "dir-win32.h"
50#endif
51
52#include <fcntl.h>
53#ifdef HAVE_SDL_THREADS
54#include "thread-sdl.h"
55#else
56#define sim_thread_unlock() NULL
57#define sim_thread_lock(a)
58#endif
59#include "thread.h"
60#include "kernel.h"
61#include "debug.h"
62#include "ata.h" /* for IF_MV et al. */
63#include "rbpaths.h"
64#include "load_code.h"
65
66/* keep this in sync with file.h! */
67#undef MAX_PATH /* this avoids problems when building simulator */
68#define MAX_PATH 260
69#define MAX_OPEN_FILES 11
70
71/* Windows (and potentially other OSes) distinguish binary and text files.
72 * Define a dummy for the others. */
73#ifndef O_BINARY
74#define O_BINARY 0
75#endif
76
77/* Unicode compatibility for win32 */
78#if defined __MINGW32__
79/* Rockbox unicode functions */
80extern const unsigned char* utf8decode(const unsigned char *utf8,
81 unsigned short *ucs);
82extern unsigned char* utf8encode(unsigned long ucs, unsigned char *utf8);
83
84/* Static buffers for the conversion results. This isn't thread safe,
85 * but it's sufficient for rockbox. */
86static unsigned char convbuf1[3*MAX_PATH];
87static unsigned char convbuf2[3*MAX_PATH];
88
89static wchar_t* utf8_to_ucs2(const unsigned char *utf8, void *buffer)
90{
91 wchar_t *ucs = buffer;
92
93 while (*utf8)
94 utf8 = utf8decode(utf8, ucs++);
95
96 *ucs = 0;
97 return buffer;
98}
99static unsigned char *ucs2_to_utf8(const wchar_t *ucs, unsigned char *buffer)
100{
101 unsigned char *utf8 = buffer;
102
103 while (*ucs)
104 utf8 = utf8encode(*ucs++, utf8);
105
106 *utf8 = 0;
107 return buffer;
108}
109
110#define UTF8_TO_OS(a) utf8_to_ucs2(a,convbuf1)
111#define OS_TO_UTF8(a) ucs2_to_utf8(a,convbuf1)
112#define DIR_T _WDIR
113#define DIRENT_T struct _wdirent
114#define STAT_T struct _stat
115extern int _wmkdir(const wchar_t*);
116extern int _wrmdir(const wchar_t*);
117#define MKDIR(a,b) (_wmkdir)(UTF8_TO_OS(a))
118#define RMDIR(a) (_wrmdir)(UTF8_TO_OS(a))
119#define OPENDIR(a) (_wopendir)(UTF8_TO_OS(a))
120#define READDIR(a) (_wreaddir)(a)
121#define CLOSEDIR(a) (_wclosedir)(a)
122#define STAT(a,b) (_wstat)(UTF8_TO_OS(a),b)
123/* empty variable parameter list doesn't work for variadic macros,
124 * so pretend the second parameter is variable too */
125#define OPEN(a,...) (_wopen)(UTF8_TO_OS(a), __VA_ARGS__)
126#define CLOSE(a) (close)(a)
127#define REMOVE(a) (_wremove)(UTF8_TO_OS(a))
128#define RENAME(a,b) (_wrename)(UTF8_TO_OS(a),utf8_to_ucs2(b,convbuf2))
129/* readlink isn't used in the sim yet (FIXME) */
130#define READLINK(a,b,c) ({ fprintf(stderr, "no readlink on windows yet"); abort(); })
131#else /* !__MINGW32__ */
132
133#define UTF8_TO_OS(a) (a)
134#define OS_TO_UTF8(a) (a)
135#define DIR_T DIR
136#define DIRENT_T struct dirent
137#define STAT_T struct stat
138#define MKDIR(a,b) (mkdir)(a,b)
139#define RMDIR(a) (rmdir)(a)
140#define OPENDIR(a) (opendir)(a)
141#define READDIR(a) (readdir)(a)
142#define CLOSEDIR(a) (closedir)(a)
143#define STAT(a,b) (stat)(a,b)
144/* empty variable parameter list doesn't work for variadic macros,
145 * so pretend the second parameter is variable too */
146#define OPEN(a, ...) (open)(a, __VA_ARGS__)
147#define CLOSE(x) (close)(x)
148#define REMOVE(a) (remove)(a)
149#define RENAME(a,b) (rename)(a,b)
150#define READLINK(a,b,c) (readlink)(a,b,c)
151
152#endif /* !__MINGW32__ */
153
154
155#ifdef HAVE_DIRCACHE
156int dircache_get_entry_id(const char *filename);
157void dircache_add_file(const char *name, long startcluster);
158void dircache_remove(const char *name);
159void dircache_rename(const char *oldname, const char *newname);
160#endif
161
162#ifndef APPLICATION
163
164#define SIMULATOR_DEFAULT_ROOT "simdisk"
165extern const char *sim_root_dir;
166
167static int num_openfiles = 0;
168
169/* from dir.h */
170struct dirinfo {
171 int attribute;
172 long size;
173 unsigned short wrtdate;
174 unsigned short wrttime;
175};
176
177struct sim_dirent {
178 unsigned char d_name[MAX_PATH];
179 struct dirinfo info;
180 long startcluster;
181};
182
183struct dirstruct {
184 void *dir; /* actually a DIR* dir */
185 char *name;
186} SIM_DIR;
187
188struct mydir {
189 DIR_T *dir;
190 IF_MV(int volumes_returned);
191 char *name;
192};
193
194typedef struct mydir MYDIR;
195
196static unsigned int rockbox2sim(int opt)
197{
198#if 0
199/* this shouldn't be needed since we use the host's versions */
200 int newopt = O_BINARY;
201
202 if(opt & 1)
203 newopt |= O_WRONLY;
204 if(opt & 2)
205 newopt |= O_RDWR;
206 if(opt & 4)
207 newopt |= O_CREAT;
208 if(opt & 8)
209 newopt |= O_APPEND;
210 if(opt & 0x10)
211 newopt |= O_TRUNC;
212
213 return newopt;
214#else
215 return opt|O_BINARY;
216#endif
217}
218
219#endif /* APPLICATION */
220
221/** Simulator I/O engine routines **/
222#define IO_YIELD_THRESHOLD 512
223
224enum io_dir
225{
226 IO_READ,
227 IO_WRITE,
228};
229
230struct sim_io
231{
232 struct mutex sim_mutex; /* Rockbox mutex */
233 int cmd; /* The command to perform */
234 int ready; /* I/O ready flag - 1= ready */
235 int fd; /* The file to read/write */
236 void *buf; /* The buffer to read/write */
237 size_t count; /* Number of bytes to read/write */
238 size_t accum; /* Acculated bytes transferred */
239};
240
241static struct sim_io io;
242
243int ata_init(void)
244{
245 /* Initialize the rockbox kernel objects on a rockbox thread */
246 mutex_init(&io.sim_mutex);
247 io.accum = 0;
248 return 1;
249}
250
251int ata_spinup_time(void)
252{
253 return HZ;
254}
255
256static ssize_t io_trigger_and_wait(enum io_dir cmd)
257{
258 void *mythread = NULL;
259 ssize_t result;
260
261 if (io.count > IO_YIELD_THRESHOLD ||
262 (io.accum += io.count) >= IO_YIELD_THRESHOLD)
263 {
264 /* Allow other rockbox threads to run */
265 io.accum = 0;
266 mythread = sim_thread_unlock();
267 }
268
269 switch (cmd)
270 {
271 case IO_READ:
272 result = read(io.fd, io.buf, io.count);
273 break;
274 case IO_WRITE:
275 result = write(io.fd, io.buf, io.count);
276 break;
277 /* shut up gcc */
278 default:
279 result = -1;
280 }
281
282 call_storage_idle_notifys(false);
283
284 /* Regain our status as current */
285 if (mythread != NULL)
286 {
287 sim_thread_lock(mythread);
288 }
289
290 return result;
291}
292
293
294ssize_t sim_read(int fd, void *buf, size_t count)
295{
296 ssize_t result;
297
298 mutex_lock(&io.sim_mutex);
299
300 /* Setup parameters */
301 io.fd = fd;
302 io.buf = buf;
303 io.count = count;
304
305 result = io_trigger_and_wait(IO_READ);
306
307 mutex_unlock(&io.sim_mutex);
308
309 return result;
310}
311
312
313ssize_t sim_write(int fd, const void *buf, size_t count)
314{
315 ssize_t result;
316
317 mutex_lock(&io.sim_mutex);
318
319 io.fd = fd;
320 io.buf = (void*)buf;
321 io.count = count;
322
323 result = io_trigger_and_wait(IO_WRITE);
324
325 mutex_unlock(&io.sim_mutex);
326
327 return result;
328}
329
330#if !defined(APPLICATION)
331
332static const char *handle_special_links(const char* link)
333{
334#ifdef HAVE_MULTIDRIVE
335 static char buffer[MAX_PATH]; /* sufficiently big */
336 char vol_string[VOL_ENUM_POS + 8];
337 int len = sprintf(vol_string, VOL_NAMES, 1);
338
339 /* link might be passed with or without HOME_DIR expanded. To handle
340 * both perform substring matching (VOL_NAMES is unique enough) */
341 const char *begin = strstr(link, vol_string);
342 if (begin)
343 {
344 /* begin now points to the start of vol_string within link,
345 * we want to copy the remainder of the paths, prefixed by
346 * the actual mount point (the remainder might be "") */
347 snprintf(buffer, sizeof(buffer), "%s/../simext/%s",
348 sim_root_dir ?: SIMULATOR_DEFAULT_ROOT, begin + len);
349 return buffer;
350 }
351 else
352#endif
353 return link;
354}
355
356
357static const char *get_sim_pathname(const char *name)
358{
359 static char buffer[MAX_PATH]; /* sufficiently big */
360
361 if(name[0] == '/')
362 {
363 snprintf(buffer, sizeof(buffer), "%s%s",
364 sim_root_dir ?: SIMULATOR_DEFAULT_ROOT, name);
365 return handle_special_links(buffer);
366 }
367 fprintf(stderr, "WARNING, bad file name lacks slash: %s\n", name);
368 return name;
369}
370
371
372MYDIR *sim_opendir(const char *name)
373{
374 DIR_T *dir;
375 dir = (DIR_T *) OPENDIR(get_sim_pathname(name));
376
377 if (dir)
378 {
379 MYDIR *my = (MYDIR *)malloc(sizeof(MYDIR));
380 my->dir = dir;
381 my->name = (char *)malloc(strlen(name)+1);
382 strcpy(my->name, name);
383 IF_MV(my->volumes_returned = 0);
384
385 return my;
386 }
387 /* failed open, return NULL */
388 return (MYDIR *)0;
389}
390
391#if defined(WIN32)
392static inline struct tm* localtime_r (const time_t *clock, struct tm *result) {
393 if (!clock || !result) return NULL;
394 memcpy(result,localtime(clock),sizeof(*result));
395 return result;
396}
397#endif
398
399struct sim_dirent *sim_readdir(MYDIR *dir)
400{
401 char buffer[MAX_PATH]; /* sufficiently big */
402 static struct sim_dirent secret;
403 STAT_T s;
404 struct tm tm;
405 DIRENT_T *x11;
406
407#ifdef EOVERFLOW
408read_next:
409#endif
410
411#define ATTR_LINK 0x80 /* see dir.h */
412
413 secret.info.attribute = 0;
414#ifdef HAVE_MULTIVOLUME
415 if (dir->name[0] == '/' && dir->name[1] == '\0'
416 && dir->volumes_returned++ < (NUM_VOLUMES-1)
417 && volume_present(dir->volumes_returned))
418 {
419 sprintf((char *)secret.d_name, VOL_NAMES, dir->volumes_returned);
420 secret.info.attribute = ATTR_LINK;
421 /* build file name for stat() which is the actual mount point */
422 snprintf(buffer, sizeof(buffer), "%s/../simext",
423 sim_root_dir ?: SIMULATOR_DEFAULT_ROOT);
424 }
425 else
426#endif
427 {
428 x11 = READDIR(dir->dir);
429
430 if(!x11)
431 return (struct sim_dirent *)0;
432
433 strcpy((char *)secret.d_name, OS_TO_UTF8(x11->d_name));
434 /* build file name for stat() */
435 snprintf(buffer, sizeof(buffer), "%s/%s",
436 get_sim_pathname(dir->name), secret.d_name);
437 }
438
439 if (STAT(buffer, &s)) /* get info */
440 {
441#ifdef EOVERFLOW
442 /* File size larger than 2 GB? */
443 if (errno == EOVERFLOW)
444 {
445 DEBUGF("stat() overflow for %s. Skipping\n", buffer);
446 goto read_next;
447 }
448#endif
449
450 return NULL;
451 }
452
453#define ATTR_DIRECTORY 0x10
454
455 if (S_ISDIR(s.st_mode))
456 secret.info.attribute = ATTR_DIRECTORY;
457
458 secret.info.size = s.st_size;
459
460 if (localtime_r(&(s.st_mtime), &tm) == NULL)
461 return NULL;
462 secret.info.wrtdate = ((tm.tm_year - 80) << 9) |
463 ((tm.tm_mon + 1) << 5) |
464 tm.tm_mday;
465 secret.info.wrttime = (tm.tm_hour << 11) |
466 (tm.tm_min << 5) |
467 (tm.tm_sec >> 1);
468
469#if HAVE_LSTAT
470 if (!lstat(buffer, &s) && S_ISLNK(s.st_mode))
471 {
472 secret.info.attribute |= ATTR_LINK;
473 }
474#endif
475
476 return &secret;
477}
478
479void sim_closedir(MYDIR *dir)
480{
481 free(dir->name);
482 CLOSEDIR(dir->dir);
483
484 free(dir);
485}
486
487int sim_open(const char *name, int o, ...)
488{
489 int opts = rockbox2sim(o);
490 int ret;
491 if (num_openfiles >= MAX_OPEN_FILES)
492 return -2;
493
494 if (opts & O_CREAT)
495 {
496 va_list ap;
497 va_start(ap, o);
498 mode_t mode = va_arg(ap, unsigned int);
499 ret = OPEN(get_sim_pathname(name), opts, mode);
500#ifdef HAVE_DIRCACHE
501 if (ret >= 0 && (dircache_get_entry_id(name) < 0))
502 dircache_add_file(name, 0);
503#endif
504 va_end(ap);
505 }
506 else
507 ret = OPEN(get_sim_pathname(name), opts);
508
509 if (ret >= 0)
510 num_openfiles++;
511 return ret;
512}
513
514int sim_close(int fd)
515{
516 int ret;
517 ret = CLOSE(fd);
518 if (ret == 0)
519 num_openfiles--;
520 return ret;
521}
522
523int sim_creat(const char *name, mode_t mode)
524{
525 int ret = OPEN(get_sim_pathname(name),
526 O_BINARY | O_WRONLY | O_CREAT | O_TRUNC, mode);
527#ifdef HAVE_DIRCACHE
528 if (ret >= 0 && (dircache_get_entry_id(name) < 0))
529 dircache_add_file(name, 0);
530#endif
531 return ret;
532}
533
534int sim_mkdir(const char *name)
535{
536 return MKDIR(get_sim_pathname(name), 0777);
537}
538
539int sim_rmdir(const char *name)
540{
541 return RMDIR(get_sim_pathname(name));
542}
543
544int sim_remove(const char *name)
545{
546 int ret = REMOVE(get_sim_pathname(name));
547#ifdef HAVE_DIRCACHE
548 if (ret >= 0)
549 dircache_remove(name);
550#endif
551 return ret;
552}
553
554int sim_rename(const char *oldname, const char *newname)
555{
556 char sim_old[MAX_PATH];
557 char sim_new[MAX_PATH];
558#ifdef HAVE_DIRCACHE
559 dircache_rename(oldname, newname);
560#endif
561 // This is needed as get_sim_pathname() has a static buffer
562 strncpy(sim_old, get_sim_pathname(oldname), MAX_PATH);
563 strncpy(sim_new, get_sim_pathname(newname), MAX_PATH);
564 return RENAME(sim_old, sim_new);
565}
566
567/* rockbox off_t may be different from system off_t */
568long sim_lseek(int fildes, long offset, int whence)
569{
570 return lseek(fildes, offset, whence);
571}
572
573#else
574#define get_sim_pathname(x) x
575#endif
576
577long filesize(int fd)
578{
579#ifdef WIN32
580 return _filelength(fd);
581#else
582 struct stat buf;
583
584 if (!fstat(fd, &buf))
585 return buf.st_size;
586 else
587 return -1;
588#endif
589}
590
591void fat_size(IF_MV(int volume,) unsigned long* size, unsigned long* free)
592{
593#ifdef HAVE_MULTIVOLUME
594 if (volume != 0) {
595 /* debugf("io.c: fat_size(volume=%d); simulator only supports volume 0\n",volume); */
596
597 if (size) *size = 0;
598 if (free) *free = 0;
599 return;
600 }
601#endif
602
603#ifdef WIN32
604 long secperclus, bytespersec, free_clusters, num_clusters;
605
606 if (GetDiskFreeSpace(NULL, &secperclus, &bytespersec, &free_clusters,
607 &num_clusters)) {
608 if (size)
609 *size = num_clusters * secperclus / 2 * (bytespersec / 512);
610 if (free)
611 *free = free_clusters * secperclus / 2 * (bytespersec / 512);
612 } else
613#elif HAVE_STATVFS
614 struct statvfs vfs;
615
616 if (!statvfs(".", &vfs)) {
617 DEBUGF("statvfs: frsize=%d blocks=%ld free=%ld\n",
618 (int)vfs.f_frsize, (long)vfs.f_blocks, (long)vfs.f_bfree);
619 if (size)
620 *size = vfs.f_blocks / 2 * (vfs.f_frsize / 512);
621 if (free)
622 *free = vfs.f_bfree / 2 * (vfs.f_frsize / 512);
623 } else
624#endif
625 {
626 if (size)
627 *size = 0;
628 if (free)
629 *free = 0;
630 }
631}
632
633int sim_fsync(int fd)
634{
635#ifdef WIN32
636 return _commit(fd);
637#else
638 return fsync(fd);
639#endif
640}
641
642#ifndef __PCTOOL__
643
644#include <SDL_loadso.h>
645void *lc_open(const char *filename, unsigned char *buf, size_t buf_size)
646{
647 (void)buf;
648 (void)buf_size;
649 void *handle = SDL_LoadObject(get_sim_pathname(filename));
650 if (handle == NULL)
651 {
652 DEBUGF("failed to load %s\n", filename);
653 DEBUGF("lc_open(%s): %s\n", filename, SDL_GetError());
654 }
655 return handle;
656}
657
658void *lc_get_header(void *handle)
659{
660 char *ret = SDL_LoadFunction(handle, "__header");
661 if (ret == NULL)
662 ret = SDL_LoadFunction(handle, "___header");
663
664 return ret;
665}
666
667void lc_close(void *handle)
668{
669 SDL_UnloadObject(handle);
670}
671
672void *lc_open_from_mem(void *addr, size_t blob_size)
673{
674#ifndef SIMULATOR
675 (void)addr;
676 (void)blob_size;
677 /* we don't support loading code from memory on application builds,
678 * it doesn't make sense (since it means writing the blob to disk again and
679 * then falling back to load from disk) and requires the ability to write
680 * to an executable directory */
681 return NULL;
682#else
683 /* support it in the sim for the sake of simulating */
684 int fd, i;
685 char temp_filename[MAX_PATH];
686
687 /* We have to create the dynamic link library file from ram so we
688 can simulate the codec loading. With voice and crossfade,
689 multiple codecs may be loaded at the same time, so we need
690 to find an unused filename */
691 for (i = 0; i < 10; i++)
692 {
693 snprintf(temp_filename, sizeof(temp_filename),
694 ROCKBOX_DIR "/libtemp_binary_%d.dll", i);
695 fd = open(temp_filename, O_WRONLY|O_CREAT|O_TRUNC, 0700);
696 if (fd >= 0)
697 break; /* Created a file ok */
698 }
699
700 if (fd < 0)
701 {
702 DEBUGF("open failed\n");
703 return NULL;
704 }
705
706 if (write(fd, addr, blob_size) < (ssize_t)blob_size)
707 {
708 DEBUGF("Write failed\n");
709 close(fd);
710 remove(temp_filename);
711 return NULL;
712 }
713
714 close(fd);
715 return lc_open(temp_filename, NULL, 0);
716#endif
717}
718
719#endif /* __PCTOOL__ */
720
721/* rockbox off_t may be different from system off_t */
722int sim_ftruncate(int fd, long length)
723{
724#ifdef WIN32
725 return _chsize(fd, length);
726#else
727 return ftruncate(fd, length);
728#endif
729}
diff --git a/uisimulator/common/load_code-sim.c b/uisimulator/common/load_code-sim.c
new file mode 100644
index 0000000000..59ca97a259
--- /dev/null
+++ b/uisimulator/common/load_code-sim.c
@@ -0,0 +1,72 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2002 Daniel Stenberg
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#define RB_FILESYSTEM_OS
22#include <stdio.h>
23#include "config.h"
24#include "system.h"
25#include "file.h"
26#include "load_code.h"
27#include "rbpaths.h"
28#include "debug.h"
29
30void * lc_open(const char *filename, unsigned char *buf, size_t buf_size)
31{
32 char osfilename[SIM_TMPBUF_MAX_PATH];
33 if (sim_get_os_path(osfilename, filename, sizeof (osfilename)) < 0)
34 return NULL;
35
36 return os_lc_open(osfilename);
37 (void)buf; (void)buf_size;
38}
39
40void * lc_open_from_mem(void *addr, size_t blob_size)
41{
42 /* We have to create the dynamic link library file from ram so we can
43 simulate code loading. Multiple binaries may be loaded at the same
44 time, so we need to find an unused filename. */
45 int fd;
46 char tempfile[SIM_TMPBUF_MAX_PATH];
47 for (unsigned int i = 0; i < 10; i++)
48 {
49 snprintf(tempfile, sizeof(tempfile),
50 ROCKBOX_DIR "/libtemp_binary_%d.dll", i);
51 fd = sim_open(tempfile, O_WRONLY|O_CREAT|O_TRUNC, 0700);
52 if (fd >= 0)
53 break; /* Created a file ok */
54 }
55
56 if (fd < 0)
57 {
58 DEBUGF("open failed\n");
59 return NULL;
60 }
61
62 if (sim_write(fd, addr, blob_size) != (ssize_t)blob_size)
63 {
64 DEBUGF("Write failed\n");
65 sim_close(fd);
66 sim_remove(tempfile);
67 return NULL;
68 }
69
70 sim_close(fd);
71 return lc_open(tempfile, NULL, 0);
72}
diff --git a/uisimulator/common/sim_tasks.c b/uisimulator/common/sim_tasks.c
index 1299a69302..003b993740 100644
--- a/uisimulator/common/sim_tasks.c
+++ b/uisimulator/common/sim_tasks.c
@@ -28,6 +28,11 @@
28#include "thread.h" 28#include "thread.h"
29#include "debug.h" 29#include "debug.h"
30#include "usb.h" 30#include "usb.h"
31#include "mv.h"
32#include "ata_idle_notify.h"
33#ifdef WIN32
34#include <windows.h>
35#endif
31 36
32static void sim_thread(void); 37static void sim_thread(void);
33static long sim_thread_stack[DEFAULT_STACK_SIZE/sizeof(long)]; 38static long sim_thread_stack[DEFAULT_STACK_SIZE/sizeof(long)];
@@ -46,6 +51,10 @@ enum {
46#endif 51#endif
47}; 52};
48 53
54#ifdef HAVE_MULTIDRIVE
55extern void sim_ext_extracted(int drive);
56#endif
57
49void sim_thread(void) 58void sim_thread(void)
50{ 59{
51 struct queue_event ev; 60 struct queue_event ev;
@@ -54,9 +63,13 @@ void sim_thread(void)
54 63
55 while (1) 64 while (1)
56 { 65 {
57 queue_wait(&sim_queue, &ev); 66 queue_wait_w_tmo(&sim_queue, &ev, 5*HZ);
58 switch(ev.id) 67 switch(ev.id)
59 { 68 {
69 case SYS_TIMEOUT:
70 call_storage_idle_notifys(false);
71 break;
72
60 case SIM_SCREENDUMP: 73 case SIM_SCREENDUMP:
61 screen_dump(); 74 screen_dump();
62#ifdef HAVE_REMOTE_LCD 75#ifdef HAVE_REMOTE_LCD
@@ -102,6 +115,7 @@ void sim_thread(void)
102#ifdef HAVE_MULTIDRIVE 115#ifdef HAVE_MULTIDRIVE
103 case SIM_EXT_INSERTED: 116 case SIM_EXT_INSERTED:
104 case SIM_EXT_EXTRACTED: 117 case SIM_EXT_EXTRACTED:
118 sim_ext_extracted(ev.data);
105 queue_broadcast(ev.id == SIM_EXT_INSERTED ? 119 queue_broadcast(ev.id == SIM_EXT_INSERTED ?
106 SYS_HOTSWAP_INSERTED : SYS_HOTSWAP_EXTRACTED, 0); 120 SYS_HOTSWAP_INSERTED : SYS_HOTSWAP_EXTRACTED, 0);
107 sleep(HZ/20); 121 sleep(HZ/20);
@@ -174,11 +188,13 @@ static bool is_ext_inserted;
174 188
175void sim_trigger_external(bool inserted) 189void sim_trigger_external(bool inserted)
176{ 190{
191 is_ext_inserted = inserted;
192
193 int drive = 1; /* Can do others! */
177 if (inserted) 194 if (inserted)
178 queue_post(&sim_queue, SIM_EXT_INSERTED, 0); 195 queue_post(&sim_queue, SIM_EXT_INSERTED, drive);
179 else 196 else
180 queue_post(&sim_queue, SIM_EXT_EXTRACTED, 0); 197 queue_post(&sim_queue, SIM_EXT_EXTRACTED, drive);
181 is_ext_inserted = inserted;
182} 198}
183 199
184bool hostfs_present(int drive) 200bool hostfs_present(int drive)
@@ -203,7 +219,13 @@ bool volume_present(int volume)
203 /* volume == drive for now */ 219 /* volume == drive for now */
204 return hostfs_present(volume); 220 return hostfs_present(volume);
205} 221}
206#endif 222
223int volume_drive(int volume)
224{
225 /* volume == drive for now */
226 return volume;
227}
228#endif /* HAVE_MULTIVOLUME */
207 229
208#if (CONFIG_STORAGE & STORAGE_MMC) 230#if (CONFIG_STORAGE & STORAGE_MMC)
209bool mmc_touched(void) 231bool mmc_touched(void)
@@ -212,4 +234,4 @@ bool mmc_touched(void)
212} 234}
213#endif 235#endif
214 236
215#endif 237#endif /* CONFIG_STORAGE & STORAGE_MMC */
diff --git a/uisimulator/common/stubs.c b/uisimulator/common/stubs.c
index 18f60ce6b3..922b9e8da0 100644
--- a/uisimulator/common/stubs.c
+++ b/uisimulator/common/stubs.c
@@ -153,10 +153,9 @@ int fat_startsector(void)
153 return 63; 153 return 63;
154} 154}
155 155
156bool fat_ismounted(int volume) 156int ata_spinup_time(void)
157{ 157{
158 (void)volume; 158 return 100;
159 return true;
160} 159}
161 160
162int storage_spinup_time(void) 161int storage_spinup_time(void)
diff --git a/uisimulator/common/time-win32.c b/uisimulator/common/time-win32.c
new file mode 100644
index 0000000000..1de69c35e6
--- /dev/null
+++ b/uisimulator/common/time-win32.c
@@ -0,0 +1,61 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 by Michael Sevakis
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include <time.h>
22#include <errno.h>
23#include "system.h"
24#include "debug.h"
25
26/* These functions we like but Windows doesn't because it implements the
27 non-"_r" versions with thread-local storage in its multithreaded libs */
28
29struct tm * localtime_r(const time_t *restrict timer,
30 struct tm *restrict result)
31{
32 if (!timer || !result)
33 {
34 errno = EINVAL;
35 return NULL;
36 }
37
38 struct tm *tm = localtime(timer);
39 if (!tm)
40 return NULL;
41
42 *result = *tm;
43 return result;
44}
45
46struct tm * gmtime_r(const time_t *restrict timer,
47 struct tm *restrict result)
48{
49 if (!timer || !result)
50 {
51 errno = EINVAL;
52 return NULL;
53 }
54
55 struct tm *tm = gmtime(timer);
56 if (!tm)
57 return NULL;
58
59 *result = *tm;
60 return result;
61}