diff --git a/libports/lib/mk/libc_vfs.mk b/libports/lib/mk/libc_vfs.mk
new file mode 100644
index 0000000000..a33e1b9747
--- /dev/null
+++ b/libports/lib/mk/libc_vfs.mk
@@ -0,0 +1,5 @@
+SRC_CC = plugin.cc
+LIBS += libc
+INC_DIR += $(REP_DIR)/src/lib/libc
+
+vpath plugin.cc $(REP_DIR)/src/lib/libc_vfs
diff --git a/libports/run/libc_vfs.run b/libports/run/libc_vfs.run
new file mode 100644
index 0000000000..8b33c59f50
--- /dev/null
+++ b/libports/run/libc_vfs.run
@@ -0,0 +1,74 @@
+#
+# \brief Test for using the libc_vfs plugin
+# \author Norman Feske
+# \date 2014-04-10
+#
+
+#
+# Build
+#
+
+build { core init server/ram_fs test/libc_vfs }
+
+create_boot_directory
+
+#
+# Generate config
+#
+
+install_config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+#
+# Boot modules
+#
+
+build_boot_image {
+ core init
+ ld.lib.so libc.lib.so libc_log.lib.so
+ ram_fs test-libc_vfs
+}
+
+#
+# Execute test case
+#
+
+append qemu_args " -m 128 -nographic "
+run_genode_until {.*child exited with exit value 0.*} 60
+
+puts "\ntest succeeded\n"
+
+# vi: set ft=tcl :
diff --git a/libports/src/lib/libc_vfs/plugin.cc b/libports/src/lib/libc_vfs/plugin.cc
new file mode 100644
index 0000000000..1c60f48b7f
--- /dev/null
+++ b/libports/src/lib/libc_vfs/plugin.cc
@@ -0,0 +1,892 @@
+/*
+ * \brief Libc plugin for using a process-local virtual file system
+ * \author Norman Feske
+ * \date 2014-04-09
+ */
+
+/*
+ * Copyright (C) 2014 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU General Public License version 2.
+ */
+
+/* Genode includes */
+#include
+#include
+#include
+#include
+
+/* supported builtin file systems */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* libc includes */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* libc plugin interface */
+#include
+#include
+
+/* libc-internal includes */
+#include
+
+
+static Vfs::Vfs_handle *vfs_handle(Libc::File_descriptor *fd)
+{
+ return reinterpret_cast(fd->context);
+}
+
+
+static Libc::Plugin_context *vfs_context(Vfs::Vfs_handle *vfs_handle)
+{
+ return reinterpret_cast(vfs_handle);
+}
+
+
+/**
+ * Utility to convert VFS stat struct to the libc stat struct
+ *
+ * Code shared between 'stat' and 'fstat'.
+ */
+static void vfs_stat_to_libc_stat_struct(Vfs::Directory_service::Stat const &src,
+ struct stat *dst)
+{
+ enum { FS_BLOCK_SIZE = 1024 };
+
+ Genode::memset(dst, 0, sizeof(*dst));
+
+ dst->st_uid = src.uid;
+ dst->st_gid = src.gid;
+ dst->st_mode = src.mode;
+ dst->st_size = src.size;
+ dst->st_blksize = FS_BLOCK_SIZE;
+ dst->st_blocks = (dst->st_size + FS_BLOCK_SIZE - 1) / FS_BLOCK_SIZE;
+ dst->st_ino = src.inode;
+ dst->st_dev = src.device;
+}
+
+
+class Libc_file_system_factory : public Vfs::File_system_factory
+{
+ private:
+
+ struct Entry_base : Genode::List::Element
+ {
+ char const * const name;
+
+ Entry_base(char const *name) : name(name) { }
+
+ virtual Vfs::File_system *create(Genode::Xml_node node) = 0;
+
+ bool matches(Genode::Xml_node node) const
+ {
+ return node.has_type(name);
+ }
+ };
+
+ template
+ struct Builtin_entry : Entry_base
+ {
+ Builtin_entry() : Entry_base(FILE_SYSTEM::name()) { }
+
+ Vfs::File_system *create(Genode::Xml_node node) override
+ {
+ return new (Genode::env()->heap()) FILE_SYSTEM(node);
+ }
+ };
+
+ Genode::List _list;
+
+ template
+ void _add_builtin_fs()
+ {
+ _list.insert(new (Genode::env()->heap()) Builtin_entry());
+ }
+
+ Vfs::File_system *_try_create(Genode::Xml_node node)
+ {
+ for (Entry_base *e = _list.first(); e; e = e->next())
+ if (e->matches(node))
+ return e->create(node);
+
+ return 0;
+ }
+
+ public:
+
+ Vfs::File_system *create(Genode::Xml_node node) override
+ {
+ /* try if type is handled by the currently registered fs types */
+ if (Vfs::File_system *fs = _try_create(node))
+ return fs;
+
+ /* XXX probe for file system implementation available as shared lib */
+
+ /* try again with the new file system type loaded */
+ if (Vfs::File_system *fs = _try_create(node))
+ return fs;
+
+ return 0;
+ }
+
+ Libc_file_system_factory()
+ {
+ _add_builtin_fs();
+ _add_builtin_fs();
+ _add_builtin_fs();
+ _add_builtin_fs();
+ _add_builtin_fs();
+ _add_builtin_fs();
+ _add_builtin_fs();
+ _add_builtin_fs();
+ _add_builtin_fs();
+ }
+};
+
+
+static Genode::Xml_node libc_config()
+{
+ return Genode::config()->xml_node().sub_node("libc");
+}
+
+
+static Genode::Xml_node libc_vfs_config()
+{
+ return libc_config().sub_node("vfs");
+}
+
+
+class Libc_config_attr
+{
+ private:
+
+ char _buf[Vfs::MAX_PATH_LEN];
+
+ public:
+
+ Libc_config_attr(char const *attr_name, char const *default_value)
+ {
+ strncpy(_buf, default_value, sizeof(_buf));
+ try {
+ libc_config().attribute(attr_name).value(_buf, sizeof(_buf));
+ } catch (...) { }
+ }
+
+ char const *string() const { return _buf; }
+};
+
+
+static char const *libc_initial_cwd()
+{
+ static Libc_config_attr initial_cwd("cwd", "/");
+ return initial_cwd.string();
+}
+
+
+static char const *libc_config_stdin()
+{
+ static Libc_config_attr stdin("stdin", "");
+ return stdin.string();
+}
+
+
+static char const *libc_config_stdout()
+{
+ static Libc_config_attr stdout("stdout", "");
+ return stdout.string();
+}
+
+
+static char const *libc_config_stderr()
+{
+ static Libc_config_attr stderr("stderr", "");
+ return stderr.string();
+}
+
+
+namespace Libc { class Vfs_plugin; }
+
+
+class Libc::Vfs_plugin : public Libc::Plugin
+{
+ private:
+
+ Libc_file_system_factory _fs_factory;
+
+ Vfs::Dir_file_system _root_dir;
+
+ Genode::Xml_node _vfs_config()
+ {
+ try {
+ return libc_vfs_config();
+ } catch (...) {
+ PINF("no VFS configured");
+ return Genode::Xml_node("");
+ }
+ }
+
+ void _open_stdio(int libc_fd, char const *path, unsigned flags)
+ {
+ struct stat out_stat;
+ if (::strlen(path) == 0 || stat(path, &out_stat) != 0)
+ return;
+
+ Libc::File_descriptor *fd = open(path, flags, libc_fd);
+ if (fd->libc_fd != libc_fd) {
+ PERR("could not allocate fd %d for %s, got fd %d",
+ libc_fd, path, fd->libc_fd);
+ close(fd);
+ return;
+ }
+
+ /*
+ * We need to manually register the path. Normally this is done
+ * by '_open'. But we call the local 'open' function directly
+ * because we want to explicitly specify the libc fd ID.
+ *
+ * We have to allocate the path from the libc (done via 'strdup')
+ * such that the path can be freed when an stdio fd is closed.
+ */
+ fd->fd_path = strdup(path);
+ }
+
+ public:
+
+ /**
+ * Constructor
+ */
+ Vfs_plugin() : _root_dir(_vfs_config(), _fs_factory)
+ {
+ chdir(libc_initial_cwd());
+
+ _open_stdio(0, libc_config_stdin(), O_RDONLY);
+ _open_stdio(1, libc_config_stdout(), O_WRONLY);
+ _open_stdio(2, libc_config_stderr(), O_WRONLY);
+ }
+
+ ~Vfs_plugin() { }
+
+ bool supports_mkdir(const char *, mode_t) override { return true; }
+ bool supports_open(const char *, int) override { return true; }
+ bool supports_readlink(const char *, char *, size_t) override { return true; }
+ bool supports_rename(const char *, const char *) override { return true; }
+ bool supports_rmdir(const char *) override { return true; }
+ bool supports_stat(const char *) override { return true; }
+ bool supports_symlink(const char *, const char *) override { return true; }
+ bool supports_unlink(const char *) override { return true; }
+ bool supports_mmap() override { return true; }
+
+ Libc::File_descriptor *open(const char *, int, int libc_fd);
+
+ Libc::File_descriptor *open(const char *path, int flags) override
+ {
+ return open(path, flags, Libc::ANY_FD);
+ }
+
+ int close(Libc::File_descriptor *) override;
+ int dup2(Libc::File_descriptor *, Libc::File_descriptor *) override;
+ int fcntl(Libc::File_descriptor *, int, long) override;
+ int fstat(Libc::File_descriptor *, struct stat *) override;
+ int fstatfs(Libc::File_descriptor *, struct statfs *) override;
+ int fsync(Libc::File_descriptor *fd) override;
+ int ftruncate(Libc::File_descriptor *, ::off_t) override;
+ ssize_t getdirentries(Libc::File_descriptor *, char *, ::size_t , ::off_t *) override;
+ int ioctl(Libc::File_descriptor *, int , char *) override;
+ ::off_t lseek(Libc::File_descriptor *fd, ::off_t offset, int whence) override;
+ int mkdir(const char *, mode_t) override;
+ ssize_t read(Libc::File_descriptor *, void *, ::size_t) override;
+ ssize_t readlink(const char *, char *, size_t) override;
+ int rename(const char *, const char *) override;
+ int rmdir(const char *) override;
+ int stat(const char *, struct stat *) override;
+ int symlink(const char *, const char *) override;
+ int unlink(const char *) override;
+ ssize_t write(Libc::File_descriptor *, const void *, ::size_t ) override;
+ void *mmap(void *, ::size_t, int, int, Libc::File_descriptor *, ::off_t) override;
+ int munmap(void *, ::size_t) override;
+};
+
+
+Libc::File_descriptor *Libc::Vfs_plugin::open(char const *path, int flags,
+ int libc_fd)
+{
+ typedef Vfs::Directory_service::Open_result Result;
+
+ Vfs::Vfs_handle *handle = 0;
+
+ while (handle == 0) {
+
+ switch (_root_dir.open(path, flags, &handle)) {
+
+ case Result::OPEN_OK:
+ errno = 0;
+ break;
+
+ case Result::OPEN_ERR_UNACCESSIBLE:
+ {
+ if (!(flags & O_CREAT)) {
+ errno = ENOENT;
+ return 0;
+ }
+
+ /* O_CREAT is set, so try to create the file */
+ switch (_root_dir.open(path, flags | O_EXCL, &handle)) {
+
+ case Result::OPEN_OK:
+ errno = 0;
+ break;
+
+ case Result::OPEN_ERR_EXISTS:
+
+ /* file has been created by someone else in the meantime */
+ break;
+
+ case Result::OPEN_ERR_NO_PERM: errno = EPERM; return 0;
+ case Result::OPEN_ERR_UNACCESSIBLE: errno = ENOENT; return 0;
+ }
+ }
+ break;
+
+ case Result::OPEN_ERR_NO_PERM: errno = EPERM; return 0;
+ case Result::OPEN_ERR_EXISTS: errno = EEXIST; return 0;
+ }
+ }
+
+ /* the file was successfully opened */
+
+ Libc::File_descriptor *fd =
+ Libc::file_descriptor_allocator()->alloc(this, vfs_context(handle), libc_fd);
+
+ fd->status = flags;
+
+ if ((flags & O_TRUNC) && (ftruncate(fd, 0) == -1)) {
+ /* XXX leaking fd, missing errno */
+ return 0;
+ }
+
+ return fd;
+}
+
+
+int Libc::Vfs_plugin::close(Libc::File_descriptor *fd)
+{
+ Genode::destroy(Genode::env()->heap(), vfs_handle(fd));
+ Libc::file_descriptor_allocator()->free(fd);
+ return 0;
+}
+
+
+int Libc::Vfs_plugin::dup2(Libc::File_descriptor *fd,
+ Libc::File_descriptor *new_fd)
+{
+ new_fd->context = fd->context;
+ return new_fd->libc_fd;
+}
+
+
+int Libc::Vfs_plugin::fstat(Libc::File_descriptor *fd, struct stat *buf)
+{
+ return stat(fd->fd_path, buf);
+}
+
+
+int Libc::Vfs_plugin::fstatfs(Libc::File_descriptor *, struct statfs *buf)
+{
+ buf->f_flags = MNT_UNION;
+ return 0;
+}
+
+
+int Libc::Vfs_plugin::mkdir(const char *path, mode_t mode)
+{
+ typedef Vfs::Directory_service::Mkdir_result Result;
+
+ switch (_root_dir.mkdir(path, mode)) {
+ case Result::MKDIR_ERR_EXISTS: errno = EEXIST; return -1;
+ case Result::MKDIR_ERR_NO_ENTRY: errno = ENOENT; return -1;
+ case Result::MKDIR_ERR_NO_SPACE: errno = ENOSPC; return -1;
+ case Result::MKDIR_ERR_NAME_TOO_LONG: errno = ENAMETOOLONG; return -1;
+ case Result::MKDIR_ERR_NO_PERM: errno = EPERM; return -1;
+ case Result::MKDIR_OK: errno = 0; break;
+ }
+ return 0;
+}
+
+
+int Libc::Vfs_plugin::stat(char const *path, struct stat *buf)
+{
+ if (!path or !buf) {
+ errno = EFAULT;
+ return -1;
+ }
+
+ typedef Vfs::Directory_service::Stat_result Result;
+
+ Vfs::Directory_service::Stat stat;
+
+ switch (_root_dir.stat(path, stat)) {
+ case Result::STAT_ERR_NO_ENTRY: errno = ENOENT; return -1;
+ case Result::STAT_OK: errno = 0; break;
+ }
+
+ vfs_stat_to_libc_stat_struct(stat, buf);
+ return 0;
+}
+
+
+ssize_t Libc::Vfs_plugin::write(Libc::File_descriptor *fd, const void *buf,
+ ::size_t count)
+{
+ typedef Vfs::File_io_service::Write_result Result;
+
+ Vfs::Vfs_handle *handle = vfs_handle(fd);
+
+ size_t out_count = 0;
+
+ switch (handle->fs().write(handle, (char const *)buf, count, out_count)) {
+ case Result::WRITE_ERR_AGAIN: errno = EAGAIN; return -1;
+ case Result::WRITE_ERR_WOULD_BLOCK: errno = EWOULDBLOCK; return -1;
+ case Result::WRITE_ERR_INVALID: errno = EINVAL; return -1;
+ case Result::WRITE_ERR_IO: errno = EIO; return -1;
+ case Result::WRITE_ERR_INTERRUPT: errno = EINTR; return -1;
+ case Result::WRITE_OK: errno = 0; break;
+ }
+
+ handle->advance_seek(out_count);
+
+ return out_count;
+}
+
+
+ssize_t Libc::Vfs_plugin::read(Libc::File_descriptor *fd, void *buf,
+ ::size_t count)
+{
+ typedef Vfs::File_io_service::Read_result Result;
+
+ Vfs::Vfs_handle *handle = vfs_handle(fd);
+
+ Genode::size_t out_count = 0;
+
+ switch (handle->fs().read(handle, (char *)buf, count, out_count)) {
+ case Result::READ_ERR_AGAIN: errno = EAGAIN; PERR("A1"); return -1;
+ case Result::READ_ERR_WOULD_BLOCK: errno = EWOULDBLOCK; PERR("A2"); return -1;
+ case Result::READ_ERR_INVALID: errno = EINVAL; PERR("A3"); return -1;
+ case Result::READ_ERR_IO: errno = EIO; PERR("A4"); return -1;
+ case Result::READ_ERR_INTERRUPT: errno = EINTR; PERR("A5"); return -1;
+ case Result::READ_OK: errno = 0; break;
+ }
+
+ handle->advance_seek(out_count);
+
+ return out_count;
+}
+
+
+ssize_t Libc::Vfs_plugin::getdirentries(Libc::File_descriptor *fd, char *buf,
+ ::size_t nbytes, ::off_t *basep)
+{
+ if (nbytes < sizeof(struct dirent)) {
+ PERR("getdirentries: buffer too small");
+ return -1;
+ }
+
+ typedef Vfs::Directory_service::Dirent_result Result;
+
+ Vfs::Vfs_handle *handle = vfs_handle(fd);
+
+ Vfs::Directory_service::Dirent dirent_out;
+ Genode::memset(&dirent_out, 0, sizeof(dirent_out));
+
+ unsigned const index = handle->seek() / sizeof(Vfs::Directory_service::Dirent);
+
+ switch (handle->ds().dirent(fd->fd_path, index, dirent_out)) {
+ case Result::DIRENT_ERR_INVALID_PATH: /* XXX errno */ return -1;
+ case Result::DIRENT_OK: errno = 0; break;
+ }
+
+ /*
+ * Convert dirent structure from VFS to libc
+ */
+
+ struct dirent *dirent = (struct dirent *)buf;
+ Genode::memset(dirent, 0, sizeof(struct dirent));
+
+ switch (dirent_out.type) {
+ case Vfs::Directory_service::DIRENT_TYPE_DIRECTORY: dirent->d_type = DT_DIR; break;
+ case Vfs::Directory_service::DIRENT_TYPE_FILE: dirent->d_type = DT_REG; break;
+ case Vfs::Directory_service::DIRENT_TYPE_SYMLINK: dirent->d_type = DT_LNK; break;
+ case Vfs::Directory_service::DIRENT_TYPE_FIFO: dirent->d_type = DT_FIFO; break;
+ case Vfs::Directory_service::DIRENT_TYPE_CHARDEV: dirent->d_type = DT_CHR; break;
+ case Vfs::Directory_service::DIRENT_TYPE_BLOCKDEV: dirent->d_type = DT_BLK; break;
+ case Vfs::Directory_service::DIRENT_TYPE_END: return 0;
+ }
+
+ dirent->d_fileno = dirent_out.fileno;
+ dirent->d_reclen = sizeof(struct dirent);
+
+ Genode::strncpy(dirent->d_name, dirent_out.name, sizeof(dirent->d_name));
+
+ dirent->d_namlen = Genode::strlen(dirent->d_name);
+
+ /*
+ * Keep track of VFS seek pointer and user-supplied basep.
+ */
+ handle->advance_seek(sizeof(Vfs::Directory_service::Dirent));
+
+ *basep += sizeof(struct dirent);
+
+ return sizeof(struct dirent);
+}
+
+
+int Libc::Vfs_plugin::ioctl(Libc::File_descriptor *fd, int request, char *argp)
+{
+ using ::off_t;
+
+ /*
+ * Marshal ioctl arguments
+ */
+
+ typedef Vfs::File_io_service::Ioctl_opcode Opcode;
+
+ Opcode opcode = Opcode::IOCTL_OP_UNDEFINED;
+
+ Vfs::File_io_service::Ioctl_arg arg = 0;
+
+ switch (request) {
+
+ case TIOCGWINSZ:
+ opcode = Opcode::IOCTL_OP_TIOCGWINSZ;
+ break;
+
+ case TIOCGETA:
+ {
+ ::termios *termios = (::termios *)argp;
+
+ termios->c_iflag = 0;
+ termios->c_oflag = 0;
+ termios->c_cflag = 0;
+ /*
+ * Set 'ECHO' flag, needed by libreadline. Otherwise, echoing
+ * user input doesn't work in bash.
+ */
+ termios->c_lflag = ECHO;
+ ::memset(termios->c_cc, _POSIX_VDISABLE, sizeof(termios->c_cc));
+ termios->c_ispeed = 0;
+ termios->c_ospeed = 0;
+
+ return 0;
+ }
+
+ break;
+
+ case TIOCSETAF:
+ {
+ opcode = Opcode::IOCTL_OP_TIOCSETAF;
+
+ ::termios *termios = (::termios *)argp;
+
+ /*
+ * For now only enabling/disabling of ECHO is supported
+ */
+ if (termios->c_lflag & (ECHO | ECHONL)) {
+ arg = (Vfs::File_io_service::IOCTL_VAL_ECHO |
+ Vfs::File_io_service::IOCTL_VAL_ECHONL);
+ }
+ else {
+ arg = Vfs::File_io_service::IOCTL_VAL_NULL;
+ }
+
+ break;
+ }
+
+ case TIOCSETAW:
+ {
+ opcode = Opcode::IOCTL_OP_TIOCSETAW;
+ arg = argp ? *(int*)argp : 0;
+ break;
+ }
+
+ case FIONBIO:
+ {
+ opcode = Opcode::IOCTL_OP_FIONBIO;
+ arg = argp ? *(int*)argp : 0;
+ break;
+ }
+
+ case DIOCGMEDIASIZE:
+ {
+ opcode = Opcode::IOCTL_OP_DIOCGMEDIASIZE;
+ arg = 0;
+ break;
+ }
+
+ default:
+ PWRN("unsupported ioctl (request=0x%x)", request);
+ break;
+ }
+
+ if (opcode == Opcode::IOCTL_OP_UNDEFINED) {
+ errno = ENOTTY;
+ return -1;
+ }
+
+ typedef Vfs::File_io_service::Ioctl_result Result;
+
+ Vfs::File_io_service::Ioctl_out out;
+ Genode::memset(&out, 0, sizeof(out));
+
+ Vfs::Vfs_handle *handle = vfs_handle(fd);
+
+ switch (handle->fs().ioctl(handle, opcode, arg, out)) {
+ case Vfs::File_io_service::IOCTL_ERR_INVALID: errno = EINVAL; return -1;
+ case Vfs::File_io_service::IOCTL_ERR_NOTTY: errno = ENOTTY; return -1;
+ case Vfs::File_io_service::IOCTL_OK: errno = 0; break;
+ }
+
+ /*
+ * Unmarshal ioctl results
+ */
+ switch (request) {
+
+ case TIOCGWINSZ:
+ {
+ ::winsize *winsize = (::winsize *)arg;
+ winsize->ws_row = out.tiocgwinsz.rows;
+ winsize->ws_col = out.tiocgwinsz.columns;
+ return 0;
+ }
+ case TIOCSETAF:
+ case TIOCSETAW:
+ return 0;
+
+ case FIONBIO:
+ return 0;
+
+ case DIOCGMEDIASIZE:
+ {
+ /* resolve ambiguity with libc type */
+ using Genode::int64_t;
+
+ int64_t *disk_size = (int64_t*)arg;
+ *disk_size = out.diocgmediasize.size;
+ return 0;
+ }
+
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+
+::off_t Libc::Vfs_plugin::lseek(Libc::File_descriptor *fd, ::off_t offset, int whence)
+{
+ Vfs::Vfs_handle *handle = vfs_handle(fd);
+
+ switch (whence) {
+ case SEEK_SET: handle->seek(offset); break;
+ case SEEK_CUR: handle->advance_seek(offset); break;
+ case SEEK_END:
+ {
+ struct stat stat;
+ ::memset(&stat, 0, sizeof(stat));
+ fstat(fd, &stat);
+ handle->seek(stat.st_size + offset);
+ }
+ break;
+ }
+ return handle->seek();
+}
+
+
+int Libc::Vfs_plugin::ftruncate(Libc::File_descriptor *fd, ::off_t length)
+{
+ Vfs::Vfs_handle *handle = vfs_handle(fd);
+
+ typedef Vfs::File_io_service::Ftruncate_result Result;
+
+ switch (handle->fs().ftruncate(handle, length)) {
+ case Result::FTRUNCATE_ERR_NO_PERM: errno = EPERM; return -1;
+ case Result::FTRUNCATE_ERR_INTERRUPT: errno = EINTR; return -1;
+ case Result::FTRUNCATE_OK: errno = 0; break;
+ }
+ return 0;
+}
+
+
+int Libc::Vfs_plugin::fcntl(Libc::File_descriptor *fd, int cmd, long arg)
+{
+ switch (cmd) {
+ case F_DUPFD:
+ {
+ /*
+ * Allocate free file descriptor locally.
+ */
+ Libc::File_descriptor *new_fd =
+ Libc::file_descriptor_allocator()->alloc(this, 0);
+ new_fd->path(fd->fd_path);
+
+ /*
+ * Use new allocated number as name of file descriptor
+ * duplicate.
+ */
+ if (dup2(fd, new_fd) == -1) {
+ PERR("Plugin::fcntl: dup2 unexpectedly failed");
+ errno = EINVAL;
+ return -1;
+ }
+
+ return new_fd->libc_fd;
+ }
+ case F_GETFD: return fd->flags;
+ case F_SETFD: fd->flags = arg; return 0;
+ case F_GETFL: return fd->status;
+
+ default:
+ break;
+ }
+
+ PERR("fcntl(): command %d not supported", cmd);
+ errno = EINVAL;
+ return -1;
+}
+
+
+int Libc::Vfs_plugin::fsync(Libc::File_descriptor *fd)
+{
+ _root_dir.sync();
+ return 0;
+}
+
+
+int Libc::Vfs_plugin::symlink(const char *oldpath, const char *newpath)
+{
+ typedef Vfs::Directory_service::Symlink_result Result;
+
+ switch (_root_dir.symlink(oldpath, newpath)) {
+ case Result::SYMLINK_ERR_EXISTS: errno = EEXIST; return -1;
+ case Result::SYMLINK_ERR_NO_ENTRY: errno = ENOENT; return -1;
+ case Result::SYMLINK_ERR_NAME_TOO_LONG: errno = ENAMETOOLONG; return -1;
+ case Result::SYMLINK_ERR_NO_PERM: errno = ENOSYS; return -1;
+ case Result::SYMLINK_OK: errno = 0; break;
+ }
+ return 0;
+}
+
+
+ssize_t Libc::Vfs_plugin::readlink(const char *path, char *buf, size_t buf_size)
+{
+ typedef Vfs::Directory_service::Readlink_result Result;
+
+ size_t out_len = 0;
+
+ switch (_root_dir.readlink(path, buf, buf_size, out_len)) {
+ case Result::READLINK_ERR_NO_ENTRY: errno = ENOENT; return -1;
+ case Result::READLINK_OK: errno = 0; break;
+ };
+
+ return out_len;
+}
+
+
+int Libc::Vfs_plugin::rmdir(char const *path)
+{
+ return unlink(path);
+}
+
+
+int Libc::Vfs_plugin::unlink(char const *path)
+{
+ typedef Vfs::Directory_service::Unlink_result Result;
+
+ switch (_root_dir.unlink(path)) {
+ case Result::UNLINK_ERR_NO_ENTRY: errno = ENOENT; return -1;
+ case Result::UNLINK_ERR_NO_PERM: errno = EPERM; return -1;
+ case Result::UNLINK_OK: errno = 0; break;
+ }
+ return 0;
+}
+
+
+int Libc::Vfs_plugin::rename(char const *from_path, char const *to_path)
+{
+ typedef Vfs::Directory_service::Rename_result Result;
+
+ switch (_root_dir.rename(from_path, to_path)) {
+ case Result::RENAME_ERR_NO_ENTRY: errno = ENOENT; return -1;
+ case Result::RENAME_ERR_CROSS_FS: errno = EXDEV; return -1;
+ case Result::RENAME_ERR_NO_PERM: errno = EPERM; return -1;
+ case Result::RENAME_OK: errno = 0; break;
+ }
+ return 0;
+}
+
+
+void *Libc::Vfs_plugin::mmap(void *addr_in, ::size_t length, int prot, int flags,
+ Libc::File_descriptor *fd, ::off_t offset)
+{
+ if (prot != PROT_READ) {
+ PERR("mmap for prot=%x not supported", prot);
+ errno = EACCES;
+ return (void *)-1;
+ }
+
+ if (addr_in != 0) {
+ PERR("mmap for predefined address not supported");
+ errno = EINVAL;
+ return (void *)-1;
+ }
+
+ /*
+ * XXX attempt to obtain memory mapping via
+ * 'Vfs::Directory_service::dataspace'.
+ */
+
+ void *addr = Libc::mem_alloc()->alloc(length, PAGE_SHIFT);
+ if (addr == (void *)-1) {
+ errno = ENOMEM;
+ return (void *)-1;
+ }
+
+ if (::pread(fd->libc_fd, addr, length, offset) < 0) {
+ PERR("mmap could not obtain file content");
+ ::munmap(addr, length);
+ errno = EACCES;
+ return (void *)-1;
+ }
+
+ return addr;
+}
+
+
+int Libc::Vfs_plugin::munmap(void *addr, ::size_t)
+{
+ Libc::mem_alloc()->free(addr);
+ return 0;
+}
+
+
+void __attribute__((constructor)) init_libc_vfs(void)
+{
+ static Libc::Vfs_plugin plugin;
+}
diff --git a/libports/src/test/libc_vfs/target.mk b/libports/src/test/libc_vfs/target.mk
new file mode 100644
index 0000000000..c97634b3ab
--- /dev/null
+++ b/libports/src/test/libc_vfs/target.mk
@@ -0,0 +1,6 @@
+TARGET = test-libc_vfs
+LIBS = libc libc_log libc_vfs
+SRC_CC = main.cc
+
+# we re-use the libc_ffat test
+vpath main.cc $(REP_DIR)/src/test/libc_ffat
diff --git a/tool/autopilot.list b/tool/autopilot.list
index 267487fe55..9b3b319587 100644
--- a/tool/autopilot.list
+++ b/tool/autopilot.list
@@ -8,6 +8,7 @@ tar_rom
noux
noux_net_netcat
libc_ffat
+libc_vfs
timed_semaphore
signal
sub_rm