diff --git a/repos/libports/lib/mk/vfs_fatfs.mk b/repos/libports/lib/mk/vfs_fatfs.mk new file mode 100644 index 000000000..79bbbf074 --- /dev/null +++ b/repos/libports/lib/mk/vfs_fatfs.mk @@ -0,0 +1,7 @@ +SRC_CC = vfs_fatfs.cc + +LIBS += fatfs_block + +vpath %.cc $(REP_DIR)/src/lib/vfs/fatfs + +SHARED_LIB = yes diff --git a/repos/libports/run/libc_vfs_fat.run b/repos/libports/run/libc_vfs_fat.run new file mode 100644 index 000000000..c89c50324 --- /dev/null +++ b/repos/libports/run/libc_vfs_fat.run @@ -0,0 +1,10 @@ +set mkfs_cmd [check_installed mkfs.vfat] +set mkfs_opts "-F32 -nlibc_vfs" + +set test_build_components lib/vfs/fatfs +set test_vfs_config "" +set test_boot_modules vfs_fatfs.lib.so + +set use_vfs_server 0 + +source ${genode_dir}/repos/libports/run/libc_vfs_filesystem_test.inc diff --git a/repos/libports/run/libc_vfs_fs_fat.run b/repos/libports/run/libc_vfs_fs_fat.run new file mode 100644 index 000000000..bc6daa71f --- /dev/null +++ b/repos/libports/run/libc_vfs_fs_fat.run @@ -0,0 +1,10 @@ +set mkfs_cmd [check_installed mkfs.vfat] +set mkfs_opts "-F32 -nlibc_vfs" + +set test_build_components lib/vfs/fatfs +set test_vfs_config "" +set test_boot_modules vfs_fatfs.lib.so + +set use_vfs_server 1 + +source ${genode_dir}/repos/libports/run/libc_vfs_filesystem_test.inc diff --git a/repos/libports/src/lib/vfs/fatfs/README b/repos/libports/src/lib/vfs/fatfs/README new file mode 100644 index 000000000..cdf7b9658 --- /dev/null +++ b/repos/libports/src/lib/vfs/fatfs/README @@ -0,0 +1,46 @@ +This plugin provides resource-optimized FAT and exFAT support to the VFS library. + +Usage +~~~~~ + +The plugin takes two configuration options as XML attributes, 'codepage' and 'drive'. +A codepage number is required only for non-ASCII filename support. The 'drive' option +takes an integer value between 1 and 10 and is simply a symbolic identifier passed +thru the Block session request. In this manner multiple drives are supported. + +Codepages +~~~~~~~~~ + +Support for non-ACII filenames is experimental and only one codepage +may be in use for any number of drives. + +Supported codepages +-------------------------------- +437 | U.S. +720 | Arabic +737 | Greek +771 | KBL +775 | Baltic +850 | Latin 1 +852 | Latin 2 +855 | Cyrillic +857 | Turkish +860 | Portuguese +861 | Icelandic +862 | Hebrew +863 | Canadian French +864 | Arabic +865 | Nordic +866 | Russian +869 | Greek 2 +932 | Japanese (DBCS) +936 | Simplified Chinese (DBCS) +949 | Korean (DBCS) +950 | Traditional Chinese (DBCS) + +Caching +~~~~~~~~ + +This plugin may cache some file data but schedules a full write cache flush a few +seconds after any write operation. If a read caching is desired, please use the +'blk_cache' component to cache at the block device. diff --git a/repos/libports/src/lib/vfs/fatfs/target.mk b/repos/libports/src/lib/vfs/fatfs/target.mk new file mode 100644 index 000000000..cd9fe4e6f --- /dev/null +++ b/repos/libports/src/lib/vfs/fatfs/target.mk @@ -0,0 +1,2 @@ +TARGET = dummy-vfs_fatfs +LIBS = vfs_fatfs diff --git a/repos/libports/src/lib/vfs/fatfs/vfs_fatfs.cc b/repos/libports/src/lib/vfs/fatfs/vfs_fatfs.cc new file mode 100644 index 000000000..9ecb454c7 --- /dev/null +++ b/repos/libports/src/lib/vfs/fatfs/vfs_fatfs.cc @@ -0,0 +1,621 @@ +/* + * \brief FatFS VFS plugin + * \author Christian Prochaska + * \author Emery Hemingway + * \date 2016-05-22 + * + * See http://www.elm-chan.org/fsw/ff/00index_e.html + * or documents/00index_e.html in the FatFS source. + */ + +/* + * Copyright (C) 2016-2017 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU Affero General Public License version 3. + */ + +/* Genode includes */ +#include +#include +#include +#include +#include + +/* Genode block backend */ +#include + +namespace Fatfs { + +/* FatFS includes */ +#include + + using namespace Vfs; + using namespace Fatfs; + struct File_system; + +}; + + +class Fatfs::File_system : public Vfs::File_system +{ + private: + + typedef Genode::Path Path; + + struct Fatfs_handle; + typedef Genode::List Fatfs_handles; + + /** + * The FatFS library does not support opening a file + * for writing twice, so this plugin manages a tree of + * open files shared across open VFS handles. + */ + + struct File : Genode::Avl_node + { + Path path; + Fatfs::FIL fil; + Fatfs_handles handles; + + /************************ + ** Avl node interface ** + ************************/ + + bool higher(File *other) { + return (Genode::strcmp(other->path.base(), path.base()) > 0); } + + File *lookup(char const *path_str) + { + int const cmp = Genode::strcmp(path_str, path.base()); + if (cmp == 0) + return this; + + File *f = Genode::Avl_node::child(cmp); + return f ? f->lookup(path_str) : nullptr; + } + + /** + * Recursive flush to block device + */ + void flush() + { + /* flush the cache for this open file */ + f_sync(&fil); + + /* flush child nodes */ + if (File *f = Genode::Avl_node::child(-1)) + f->flush(); + if (File *f = Genode::Avl_node::child( 1)) + f->flush(); + } + }; + + struct Fatfs_handle : Vfs_handle, Fatfs_handles::Element + { + File *file = nullptr; + + Fatfs_handle(File_system &fs, Allocator &alloc, int status_flags) + : Vfs_handle(fs, fs, alloc, status_flags) { } + }; + + Genode::Env &_env; + Genode::Allocator &_alloc; + + FATFS _fatfs; + + /* Tree of open FatFS file objects */ + Genode::Avl_tree _open_files; + + /* Pre-allocated FIL */ + File *_next_file = nullptr; + + /** + * Flush pending writes on open files to blocks + */ + void _flush_open(Genode::Duration time) + { + if (_open_files.first()) + _open_files.first()->flush(); + } + + /** + * Timeout to schedule after writes + */ + Timer::Connection _timer { _env, "vfs_fatfs" }; + + Timer::One_shot_timeout _flush_timeout { + _timer, *this, &File_system::_flush_open }; + + /** + * Return an open FatFS file matching path or null. + */ + File *_opened_file(char const *path) + { + return _open_files.first() ? + _open_files.first()->lookup(path) : nullptr; + } + + /** + * Close an open FatFS file + */ + void _close(File &file) + { + /* close file */ + _open_files.remove(&file); + f_close(&file.fil); + + if (_next_file == nullptr) { + /* reclaim heap space */ + file.path.import(""); + _next_file = &file; + } else { + destroy(_alloc, &file); + } + } + + /** + * Invalidate all handles on a FatFS file + * and close the file + */ + void _close_all(File &file) + { + /* invalidate handles */ + for (Fatfs_handle *handle = file.handles.first(); + handle; handle = file.handles.first()) + { + handle->file = nullptr; + file.handles.remove(handle); + } + _close(file); + } + + public: + + File_system(Genode::Env &env, + Genode::Allocator &alloc, + Genode::Xml_node config) + : _env(env), _alloc(alloc) + { + { + static unsigned codepage = 0; + unsigned const cp = config.attribute_value( + "codepage", 0); + + if (codepage != 0 && codepage != cp) { + Genode::error( + "cannot reinitialize codepage for FAT library, please " + "use additional VFS instances for additional codepages"); + throw ~0; + } + + if (f_setcp(cp) != FR_OK) { + Genode::error("invalid OEM code page '", cp, "'"); + throw FR_INVALID_PARAMETER; + } + codepage = cp; + } + + auto const drive_num = config.attribute_value( + "drive", Genode::String<4>("0")); + +#if _USE_MKFS == 1 + if (config.attribute_value("format", false)) { + Genode::log("formatting drive ", drive_num, "..."); + if (f_mkfs((const TCHAR*)drive_num.string(), 1, 0) != FR_OK) { + Genode::error("format of drive ", drive_num, " failed"); + throw ~0; + } + } +#endif + + /* mount the file system */ + switch (f_mount(&_fatfs, (const TCHAR*)drive_num.string(), 1)) { + case FR_OK: { + TCHAR label[24] = { '\0' }; + f_getlabel((const TCHAR*)drive_num.string(), label, nullptr); + Genode::log("FAT file system \"", (char const *)label, "\" mounted"); + return; + } + case FR_INVALID_DRIVE: + Genode::error("invalid drive ", drive_num); throw ~0; + case FR_DISK_ERR: + Genode::error("drive ", drive_num, " disk error"); throw ~0; + case FR_NOT_READY: + Genode::error("drive ", drive_num, " not ready"); throw ~0; + case FR_NO_FILESYSTEM: + Genode::error("no file system on drive ", drive_num); throw ~0; + default: + Genode::error("failed to mount drive ", drive_num); throw ~0; + } + } + + /*************************** + ** File_system interface ** + ***************************/ + + char const *type() override { return "fatfs"; } + + + /********************************* + ** Directory service interface ** + *********************************/ + + Open_result open(char const *path, unsigned vfs_mode, + Vfs_handle **vfs_handle, + Allocator &alloc) override + { + Fatfs_handle *handle; + File *file = _opened_file(path); + bool create = vfs_mode & OPEN_MODE_CREATE; + + if (file && create) { + Genode::error("OPEN_ERR_EXISTS"); + return OPEN_ERR_EXISTS; + } + + /* attempt allocation before modifying blocks */ + if (!_next_file) + _next_file = new (_alloc) File(); + handle = new (alloc) Fatfs_handle(*this, alloc, vfs_mode); + + if (!file) { + file = _next_file; + FRESULT fres = f_open( + &_next_file->fil, (TCHAR const *)path, + FA_READ | FA_WRITE | (create ? FA_CREATE_NEW : FA_OPEN_EXISTING)); + if (fres != FR_OK) { + destroy(alloc, handle); + switch(fres) { + case FR_NO_FILE: + case FR_NO_PATH: return OPEN_ERR_UNACCESSIBLE; + case FR_EXIST: return OPEN_ERR_EXISTS; + case FR_INVALID_NAME: return OPEN_ERR_NAME_TOO_LONG; + default: return OPEN_ERR_NO_PERM; + } + } + + file->path.import(path); + _open_files.insert(file); + _next_file = nullptr; + } + + file->handles.insert(handle); + handle->file = file; + *vfs_handle = handle; + return OPEN_OK; + } + + void close(Vfs_handle *vfs_handle) override + { + Fatfs_handle *handle = static_cast(vfs_handle); + + File *file = handle->file; + if (file) { + file->handles.remove(handle); + if (!file->handles.first()) + _close(*file); + else + f_sync(&file->fil); + } + + destroy(handle->alloc(), handle); + } + + void sync(char const *path) override + { + /** + * Files are flushed when they are closed so + * only open files need to be synced. + */ + if (File *file = _opened_file(path)) + f_sync(&file->fil); + } + + Genode::Dataspace_capability dataspace(char const *path) override + { + Genode::warning(__func__, " not implemented in FAT plugin"); + return Genode::Dataspace_capability(); + } + + void release(char const *path, + Genode::Dataspace_capability ds_cap) override { } + + file_size num_dirent(char const *path) override + { + DIR dir; + FILINFO fno; + file_size count = 0; + + if (f_opendir(&dir, (const TCHAR*)path) != FR_OK) return 0; + + fno.fname[0] = 0xFF; + while ((f_readdir (&dir, &fno) == FR_OK) && fno.fname[0]) + ++count; + f_closedir(&dir); + return count; + } + + bool directory(char const *path) override + { + FILINFO fno; + + return f_stat((const TCHAR*)path, &fno) == FR_OK ? + (fno.fattrib & AM_DIR) : false; + } + + char const *leaf_path(char const *path) override + { + if (_opened_file(path)) { + return path; + } else { + FILINFO fno; + return (f_stat((const TCHAR*)path, &fno) == FR_OK) ? + path : 0; + } + } + + Mkdir_result mkdir(char const *path, unsigned mode) override + { + FRESULT res = f_mkdir((const TCHAR*)path); + switch (res) { + case FR_OK: return MKDIR_OK; + case FR_EXIST: return MKDIR_ERR_EXISTS; + case FR_NO_PATH: return MKDIR_ERR_NO_ENTRY; + case FR_INVALID_NAME: return MKDIR_ERR_NAME_TOO_LONG; + default: return MKDIR_ERR_NO_PERM; + } + } + + Stat_result stat(char const *path, Stat &stat) + { + stat = Stat(); + + FILINFO info; + + FRESULT const err = f_stat((const TCHAR*)path, &info); + switch (err) { + case FR_OK: + stat.inode = 1; + stat.device = (Genode::addr_t)this; + stat.mode = (info.fattrib & AM_DIR) ? + STAT_MODE_DIRECTORY : STAT_MODE_FILE; + /* XXX: size in f_stat is always zero */ + if ((stat.mode == STAT_MODE_FILE) && (info.fsize == 0)) { + File *file = _opened_file(path); + if (file) { + stat.size = f_size(&file->fil); + } else { + FIL fil; + if (f_open(&fil, (TCHAR const *)path, FA_READ) == FR_OK) { + stat.size = f_size(&fil); + f_close(&fil); + } + } + } else { + stat.size = info.fsize; + } + return STAT_OK; + + case FR_NO_FILE: + case FR_NO_PATH: + return STAT_ERR_NO_ENTRY; + + default: + Genode::error("unhandled FatFS::f_stat error ", (int)err); + return STAT_ERR_NO_PERM; + } + return STAT_ERR_NO_PERM; + } + + Dirent_result dirent(char const *path, file_offset dir_index, + Dirent &vfs_dir) override + { + /* not very efficient, just N calls to f_readdir */ + + DIR dir; + FILINFO info; + FRESULT res; + vfs_dir.fileno = 1; /* inode 0 is a pending unlink */ + + switch (f_opendir(&dir, (const TCHAR*)path)) { + case FR_OK: break; + case FR_NO_PATH: return DIRENT_ERR_INVALID_PATH; + default: return DIRENT_ERR_NO_PERM; + } + + do { + res = f_readdir (&dir, &info); + if ((res != FR_OK) || (!info.fname[0])) { + vfs_dir.type = DIRENT_TYPE_END; + vfs_dir.name[0] = '\0'; + f_closedir(&dir); + return DIRENT_OK; + } + } while (--dir_index >= 0); + + vfs_dir.type = (info.fattrib & AM_DIR) ? + DIRENT_TYPE_DIRECTORY : DIRENT_TYPE_FILE; + Genode::strncpy(vfs_dir.name, (const char*)info.fname, + sizeof(vfs_dir.name)); + f_closedir(&dir); + return DIRENT_OK; + } + + Unlink_result unlink(char const *path) override + { + /* close the file if it is open */ + if (File *file = _opened_file(path)) + _close_all(*file); + + switch (f_unlink((const TCHAR*)path)) { + case FR_OK: return UNLINK_OK; + case FR_NO_FILE: + case FR_NO_PATH: return UNLINK_ERR_NO_ENTRY; + default: return UNLINK_ERR_NO_PERM; + } + } + + Readlink_result readlink(char const*, char*, file_size, file_size&) override { + return READLINK_ERR_NO_PERM; } + + Symlink_result symlink(char const*, char const*) override { + return SYMLINK_ERR_NO_PERM; } + + Rename_result rename(char const *from, char const *to) override + { + if (File *to_file = _opened_file(to)) { + _close_all(*to_file); + f_unlink((TCHAR const *)to); + } else { + FILINFO info; + if (FR_OK == f_stat((TCHAR const *)to, &info)) { + if (info.fattrib & AM_DIR) { + return RENAME_ERR_NO_PERM; + } else { + f_unlink((TCHAR const *)to); + } + } + } + + if (File *from_file = _opened_file(from)) + _close_all(*from_file); + + switch (f_rename((const TCHAR*)from, (const TCHAR*)to)) { + case FR_OK: return RENAME_OK; + case FR_NO_FILE: + case FR_NO_PATH: return RENAME_ERR_NO_ENTRY; + default: return RENAME_ERR_NO_PERM; + } + } + + + /******************************* + ** File io service interface ** + *******************************/ + + Write_result write(Vfs_handle *vfs_handle, + char const *buf, file_size buf_size, + file_size &out_count) override + { + Fatfs_handle *handle = static_cast(vfs_handle); + if (!handle->file) + return WRITE_ERR_INVALID; + if ((handle->status_flags()&OPEN_MODE_ACCMODE) == OPEN_MODE_RDONLY) + return WRITE_ERR_INVALID; + + FRESULT fres; + FIL *fil = &handle->file->fil; + + fres = f_lseek(fil, handle->seek()); + if (fres == FR_OK) { + UINT bw = 0; + fres = f_write(fil, buf, buf_size, &bw); + out_count = bw; + } + + switch (fres) { + case FR_OK: + /* flush to blocks after ~1 seconds of inactivity */ + _flush_timeout.schedule(Genode::Microseconds(1 << 20)); + return WRITE_OK; + case FR_INVALID_OBJECT: return WRITE_ERR_INVALID; + case FR_TIMEOUT: return WRITE_ERR_WOULD_BLOCK; + default: return WRITE_ERR_IO; + } + } + + Read_result read(Vfs_handle *vfs_handle, char *buf, file_size buf_size, + file_size &out_count) override + { + Fatfs_handle *handle = static_cast(vfs_handle); + if (!handle->file) { + Genode::error("READ_ERR_INVALID"); + return READ_ERR_INVALID; + } + if ((handle->status_flags()&OPEN_MODE_ACCMODE) == OPEN_MODE_WRONLY) + return READ_ERR_INVALID; + + FRESULT fres; + FIL *fil = &handle->file->fil; + + fres = f_lseek(fil, handle->seek()); + if (fres == FR_OK) { + UINT bw = 0; + fres = f_read(fil, buf, buf_size, &bw); + out_count = bw; + } + + switch (fres) { + case FR_OK: return READ_OK; + case FR_INVALID_OBJECT: return READ_ERR_INVALID; + case FR_TIMEOUT: return READ_ERR_WOULD_BLOCK; + case FR_DISK_ERR: return READ_ERR_IO; + case FR_INT_ERR: return READ_ERR_IO; + case FR_DENIED: return READ_ERR_IO; + default: return READ_ERR_IO; + } + } + + Ftruncate_result ftruncate(Vfs_handle *vfs_handle, file_size len) override + { + FRESULT res; + Fatfs_handle *handle = static_cast(vfs_handle); + if (!handle->file) + return FTRUNCATE_ERR_NO_PERM; + if ((handle->status_flags()&OPEN_MODE_ACCMODE) == OPEN_MODE_RDONLY) + return FTRUNCATE_ERR_NO_PERM; + + FIL *fil = &handle->file->fil; + + /* f_lseek will exapand a file */ + res = f_lseek(fil, len); + + /* otherwise truncate will shorten the file to its seek position */ + if ((res == FR_OK) && (len < f_size(fil))) { + res = f_truncate(fil); + if (res == FR_OK && len < handle->seek()) + handle->seek(len); + } + + return res == FR_OK ? + FTRUNCATE_OK : FTRUNCATE_ERR_NO_PERM; + } + + bool read_ready(Vfs_handle *) override { return true; } +}; + + +struct Fatfs_factory : Vfs::File_system_factory +{ + struct Inner : Vfs::File_system_factory + { + Inner(Genode::Env &env, Genode::Allocator &alloc) { + Fatfs::block_init(env, alloc); } + + Vfs::File_system *create(Genode::Env &env, + Genode::Allocator &alloc, + Genode::Xml_node node, + Vfs::Io_response_handler &) override + { + return new (alloc) + Fatfs::File_system(env, alloc, node); + } + }; + + Vfs::File_system *create(Genode::Env &env, + Genode::Allocator &alloc, + Genode::Xml_node node, + Vfs::Io_response_handler &io_handler) override + { + static Inner factory(env, alloc); + return factory.create(env, alloc, node, io_handler); + } +}; + + +extern "C" Vfs::File_system_factory *vfs_file_system_factory(void) +{ + static Fatfs_factory factory; + return &factory; +} \ No newline at end of file