lib/vfs/fatfs: FAT file-system plugin using FatFS library

See repos/libports/src/lib/vfs/fatfs/README and
/home/user/repo/genode/repos/libports/run/libc_vfs_fat.run for
documentation.

Ref #2410
This commit is contained in:
Emery Hemingway 2017-07-24 11:46:24 -05:00 committed by Christian Helmuth
parent 6401d52e61
commit 31caae4765
6 changed files with 696 additions and 0 deletions

View File

@ -0,0 +1,7 @@
SRC_CC = vfs_fatfs.cc
LIBS += fatfs_block
vpath %.cc $(REP_DIR)/src/lib/vfs/fatfs
SHARED_LIB = yes

View File

@ -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 "<fatfs/>"
set test_boot_modules vfs_fatfs.lib.so
set use_vfs_server 0
source ${genode_dir}/repos/libports/run/libc_vfs_filesystem_test.inc

View File

@ -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 "<fatfs/>"
set test_boot_modules vfs_fatfs.lib.so
set use_vfs_server 1
source ${genode_dir}/repos/libports/run/libc_vfs_filesystem_test.inc

View File

@ -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.

View File

@ -0,0 +1,2 @@
TARGET = dummy-vfs_fatfs
LIBS = vfs_fatfs

View File

@ -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 <vfs/file_system_factory.h>
#include <vfs/file_system.h>
#include <vfs/vfs_handle.h>
#include <os/path.h>
#include <timer_session/connection.h>
/* Genode block backend */
#include <fatfs/block.h>
namespace Fatfs {
/* FatFS includes */
#include <fatfs/ff.h>
using namespace Vfs;
using namespace Fatfs;
struct File_system;
};
class Fatfs::File_system : public Vfs::File_system
{
private:
typedef Genode::Path<FF_MAX_LFN> Path;
struct Fatfs_handle;
typedef Genode::List<Fatfs_handle> 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<File>
{
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<File>::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<File>::child(-1))
f->flush();
if (File *f = Genode::Avl_node<File>::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<File> _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<Fatfs::File_system> _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<unsigned>(
"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<Fatfs_handle *>(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<Fatfs_handle *>(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<Fatfs_handle *>(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<Fatfs_handle *>(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;
}