diff --git a/repos/libports/lib/mk/vfs_fatfs.mk b/repos/libports/lib/mk/vfs_fatfs.mk
new file mode 100644
index 0000000000..79bbbf0740
--- /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 0000000000..c89c50324c
--- /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 0000000000..bc6daa71ff
--- /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 0000000000..cdf7b96585
--- /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 0000000000..cd9fe4e6f8
--- /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 0000000000..9ecb454c7f
--- /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