diff --git a/ports/include/noux_session/noux_session.h b/ports/include/noux_session/noux_session.h
index f51439d5db..caa46564ff 100644
--- a/ports/include/noux_session/noux_session.h
+++ b/ports/include/noux_session/noux_session.h
@@ -44,6 +44,7 @@ namespace Noux {
 			SYSCALL_OPEN,
 			SYSCALL_CLOSE,
 			SYSCALL_IOCTL,
+			SYSCALL_LSEEK,
 			SYSCALL_DIRENT,
 			SYSCALL_FCHDIR,
 			SYSCALL_EXECVE,
@@ -53,6 +54,9 @@ namespace Noux {
 			SYSCALL_WAIT4,
 			SYSCALL_PIPE,
 			SYSCALL_DUP2,
+			SYSCALL_UNLINK,
+			SYSCALL_RENAME,
+			SYSCALL_MKDIR,
 			SYSCALL_INVALID = -1
 		};
 
@@ -69,6 +73,7 @@ namespace Noux {
 			NOUX_DECL_SYSCALL_NAME(OPEN)
 			NOUX_DECL_SYSCALL_NAME(CLOSE)
 			NOUX_DECL_SYSCALL_NAME(IOCTL)
+			NOUX_DECL_SYSCALL_NAME(LSEEK)
 			NOUX_DECL_SYSCALL_NAME(DIRENT)
 			NOUX_DECL_SYSCALL_NAME(FCHDIR)
 			NOUX_DECL_SYSCALL_NAME(EXECVE)
@@ -78,6 +83,9 @@ namespace Noux {
 			NOUX_DECL_SYSCALL_NAME(WAIT4)
 			NOUX_DECL_SYSCALL_NAME(PIPE)
 			NOUX_DECL_SYSCALL_NAME(DUP2)
+			NOUX_DECL_SYSCALL_NAME(UNLINK)
+			NOUX_DECL_SYSCALL_NAME(RENAME)
+			NOUX_DECL_SYSCALL_NAME(MKDIR)
 			case SYSCALL_INVALID: return 0;
 			}
 			return 0;
diff --git a/ports/include/noux_session/sysio.h b/ports/include/noux_session/sysio.h
index da4be46513..543a6794c2 100644
--- a/ports/include/noux_session/sysio.h
+++ b/ports/include/noux_session/sysio.h
@@ -58,6 +58,17 @@ namespace Noux {
 
 		typedef __SIZE_TYPE__ size_t;
 
+		/**
+		 * Flags of 'mode' argument of open syscall
+		 */
+		enum {
+			OPEN_MODE_RDONLY  = 0,
+			OPEN_MODE_WRONLY  = 1,
+			OPEN_MODE_RDWR    = 2,
+			OPEN_MODE_ACCMODE = 3,
+			OPEN_MODE_CREATE  = 0x0200,
+		};
+
 		enum {
 			STAT_MODE_SYMLINK   = 0120000,
 			STAT_MODE_FILE      = 0100000,
@@ -100,6 +111,8 @@ namespace Noux {
 			};
 		};
 
+		enum Lseek_whence { LSEEK_SET, LSEEK_CUR, LSEEK_END };
+
 		enum { DIRENT_MAX_NAME_LEN = 128 };
 
 		enum Dirent_type {
@@ -208,8 +221,14 @@ namespace Noux {
 		enum General_error { ERR_FD_INVALID, NUM_GENERAL_ERRORS };
 		enum Stat_error    { STAT_ERR_NO_ENTRY     = NUM_GENERAL_ERRORS };
 		enum Fcntl_error   { FCNTL_ERR_CMD_INVALID = NUM_GENERAL_ERRORS };
-		enum Open_error    { OPEN_ERR_UNACCESSIBLE = NUM_GENERAL_ERRORS };
+		enum Open_error    { OPEN_ERR_UNACCESSIBLE, OPEN_ERR_NO_PERM };
 		enum Execve_error  { EXECVE_NONEXISTENT    = NUM_GENERAL_ERRORS };
+		enum Unlink_error  { UNLINK_ERR_NO_ENTRY, UNLINK_ERR_NO_PERM };
+		enum Rename_error  { RENAME_ERR_NO_ENTRY, RENAME_ERR_CROSS_FS,
+		                     RENAME_ERR_NO_PERM };
+		enum Mkdir_error   { MKDIR_ERR_EXISTS,   MKDIR_ERR_NO_ENTRY,
+		                     MKDIR_ERR_NO_SPACE, MKDIR_ERR_NO_PERM,
+		                     MKDIR_ERR_NAME_TOO_LONG};
 
 		union {
 			General_error general;
@@ -217,13 +236,17 @@ namespace Noux {
 			Fcntl_error   fcntl;
 			Open_error    open;
 			Execve_error  execve;
+			Unlink_error  unlink;
+			Rename_error  rename;
+			Mkdir_error   mkdir;
 		} error;
 
 		union {
 
 			SYSIO_DECL(getcwd, { }, { Path path; });
 
-			SYSIO_DECL(write,  { int fd; size_t count; Chunk chunk; }, { });
+			SYSIO_DECL(write,  { int fd; size_t count; Chunk chunk; },
+			                   { size_t count; });
 
 			SYSIO_DECL(stat,   { Path path; }, { Stat st; });
 
@@ -238,7 +261,10 @@ namespace Noux {
 
 			SYSIO_DECL(ioctl,  : Ioctl_in { int fd; }, : Ioctl_out { });
 
-			SYSIO_DECL(dirent, { int fd; int index; }, { Dirent entry; });
+			SYSIO_DECL(lseek,  { int fd; off_t offset; Lseek_whence whence; },
+			                   { off_t offset; });
+
+			SYSIO_DECL(dirent, { int fd; }, { Dirent entry; });
 
 			SYSIO_DECL(fchdir, { int fd; }, { });
 
@@ -261,6 +287,12 @@ namespace Noux {
 			SYSIO_DECL(pipe,   { }, { int fd[2]; });
 
 			SYSIO_DECL(dup2,   { int fd; int to_fd; }, { });
+
+			SYSIO_DECL(unlink, { Path path; }, { });
+
+			SYSIO_DECL(rename, { Path from_path; Path to_path; }, { });
+
+			SYSIO_DECL(mkdir, { Path path; int mode; }, { });
 		};
 	};
 };
diff --git a/ports/run/noux.run b/ports/run/noux.run
index e99fcf3acb..8a9e411ba5 100644
--- a/ports/run/noux.run
+++ b/ports/run/noux.run
@@ -47,7 +47,7 @@ install_config {
 		<start name="noux">
 			<resource name="RAM" quantum="1G"/>
 			<config>
-				<fstab> <tar name="coreutils.tar" at="/"/> </fstab>
+				<fstab> <tar name="coreutils.tar" /> </fstab>
 				<start name="/bin/ls"> <arg value="-Rla"/> </start>
 			</config>
 		</start>
diff --git a/ports/run/noux_bash.run b/ports/run/noux_bash.run
index d026e82cba..96dda1e06a 100644
--- a/ports/run/noux_bash.run
+++ b/ports/run/noux_bash.run
@@ -12,7 +12,7 @@ if {![have_spec x86]} {
 set build_components {
 	core init drivers/timer noux lib/libc_noux
 	drivers/framebuffer drivers/pci drivers/input
-	server/terminal
+	server/terminal server/ram_fs
 	test/libports/ncurses
 }
 
@@ -38,9 +38,9 @@ close $vimrc_fd
 # strip all binaries prior archiving
 exec sh -c "find bin/bash/ bin/vim/ bin/coreutils/ -type f | (xargs strip || true) 2>/dev/null"
 
-exec tar cfv bin/bash.tar -h -C bin/bash .
-exec tar rfv bin/bash.tar -h -C bin/coreutils .
-exec tar rfv bin/bash.tar -h -C bin/vim .
+exec tar cfv bin/bash.tar      -h -C bin/bash .
+exec tar cfv bin/coreutils.tar -h -C bin/coreutils .
+exec tar cfv bin/vim.tar       -h -C bin/vim .
 
 create_boot_directory
 
@@ -101,23 +101,62 @@ append_if [have_spec ps2] config {
 	</start> }
 
 append config {
-		<start name="terminal">
-			<resource name="RAM" quantum="2M"/>
-			<provides><service name="Terminal"/></provides>
-			<config>
-				<keyboard layout="de"/>
-			</config>
-		</start>
-		<start name="noux">
-			<resource name="RAM" quantum="1G"/>
-			<config>
-				<fstab> <tar name="bash.tar" at="/"/> </fstab>
-				<start name="/bin/bash">
-					<env name="TERM" value="linux" />
-				</start>
-			</config>
-		</start>
-	</config>
+	<start name="terminal">
+		<resource name="RAM" quantum="2M"/>
+		<provides><service name="Terminal"/></provides>
+		<config>
+			<keyboard layout="de"/>
+		</config>
+	</start>
+	<start name="ram_fs">
+		<resource name="RAM" quantum="10M"/>
+		<provides><service name="File_system"/></provides>
+		<config>
+			<!-- preload RAM file system with some ROM images -->
+			<content>
+				<dir name="tmp">
+					<rom name="init" as="blubb" />
+				</dir>
+				<dir name="home">
+					<dir name="user">
+						<!-- just a place holder -->
+						<rom name="timer" />
+					</dir>
+				</dir>
+			</content>
+			<!-- constrain sessions according to their labels -->
+			<policy label="noux -> root" root="/" />
+			<policy label="noux -> home" root="/home/user" writeable="yes" />
+			<policy label="noux -> tmp"  root="/tmp"       writeable="yes" />
+		</config>
+	</start>
+	<start name="noux">
+		<resource name="RAM" quantum="1G" />
+		<config>
+			<fstab>
+				<tar name="coreutils.tar" />
+				<tar name="vim.tar" />
+				<tar name="bash.tar" />
+
+				<!-- Example of how to impose policy onto a file system
+				     session. The label attached to the 'fs' node is used
+				     as a key to select the policy of 'ram_fs' -->
+
+				<dir name="home"> <fs label="home" /> </dir>
+
+				<!-- The entirety of ram_fs is mounted within the '/ram'
+				     directory. -->
+
+				<dir name="ram"> <fs label="root" /> </dir>
+				<dir name="tmp"> <fs label="tmp" /> </dir>
+
+			</fstab>
+			<start name="/bin/bash">
+				<env name="TERM" value="linux" />
+			</start>
+		</config>
+	</start>
+</config>
 }
 
 install_config $config
@@ -129,9 +168,9 @@ install_config $config
 
 # generic modules
 set boot_modules {
-	core init timer ld.lib.so noux terminal
+	core init timer ld.lib.so noux terminal ram_fs
 	libc.lib.so libm.lib.so libc_noux.lib.so ncurses.lib.so
-	bash.tar
+	bash.tar coreutils.tar vim.tar
 }
 
 # platform-specific modules
diff --git a/ports/run/noux_fork.run b/ports/run/noux_fork.run
index 58f1e126cf..d783866944 100644
--- a/ports/run/noux_fork.run
+++ b/ports/run/noux_fork.run
@@ -46,7 +46,7 @@ install_config {
 		<start name="noux">
 			<resource name="RAM" quantum="1G"/>
 			<config>
-				<fstab> <tar name="noux_fork.tar" at="/"/> </fstab>
+				<fstab> <tar name="noux_fork.tar" /> </fstab>
 				<start name="test-noux_fork"> </start>
 			</config>
 		</start>
diff --git a/ports/run/noux_vim.run b/ports/run/noux_vim.run
index 351d72ceba..9c9eefbf44 100644
--- a/ports/run/noux_vim.run
+++ b/ports/run/noux_vim.run
@@ -90,7 +90,7 @@ append config {
 		<start name="noux">
 			<resource name="RAM" quantum="1G"/>
 			<config>
-				<fstab> <tar name="vim.tar" at="/"/> </fstab>
+				<fstab> <tar name="vim.tar" /> </fstab>
 				<start name="/bin/vim">
 					<env name="TERM" value="linux" />
 
diff --git a/ports/src/lib/libc_noux/plugin.cc b/ports/src/lib/libc_noux/plugin.cc
index 910ea7479f..5b6cbdab39 100644
--- a/ports/src/lib/libc_noux/plugin.cc
+++ b/ports/src/lib/libc_noux/plugin.cc
@@ -122,7 +122,7 @@ static void _sysio_to_stat_struct(Noux::Sysio const *sysio, struct stat *buf)
 }
 
 
-static int _stat(const char *path, struct stat *buf, bool lstat = false)
+static int _stat(char const *path, struct stat *buf, bool lstat = false)
 {
 	if ((path == NULL) or (buf == NULL)) {
 		errno = EFAULT;
@@ -144,7 +144,7 @@ static int _stat(const char *path, struct stat *buf, bool lstat = false)
 }
 
 
-extern "C" int lstat(const char *path, struct stat *buf) { return _stat(path, buf, true);  }
+extern "C" int lstat(char const *path, struct stat *buf) { return _stat(path, buf, true);  }
 
 
 static bool serialize_string_array(char const * const * array, char *dst, Genode::size_t dst_len)
@@ -166,7 +166,7 @@ static bool serialize_string_array(char const * const * array, char *dst, Genode
 }
 
 
-extern "C" int execve(const char *filename, char *const argv[],
+extern "C" int execve(char const *filename, char *const argv[],
                       char *const envp[])
 {
 	if (verbose) {
@@ -383,6 +383,20 @@ extern "C" pid_t getpid(void)
 }
 
 
+extern "C" int access(char const *pathname, int mode)
+{
+	PDBG("access '%s' (mode=%x) called, not implemented", pathname, mode);
+	return 0;
+}
+
+
+extern "C" int chmod(char const *path, mode_t mode)
+{
+	PDBG("chmod '%s' to 0x%x not implemented", path, mode);
+	return 0;
+}
+
+
 extern "C" pid_t _wait4(pid_t pid, int *status, int options,
                         struct rusage *rusage)
 {
@@ -496,35 +510,42 @@ namespace {
 				_stderr(Libc::file_descriptor_allocator()->alloc(this, noux_context(2), 2))
 			{ }
 
-			bool supports_chdir(const char *)     { return true; }
-			bool supports_open(const char *, int) { return true; }
-			bool supports_stat(const char *)      { return true; }
-			bool supports_pipe()                  { return true; }
+			bool supports_chdir(char const *)                { return true; }
+			bool supports_open(char const *, int)            { return true; }
+			bool supports_stat(char const *)                 { return true; }
+			bool supports_pipe()                             { return true; }
+			bool supports_unlink(char const *)               { return true; }
+			bool supports_rename(const char *, const char *) { return true; }
+			bool supports_mkdir(const char *, mode_t)        { return true; }
 
-			Libc::File_descriptor *open(const char *, int);
+			Libc::File_descriptor *open(char const *, int);
 			ssize_t write(Libc::File_descriptor *, const void *, ::size_t);
 			int close(Libc::File_descriptor *);
 			int dup2(Libc::File_descriptor *, Libc::File_descriptor *);
 			int fstat(Libc::File_descriptor *, struct stat *);
+			int fsync(Libc::File_descriptor *);
 			int fstatfs(Libc::File_descriptor *, struct statfs *);
 			int fcntl(Libc::File_descriptor *, int, long);
 			ssize_t getdirentries(Libc::File_descriptor *, char *, ::size_t, ::off_t *);
 			::off_t lseek(Libc::File_descriptor *, ::off_t offset, int whence);
 			int fchdir(Libc::File_descriptor *);
 			ssize_t read(Libc::File_descriptor *, void *, ::size_t);
-			int stat(const char *, struct stat *);
+			int stat(char const *, struct stat *);
 			int ioctl(Libc::File_descriptor *, int request, char *argp);
 			int pipe(Libc::File_descriptor *pipefd[2]);
+			int unlink(char const *path);
+			int rename(const char *oldpath, const char *newpath);
+			int mkdir(const char *path, mode_t mode);
 	};
 
 
-	int Plugin::stat(const char *path, struct stat *buf)
+	int Plugin::stat(char const *path, struct stat *buf)
 	{
 		return _stat(path, buf, false);
 	}
 
 
-	Libc::File_descriptor *Plugin::open(const char *pathname, int flags)
+	Libc::File_descriptor *Plugin::open(char const *pathname, int flags)
 	{
 		if (Genode::strlen(pathname) + 1 > sizeof(sysio()->open_in.path)) {
 			errno = ENAMETOOLONG;
@@ -549,6 +570,7 @@ namespace {
 
 	int Plugin::fstatfs(Libc::File_descriptor *, struct statfs *buf)
 	{
+		buf->f_flags = MNT_UNION;
 		return 0;
 	}
 
@@ -556,11 +578,6 @@ namespace {
 	ssize_t Plugin::write(Libc::File_descriptor *fd, const void *buf,
 	                      ::size_t count)
 	{
-		if (fd != _stdout && fd != _stderr) {
-			errno = EBADF;
-			return -1;
-		}
-
 		/* remember original len for the return value */
 		int const orig_count = count;
 
@@ -574,7 +591,7 @@ namespace {
 			Genode::memcpy(sysio()->write_in.chunk, src, curr_count);
 
 			if (!noux()->syscall(Noux::Session::SYSCALL_WRITE)) {
-				PERR("write error %d", sysio()->error.general);
+				PERR("write error %d (fd %d)", sysio()->error.general, noux_fd(fd->context));
 			}
 
 			count -= curr_count;
@@ -750,12 +767,52 @@ namespace {
 	}
 
 
+	int Plugin::fsync(Libc::File_descriptor *fd)
+	{
+		PDBG("not implemented");
+		return 0;
+	}
+
+
 	int Plugin::fcntl(Libc::File_descriptor *fd, int cmd, long arg)
 	{
 		/* copy arguments to sysio */
 		sysio()->fcntl_in.fd = noux_fd(fd->context);
 		switch (cmd) {
 
+		case F_DUPFD:
+			{
+				/*
+				 * Allocate free file descriptor locally. Noux FDs are expected
+				 * to correspond one-to-one to libc FDs.
+				 */
+				Libc::File_descriptor *new_fd =
+					Libc::file_descriptor_allocator()->alloc(this, 0);
+
+				new_fd->context = noux_context(new_fd->libc_fd);
+
+				/*
+				 * Use new allocated number as name of file descriptor
+				 * duplicate.
+				 */
+				if (dup2(fd, new_fd)) {
+					PERR("Plugin::fcntl: dup2 unexpectedly failed");
+					errno = EINVAL;
+					return -1;
+				}
+
+				return new_fd->libc_fd;
+			}
+
+		case F_GETFD:
+			/*
+			 * Normally, we would return the file-descriptor flags.
+			 *
+			 * XXX: FD_CLOEXEC not yet supported
+			 */
+			PWRN("fcntl(F_GETFD) not implemented, returning 0");
+			return 0;
+
 		case F_SETFD:
 			sysio()->fcntl_in.cmd      = Noux::Sysio::FCNTL_CMD_SET_FD_FLAGS;
 			sysio()->fcntl_in.long_arg = arg;
@@ -793,10 +850,7 @@ namespace {
 			return -1;
 		}
 
-		unsigned const curr_offset = *basep;
-
 		sysio()->dirent_in.fd = noux_fd(fd->context);
-		sysio()->dirent_in.index = curr_offset / sizeof(struct dirent);
 
 		struct dirent *dirent = (struct dirent *)buf;
 		Genode::memset(dirent, 0, sizeof(struct dirent));
@@ -837,13 +891,29 @@ namespace {
 	::off_t Plugin::lseek(Libc::File_descriptor *fd,
 	                      ::off_t offset, int whence)
 	{
-		PWRN("lseek - not implemented: fd=%d, offset=%ld, whence=%d",
-		     noux_fd(fd->context), (long)offset, whence);
+		sysio()->lseek_in.fd = noux_fd(fd->context);
+		sysio()->lseek_in.offset = offset;
 
-		if (whence == SEEK_SET)
-			return offset;
+		switch (whence) {
+		default:
+		case SEEK_SET: sysio()->lseek_in.whence = Noux::Sysio::LSEEK_SET; break;
+		case SEEK_CUR: sysio()->lseek_in.whence = Noux::Sysio::LSEEK_CUR; break;
+		case SEEK_END: sysio()->lseek_in.whence = Noux::Sysio::LSEEK_END; break;
+		}
 
-		return 0;
+		if (!noux()->syscall(Noux::Session::SYSCALL_LSEEK)) {
+			switch (sysio()->error.general) {
+
+			case Noux::Sysio::ERR_FD_INVALID:
+				errno = EBADF;
+				PERR("dirent: ERR_FD_INVALID");
+				return -1;
+
+			case Noux::Sysio::NUM_GENERAL_ERRORS: return -1;
+			}
+		}
+
+		return sysio()->lseek_out.offset;
 	}
 
 
@@ -859,6 +929,62 @@ namespace {
 		return 0;
 	}
 
+
+	int Plugin::unlink(char const *path)
+	{
+		Genode::strncpy(sysio()->unlink_in.path, path, sizeof(sysio()->unlink_in.path));
+
+		if (!noux()->syscall(Noux::Session::SYSCALL_UNLINK)) {
+			PWRN("unlink syscall failed for path \"%s\"", path);
+			switch (sysio()->error.unlink) {
+			case Noux::Sysio::UNLINK_ERR_NO_ENTRY: errno = ENOENT; break;
+			default:                               errno = EPERM;  break;
+			}
+			return -1;
+		}
+
+		return 0;
+	}
+
+
+	int Plugin::rename(char const *from_path, char const *to_path)
+	{
+		Genode::strncpy(sysio()->rename_in.from_path, from_path, sizeof(sysio()->rename_in.from_path));
+		Genode::strncpy(sysio()->rename_in.to_path,   to_path,   sizeof(sysio()->rename_in.to_path));
+
+		if (!noux()->syscall(Noux::Session::SYSCALL_RENAME)) {
+			PWRN("rename syscall failed for \"%s\" -> \"%s\"", from_path, to_path);
+			switch (sysio()->error.rename) {
+			case Noux::Sysio::RENAME_ERR_NO_ENTRY: errno = ENOENT; break;
+			case Noux::Sysio::RENAME_ERR_CROSS_FS: errno = EXDEV;  break;
+			case Noux::Sysio::RENAME_ERR_NO_PERM:  errno = EPERM;  break;
+			default:                               errno = EPERM;  break;
+			}
+			return -1;
+		}
+
+		return 0;
+	}
+
+	int Plugin::mkdir(const char *path, mode_t mode)
+	{
+		Genode::strncpy(sysio()->mkdir_in.path, path, sizeof(sysio()->mkdir_in.path));
+
+		if (!noux()->syscall(Noux::Session::SYSCALL_MKDIR)) {
+			PWRN("mkdir syscall failed for \"%s\" mode=0x%x", path, (int)mode);
+			switch (sysio()->error.mkdir) {
+			case Noux::Sysio::MKDIR_ERR_EXISTS:        errno = EEXIST;       break;
+			case Noux::Sysio::MKDIR_ERR_NO_ENTRY:      errno = ENOENT;       break;
+			case Noux::Sysio::MKDIR_ERR_NO_SPACE:      errno = ENOSPC;       break;
+			case Noux::Sysio::MKDIR_ERR_NAME_TOO_LONG: errno = ENAMETOOLONG; break;
+			case Noux::Sysio::MKDIR_ERR_NO_PERM:       errno = EPERM;        break;
+			default:                                   errno = EPERM;        break;
+			}
+			return -1;
+		}
+		return 0;
+	}
+
 } /* unnamed namespace */
 
 
diff --git a/ports/src/noux/child.h b/ports/src/noux/child.h
index bf53a822e0..f3946d2ea5 100644
--- a/ports/src/noux/child.h
+++ b/ports/src/noux/child.h
@@ -22,7 +22,7 @@
 
 /* Noux includes */
 #include <file_descriptor_registry.h>
-#include <vfs.h>
+#include <dir_file_system.h>
 #include <signal_dispatcher.h>
 #include <noux_session/capability.h>
 #include <args.h>
@@ -195,7 +195,7 @@ namespace Noux {
 			 */
 			Environment _env;
 
-			Vfs * const _vfs;
+			Dir_file_system * const _root_dir;
 
 			/**
 			 * ELF binary
@@ -266,7 +266,7 @@ namespace Noux {
 			      Family_member    *parent,
 			      int               pid,
 			      Signal_receiver  *sig_rec,
-			      Vfs              *vfs,
+			      Dir_file_system  *root_dir,
 			      Args              const &args,
 			      char const       *env,
 			      char const       *pwd,
@@ -286,9 +286,9 @@ namespace Noux {
 				_resources(name, resources_ep, false),
 				_args(ARGS_DS_SIZE, args),
 				_env(env),
-				_vfs(vfs),
+				_root_dir(root_dir),
 				_binary_ds(forked ? Dataspace_capability()
-				                  : vfs->dataspace_from_file(name)),
+				                  : root_dir->dataspace(name)),
 				_sysio_ds(Genode::env()->ram_session(), SYSIO_DS_SIZE),
 				_sysio(_sysio_ds.local_addr<Sysio>()),
 				_noux_session_cap(Session_capability(_entrypoint.manage(this))),
@@ -313,7 +313,7 @@ namespace Noux {
 
 				_entrypoint.dissolve(this);
 
-				_vfs->release_dataspace_for_file(_child_policy.name(), _binary_ds);
+				_root_dir->release(_child_policy.name(), _binary_ds);
 			}
 
 			void start() { _entrypoint.activate(); }
diff --git a/ports/src/noux/dir_file_system.h b/ports/src/noux/dir_file_system.h
new file mode 100644
index 0000000000..59eee3ca93
--- /dev/null
+++ b/ports/src/noux/dir_file_system.h
@@ -0,0 +1,518 @@
+/*
+ * \brief  Directory file system
+ * \author Norman Feske
+ * \date   2012-04-23
+ */
+
+/*
+ * Copyright (C) 2011-2012 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.
+ */
+
+#ifndef _NOUX__DIR_FILE_SYSTEM_H_
+#define _NOUX__DIR_FILE_SYSTEM_H_
+
+/* Genode includes */
+#include <base/printf.h>
+#include <base/lock.h>
+
+/* Noux includes */
+#include <noux_session/sysio.h>
+#include <tar_file_system.h>
+#include <fs_file_system.h>
+
+namespace Noux {
+
+	class Dir_file_system : public File_system
+	{
+		public:
+
+			enum { MAX_NAME_LEN = 128 };
+
+		private:
+
+			Lock _lock;
+
+			/* pointer to first child file system */
+			File_system *_first_file_system;
+
+			/* add new file system to the list of children */
+			void _append_file_system(File_system *fs)
+			{
+				if (!_first_file_system) {
+					_first_file_system = fs;
+					return;
+				}
+
+				File_system *curr = _first_file_system;
+				while (curr->next)
+					curr = curr->next;
+
+				curr->next = fs;
+			}
+
+			/**
+			 * Directory name
+			 */
+			char _name[MAX_NAME_LEN];
+
+			bool _is_root() const { return _name[0] == 0; }
+
+		public:
+
+			Dir_file_system(Xml_node node) : _first_file_system(0)
+			{
+				/* remember directory name */
+				if (node.has_type("fstab"))
+					_name[0] = 0;
+				else
+					node.attribute("name").value(_name, sizeof(_name));
+
+				for (unsigned i = 0; i < node.num_sub_nodes(); i++) {
+
+					Xml_node sub_node = node.sub_node(i);
+
+					if (sub_node.has_type("tar")) {
+						_append_file_system(new Tar_file_system(sub_node));
+						continue;
+					}
+
+					if (sub_node.has_type("fs")) {
+						_append_file_system(new Fs_file_system(sub_node));
+						continue;
+					}
+
+					/* traverse into <dir> nodes */
+					if (sub_node.has_type("dir")) {
+						_append_file_system(new Dir_file_system(sub_node));
+						continue;
+					}
+
+					{
+						char type_name[64];
+						sub_node.type_name(type_name, sizeof(type_name));
+						PWRN("unknown fstab node type <%s>", type_name);
+					}
+				}
+			}
+
+			/**
+			 * Return portion of the path after the element corresponding to
+			 * the current directory.
+			 */
+			char const *_sub_path(char const *path) const
+			{
+				/* do not strip anything from the path when we are root */
+				if (_is_root())
+					return path;
+
+				/* skip heading slash in path if present */
+				if (path[0] == '/')
+					path++;
+
+				size_t const name_len = strlen(_name);
+				if (strcmp(path, _name, name_len) != 0)
+					return 0;
+				path += name_len;
+
+				/*
+				 * The first characters of the first path element are equal to
+				 * the current directory name. Let's check if the length of the
+				 * first path element matches the name length.
+				 */
+				if (*path != 0 && *path != '/')
+					return 0;
+
+				return path;
+			}
+
+
+			/*********************************
+			 ** Directory-service interface **
+			 *********************************/
+
+			Dataspace_capability dataspace(char const *path)
+			{
+				path = _sub_path(path);
+				if (!path)
+					return Dataspace_capability();
+
+				/*
+				 * Query sub file systems for dataspace using the path local to
+				 * the respective file system
+				 */
+				File_system *fs = _first_file_system;
+				for (; fs; fs = fs->next) {
+					Dataspace_capability ds = fs->dataspace(path);
+					if (ds.valid())
+						return ds;
+				}
+
+				return Dataspace_capability();
+			}
+
+			void release(char const *path, Dataspace_capability ds_cap)
+			{
+				path = _sub_path(path);
+				if (!path)
+					return;
+
+				for (File_system *fs = _first_file_system; fs; fs = fs->next)
+					fs->release(path, ds_cap);
+			}
+
+			bool stat(Sysio *sysio, char const *path)
+			{
+				path = _sub_path(path);
+
+				/* path does not match directory name */
+				if (!path) {
+					sysio->error.stat = Sysio::STAT_ERR_NO_ENTRY;
+					return false;
+				}
+
+				/*
+				 * If path equals directory name, return information about the
+				 * current directory.
+				 */
+				if (strlen(path) == 0) {
+					sysio->stat_out.st.size = 0;
+					sysio->stat_out.st.mode = Sysio::STAT_MODE_DIRECTORY | 0755;
+					sysio->stat_out.st.uid  = 0;
+					sysio->stat_out.st.gid  = 0;
+					return true;
+				}
+
+				/*
+				 * The given path refers to one of our sub directories.
+				 * Propagate the request into our file systems.
+				 */
+				for (File_system *fs = _first_file_system; fs; fs = fs->next)
+					if (fs->stat(sysio, path))
+						return true;
+
+				/* none of our file systems felt responsible for the path */
+				sysio->error.stat = Sysio::STAT_ERR_NO_ENTRY;
+				return false;
+			}
+
+			/**
+			 * The 'path' is relative to the child file systems.
+			 */
+			bool _dirent_of_file_systems(Sysio *sysio, char const *path, off_t index)
+			{
+				int base = 0;
+				for (File_system *fs = _first_file_system; fs; fs = fs->next) {
+
+					/*
+					 * Determine number of matching directory entries within
+					 * the current file system.
+					 */
+					int const fs_num_dirent = fs->num_dirent(path);
+
+					/*
+					 * Query directory entry if index lies with the file
+					 * system.
+					 */
+					if (index - base < fs_num_dirent) {
+						index = index - base;
+						bool const res = fs->dirent(sysio, path, index);
+						sysio->dirent_out.entry.fileno += base;
+						return res;
+					}
+
+					/* adjust base index for next file system */
+					base += fs_num_dirent;
+				}
+
+				sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_END;
+				return true;
+			}
+
+			bool _dirent_of_this_dir_node(Sysio *sysio, off_t index)
+			{
+				if (index == 0) {
+					strncpy(sysio->dirent_out.entry.name, _name,
+					        sizeof(sysio->dirent_out.entry.name));
+
+					sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_DIRECTORY;
+					sysio->dirent_out.entry.fileno = (unsigned)this;
+				} else
+					sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_END;
+				return true;
+			}
+
+			bool dirent(Sysio *sysio, char const *path, off_t index)
+			{
+				Lock::Guard guard(_lock);
+
+				if (_is_root()) {
+					return _dirent_of_file_systems(sysio, path, index);
+				} else {
+
+					if (strcmp(path, "/") == 0)
+						return _dirent_of_this_dir_node(sysio, index);
+
+					/* path contains at least one element */
+
+					/* remove current element from path */
+					path = _sub_path(path);
+					if (path) {
+						return _dirent_of_file_systems(sysio, path, index);
+
+					} else {
+						/* path does not lie within our tree */
+						return false;
+					}
+				}
+			}
+
+			/*
+			 * Accumulate number of directory entries that match in any of
+			 * our sub file systems.
+			 */
+			size_t _sum_dirents_of_file_systems(char const *path)
+			{
+				size_t cnt = 0;
+				for (File_system *fs = _first_file_system; fs; fs = fs->next) {
+					cnt += fs->num_dirent(path);
+				}
+				return cnt;
+			}
+
+			size_t num_dirent(char const *path)
+			{
+				Lock::Guard guard(_lock);
+
+				if (_is_root()) {
+					return _sum_dirents_of_file_systems(path);
+
+				} else {
+
+					if (strcmp(path, "/") == 0)
+						return 1;
+
+					/*
+					 * The path contains at least one element. Remove current
+					 * element from path.
+					 */
+					path = _sub_path(path);
+
+					/*
+					 * If the resulting 'path' is non-NULL, the path lies
+					 * within our tree. In this case, determine the sum of
+					 * matching dirents of all our file systems. Otherwise,
+					 * the specified path lies outside our directory node.
+					 */
+					return path ? _sum_dirents_of_file_systems(path) : 0;
+				}
+			}
+
+			bool is_directory(char const *path)
+			{
+				Lock::Guard guard(_lock);
+
+				path = _sub_path(path);
+				if (!path)
+					return false;
+
+				if (strlen(path) == 0)
+					return true;
+
+				for (File_system *fs = _first_file_system; fs; fs = fs->next)
+					if (fs->is_directory(path))
+						return true;
+
+				return false;
+			}
+
+			char const *leaf_path(char const *path)
+			{
+				Lock::Guard guard(_lock);
+
+				path = _sub_path(path);
+				if (!path)
+					return 0;
+
+				if (strlen(path) == 0)
+					return path;
+
+				for (File_system *fs = _first_file_system; fs; fs = fs->next) {
+					char const *leaf_path = fs->leaf_path(path);
+					if (leaf_path)
+						return leaf_path;
+				}
+
+				return 0;
+			}
+
+			Vfs_handle *open(Sysio *sysio, char const *path)
+			{
+				/*
+				 * If 'path' is a directory, we create a 'Vfs_handle'
+				 * for the root directory so that subsequent 'dirent' calls
+				 * are subjected to the stacked file-system layout.
+				 */
+				if (is_directory(path))
+					return new (env()->heap()) Vfs_handle(this, this, 0);
+
+				/*
+				 * If 'path' refers to a non-directory node, create a
+				 * 'Vfs_handle' local to the file system that provides the
+				 * file.
+				 */
+				Lock::Guard guard(_lock);
+
+				path = _sub_path(path);
+
+				/* check if path does not match directory name */
+				if (!path)
+					return 0;
+
+				/* path equals directory name */
+				if (strlen(path) == 0)
+					return new (env()->heap()) Vfs_handle(this, this, 0);
+
+				/* path refers to any of our sub file systems */
+				for (File_system *fs = _first_file_system; fs; fs = fs->next) {
+					Vfs_handle *vfs_handle = fs->open(sysio, path);
+					if (vfs_handle)
+						return vfs_handle;
+				}
+
+				/* path does not match any existing file or directory */
+				return 0;
+			}
+
+			bool unlink(Sysio *sysio, char const *path)
+			{
+				path = _sub_path(path);
+
+				/* path does not match directory name */
+				if (!path) {
+					sysio->error.unlink = Sysio::UNLINK_ERR_NO_ENTRY;
+					return false;
+				}
+
+				/*
+				 * Prevent unlinking if path equals directory name defined
+				 * via the static fstab configuration.
+				 */
+				if (strlen(path) == 0) {
+					sysio->error.unlink = Sysio::UNLINK_ERR_NO_PERM;
+					return false;
+				}
+
+				/*
+				 * The given path refers to at least one of our sub
+				 * directories. Propagate the request into all of our file
+				 * systems. If at least one unlink operation succeeded, we
+				 * return success.
+				 */
+				bool unlink_ret = false;
+				Sysio::Unlink_error error = Sysio::UNLINK_ERR_NO_ENTRY;
+				for (File_system *fs = _first_file_system; fs; fs = fs->next)
+					if (fs->unlink(sysio, path)) {
+						unlink_ret = true;
+					} else {
+						/*
+						 * Keep the most meaningful error code. When using
+						 * stacked file systems, most child file systems will
+						 * eventually return 'UNLINK_ERR_NO_ENTRY'. If any of
+						 * those file systems has anything more interesting to
+						 * tell (in particular 'UNLINK_ERR_NO_PERM'), return
+						 * this information.
+						 */
+						if (sysio->error.unlink != Sysio::UNLINK_ERR_NO_ENTRY)
+							error = sysio->error.unlink;
+					}
+
+				sysio->error.unlink = error;
+				return unlink_ret;
+			}
+
+			bool rename(Sysio *sysio, char const *from_path, char const *to_path)
+			{
+				from_path = _sub_path(from_path);
+
+				/* path does not match directory name */
+				if (!from_path) {
+					sysio->error.rename = Sysio::RENAME_ERR_NO_ENTRY;
+					return false;
+				}
+
+				/*
+				 * Prevent renaming if path equals directory name defined
+				 * via the static fstab configuration.
+				 */
+				if (strlen(from_path) == 0) {
+					sysio->error.rename = Sysio::RENAME_ERR_NO_PERM;
+					return false;
+				}
+
+				/*
+				 * Check if destination path resides within the same file
+				 * system instance as the source path.
+				 */
+				to_path = _sub_path(to_path);
+				if (!to_path) {
+					sysio->error.rename = Sysio::RENAME_ERR_CROSS_FS;
+					return false;
+				}
+
+				/* path refers to any of our sub file systems */
+				for (File_system *fs = _first_file_system; fs; fs = fs->next)
+					if (fs->rename(sysio, from_path, to_path))
+						return true;
+
+				/* none of our file systems could successfully rename the path */
+				return false;
+			}
+
+			bool mkdir(Sysio *sysio, char const *path)
+			{
+				path = _sub_path(path);
+
+				/* path does not match directory name */
+				if (!path) {
+					sysio->error.mkdir = Sysio::MKDIR_ERR_NO_ENTRY;
+					return false;
+				}
+
+				/*
+				 * Prevent mkdir of path that equals directory name defined
+				 * via the static fstab configuration.
+				 */
+				if (strlen(path) == 0) {
+					sysio->error.mkdir = Sysio::MKDIR_ERR_EXISTS;
+					return false;
+				}
+
+				/* path refers to any of our sub file systems */
+				for (File_system *fs = _first_file_system; fs; fs = fs->next)
+					if (fs->mkdir(sysio, path))
+						return true;
+
+				/* none of our file systems could create the directory */
+				return false;
+			}
+
+			/***************************
+			 ** File_system interface **
+			 ***************************/
+
+			char const *name() const { return "dir"; }
+
+
+			/********************************
+			 ** File I/O service interface **
+			 ********************************/
+
+			bool write(Sysio *sysio, Vfs_handle *handle) { return false; }
+			bool read(Sysio *sysio, Vfs_handle *vfs_handle) { return false; }
+	};
+}
+
+#endif /* _NOUX__DIR_FILE_SYSTEM_H_ */
diff --git a/ports/src/noux/directory_service.h b/ports/src/noux/directory_service.h
index 64b808f810..62771340e4 100644
--- a/ports/src/noux/directory_service.h
+++ b/ports/src/noux/directory_service.h
@@ -30,14 +30,25 @@ namespace Noux {
 	struct Directory_service
 	{
 		virtual Dataspace_capability dataspace(char const *path) = 0;
-		virtual void release(Dataspace_capability) = 0;
+		virtual void release(char const *path, Dataspace_capability) = 0;
 
 		virtual Vfs_handle *open(Sysio *sysio, char const *path) = 0;
 
 		virtual bool   stat(Sysio *sysio, char const *path) = 0;
-		virtual bool dirent(Sysio *sysio, char const *path) = 0;
+		virtual bool dirent(Sysio *sysio, char const *path, off_t index) = 0;
+		virtual bool unlink(Sysio *sysio, char const *path) = 0;
+		virtual bool rename(Sysio *sysio, char const *from_path,
+		                                  char const *to_path) = 0;
+		virtual bool  mkdir(Sysio *sysio, char const *path) = 0;
 
-		virtual void close(Vfs_handle *handle) = 0;
+		/**
+		 * Return number of directory entries located at given path
+		 */
+		virtual size_t num_dirent(char const *path) = 0;
+
+		virtual bool is_directory(char const *path) = 0;
+
+		virtual char const *leaf_path(char const *path) = 0;
 	};
 }
 
diff --git a/ports/src/noux/file_system.h b/ports/src/noux/file_system.h
index 7ae9adb8f5..376a26bbfd 100644
--- a/ports/src/noux/file_system.h
+++ b/ports/src/noux/file_system.h
@@ -5,7 +5,7 @@
  */
 
 /*
- * Copyright (C) 2011-2012 gENODe Labs GmbH
+ * Copyright (C) 2011-2012 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.
@@ -22,71 +22,26 @@
 /* Noux includes */
 #include <directory_service.h>
 #include <file_io_service.h>
+#include <path.h>
 #include <noux_session/sysio.h>
 
 namespace Noux {
 
-	class File_system : public Directory_service, public File_io_service,
-	                    public List<File_system>::Element
+	struct File_system : Directory_service, File_io_service
 	{
-		private:
+		/**
+		 * Our next sibling within the same 'Dir_file_system'
+		 */
+		struct File_system *next;
 
-			char _mount_point[Sysio::MAX_PATH_LEN];
+		File_system() : next(0) { }
 
-			/**
-			 * Strip any number of trailing slashes
-			 */
-			void _strip_trailing_slashes_from_mount_point()
-			{
-				size_t len;
-				while ((len = strlen(_mount_point)) && (_mount_point[len - 1] == '/'))
-					_mount_point[len - 1] = 0;
-			}
-
-		public:
-
-			File_system(Xml_node config)
-			{
-				enum { TYPE_MAX_LEN = 64 };
-				char type[TYPE_MAX_LEN];
-				config.type_name(type, sizeof(type));
-
-				config.attribute("at").value(_mount_point, sizeof(_mount_point));
-				_strip_trailing_slashes_from_mount_point();
-
-				PINF("created %s file system at \"%s\"", type, _mount_point);
-			}
-
-			File_system(char const *mount_point)
-			{
-				strncpy(_mount_point, mount_point, sizeof(_mount_point));
-				_strip_trailing_slashes_from_mount_point();
-			}
-
-			/**
-			 * Return file-system-local path for the given global path
-			 *
-			 * This function checks if the global path lies within the mount
-			 * point of the file system. If yes, it returns the sub string of
-			 * the global path that corresponds to the path relative to the
-			 * file system.
-			 *
-			 * \return pointer to file-system relative path name within
-			 *         the 'global_path' string, or
-			 *         0 if global path lies outside the file system
-			 */
-			char const *local_path(char const *global_path)
-			{
-				size_t const mount_point_len = strlen(_mount_point);
-
-				if (strlen(global_path) < mount_point_len)
-					return 0;
-
-				if (strcmp(global_path, _mount_point, mount_point_len))
-					return 0;
-
-				return global_path + mount_point_len;
-			}
+		/**
+		 * Return name of file system
+		 *
+		 * This function is used for debugging only.
+		 */
+		virtual char const *name() const = 0;
 	};
 }
 
diff --git a/ports/src/noux/fs_file_system.h b/ports/src/noux/fs_file_system.h
new file mode 100644
index 0000000000..350ae769c6
--- /dev/null
+++ b/ports/src/noux/fs_file_system.h
@@ -0,0 +1,451 @@
+/*
+ * \brief  Adapter from Genode 'File_system' session to Noux file system
+ * \author Norman Feske
+ * \date   2011-02-17
+ */
+
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#ifndef _NOUX__FS_FILE_SYSTEM_H_
+#define _NOUX__FS_FILE_SYSTEM_H_
+
+/* Genode includes */
+#include <base/allocator_avl.h>
+#include <base/env.h>
+
+/* Noux includes */
+#include <noux_session/sysio.h>
+#include <file_system_session/connection.h>
+
+namespace Noux {
+
+	class Fs_file_system : public File_system
+	{
+		private:
+
+			Lock _lock;
+
+			Allocator_avl _fs_packet_alloc;
+
+			struct Label
+			{
+				enum { LABEL_MAX_LEN = 64 };
+				char string[LABEL_MAX_LEN];
+
+				Label(Xml_node config)
+				{
+					string[0] = 0;
+					try { config.attribute("label").value(string, sizeof(string)); }
+					catch (...) { }
+				}
+			} _label;
+
+			::File_system::Connection _fs;
+
+			class Fs_vfs_handle : public Vfs_handle
+			{
+				private:
+
+					::File_system::File_handle const _handle;
+
+				public:
+
+					Fs_vfs_handle(File_system *fs, int status_flags,
+					              ::File_system::File_handle handle)
+					: Vfs_handle(fs, fs, status_flags), _handle(handle)
+					{ }
+
+					~Fs_vfs_handle()
+					{
+						Fs_file_system *fs = static_cast<Fs_file_system *>(ds());
+						fs->_fs.close(_handle);
+					}
+
+					::File_system::File_handle file_handle() const { return _handle; }
+			};
+
+			/**
+			 * Helper for managing the lifetime of temporary open node handles
+			 */
+			struct Fs_handle_guard
+			{
+				::File_system::Session     &_fs;
+				::File_system::Node_handle  _handle;
+
+				Fs_handle_guard(::File_system::Session &fs,
+				                ::File_system::Node_handle handle)
+				: _fs(fs), _handle(handle) { }
+
+				~Fs_handle_guard() { _fs.close(_handle); }
+			};
+
+		public:
+
+			/*
+			 * XXX read label from config
+			 */
+			Fs_file_system(Xml_node config)
+			:
+				_fs_packet_alloc(env()->heap()),
+				_label(config),
+				_fs(_fs_packet_alloc, 128*1024, _label.string)
+			{ }
+
+
+			/*********************************
+			 ** Directory-service interface **
+			 *********************************/
+
+			Dataspace_capability dataspace(char const *path)
+			{
+				return Dataspace_capability();
+			}
+
+			void release(char const *path, Dataspace_capability ds_cap)
+			{
+			}
+
+			bool stat(Sysio *sysio, char const *path)
+			{
+				::File_system::Status status;
+
+				try {
+					::File_system::Node_handle node = _fs.node(path);
+					Fs_handle_guard node_guard(_fs, node);
+					status = _fs.status(node);
+				} catch (...) {
+					PWRN("stat failed for path '%s'", path);
+					return false;
+				}
+
+				sysio->stat_out.st.size = status.size;
+
+				sysio->stat_out.st.mode = Sysio::STAT_MODE_FILE | 0777;
+
+				if (status.is_symlink())
+					sysio->stat_out.st.mode = Sysio::STAT_MODE_SYMLINK | 0777;
+
+				if (status.is_directory())
+					sysio->stat_out.st.mode = Sysio::STAT_MODE_DIRECTORY | 0777;
+
+				sysio->stat_out.st.uid  = 0;
+				sysio->stat_out.st.gid  = 0;
+				return true;
+			}
+
+			bool dirent(Sysio *sysio, char const *path, off_t index)
+			{
+				Lock::Guard guard(_lock);
+
+				::File_system::Session::Tx::Source &source = *_fs.tx();
+
+				if (strcmp(path, "") == 0)
+					path = "/";
+
+				::File_system::Dir_handle dir_handle = _fs.dir(path, false);
+				Fs_handle_guard dir_guard(_fs, dir_handle);
+
+				enum { DIRENT_SIZE = sizeof(::File_system::Directory_entry) };
+
+				::File_system::Packet_descriptor
+					packet(source.alloc_packet(DIRENT_SIZE),
+					       0,
+					       dir_handle,
+					       ::File_system::Packet_descriptor::READ,
+					       DIRENT_SIZE,
+					       index*DIRENT_SIZE);
+
+				/* pass packet to server side */
+				source.submit_packet(packet);
+				source.get_acked_packet();
+
+				/*
+				 * XXX check if acked packet belongs to request,
+				 *     needed for thread safety
+				 */
+
+				typedef ::File_system::Directory_entry Directory_entry;
+
+				/* copy-out payload into destination buffer */
+				Directory_entry const *entry =
+					(Directory_entry *)source.packet_content(packet);
+
+				Sysio::Dirent_type type;
+				switch (entry->type) {
+				case Directory_entry::TYPE_DIRECTORY: type = Sysio::DIRENT_TYPE_DIRECTORY;  break;
+				case Directory_entry::TYPE_FILE:      type = Sysio::DIRENT_TYPE_FILE;       break;
+				case Directory_entry::TYPE_SYMLINK:   type = Sysio::DIRENT_TYPE_SYMLINK;    break;
+				}
+
+				sysio->dirent_out.entry.type   = type;
+				sysio->dirent_out.entry.fileno = index + 1;
+
+				strncpy(sysio->dirent_out.entry.name, entry->name,
+				        sizeof(sysio->dirent_out.entry.name));
+
+				source.release_packet(packet);
+
+				return true;
+			}
+
+			bool unlink(Sysio *sysio, char const *path)
+			{
+				Lock::Guard guard(_lock);
+
+				Absolute_path dir_path(path);
+				dir_path.strip_last_element();
+
+				Absolute_path file_name(path);
+				file_name.keep_only_last_element();
+
+				try {
+					::File_system::Dir_handle dir = _fs.dir(dir_path.base(), false);
+					Fs_handle_guard dir_guard(_fs, dir);
+
+					_fs.unlink(dir, file_name.base() + 1);
+
+				} catch (...) {
+					sysio->error.unlink = Sysio::UNLINK_ERR_NO_ENTRY;
+					return false;
+				}
+				return true;
+			}
+
+			bool rename(Sysio *sysio, char const *from_path, char const *to_path)
+			{
+				Absolute_path from_dir_path(from_path);
+				from_dir_path.strip_last_element();
+
+				Absolute_path from_file_name(from_path);
+				from_file_name.keep_only_last_element();
+
+				Absolute_path to_dir_path(to_path);
+				to_dir_path.strip_last_element();
+
+				Absolute_path to_file_name(to_path);
+				to_file_name.keep_only_last_element();
+
+				try {
+					::File_system::Dir_handle from_dir = _fs.dir(from_dir_path.base(), false);
+					Fs_handle_guard from_dir_guard(_fs, from_dir);
+
+					::File_system::Dir_handle to_dir = _fs.dir(to_dir_path.base(), false);
+					Fs_handle_guard to_dir_guard(_fs, to_dir);
+
+					_fs.move(from_dir, from_file_name.base() + 1,
+					         to_dir,   to_file_name.base() + 1);
+
+				} catch (...) {
+					sysio->error.unlink = Sysio::UNLINK_ERR_NO_ENTRY;
+					return false;
+				}
+				return true;
+			}
+
+			bool mkdir(Sysio *sysio, char const *path)
+			{
+				/*
+				 * Canonicalize path (i.e., path must start with '/')
+				 */
+				Absolute_path abs_path(path);
+
+				Sysio::Mkdir_error error = Sysio::MKDIR_ERR_NO_PERM;
+				try {
+					_fs.dir(abs_path.base(), true);
+					return true;
+				}
+				catch (::File_system::Permission_denied)   { error = Sysio::MKDIR_ERR_NO_PERM; }
+				catch (::File_system::Node_already_exists) { error = Sysio::MKDIR_ERR_EXISTS; }
+				catch (::File_system::Lookup_failed)       { error = Sysio::MKDIR_ERR_NO_ENTRY; }
+				catch (::File_system::Name_too_long)       { error = Sysio::MKDIR_ERR_NAME_TOO_LONG; }
+				catch (::File_system::No_space)            { error = Sysio::MKDIR_ERR_NO_SPACE; }
+
+				sysio->error.mkdir = error;
+				return false;
+			}
+
+			size_t num_dirent(char const *path)
+			{
+				if (strcmp(path, "") == 0)
+					path = "/";
+
+				/*
+				 * XXX handle exceptions
+				 */
+				::File_system::Node_handle node = _fs.node(path);
+				Fs_handle_guard node_guard(_fs, node);
+
+				::File_system::Status status = _fs.status(node);
+
+				return status.size / sizeof(::File_system::Directory_entry);
+			}
+
+			bool is_directory(char const *path)
+			{
+				try {
+					::File_system::Node_handle node = _fs.node(path);
+					Fs_handle_guard node_guard(_fs, node);
+
+					::File_system::Status status = _fs.status(node);
+
+					return status.is_directory();
+				}
+				catch (...) { return false; }
+			}
+
+			char const *leaf_path(char const *path)
+			{
+				/* check if node at path exists within file system */
+				try {
+					::File_system::Node_handle node = _fs.node(path);
+					_fs.close(node);
+				}
+				catch (...) {
+					return 0; }
+
+				return path;
+			}
+
+			Vfs_handle *open(Sysio *sysio, char const *path)
+			{
+				Lock::Guard guard(_lock);
+
+				Absolute_path dir_path(path);
+				dir_path.strip_last_element();
+
+				Absolute_path file_name(path);
+				file_name.keep_only_last_element();
+
+				::File_system::Mode mode;
+
+				switch (sysio->open_in.mode & Sysio::OPEN_MODE_ACCMODE) {
+				default:                      mode = ::File_system::STAT_ONLY;  break;
+				case Sysio::OPEN_MODE_RDONLY: mode = ::File_system::READ_ONLY;  break;
+				case Sysio::OPEN_MODE_WRONLY: mode = ::File_system::WRITE_ONLY; break;
+				case Sysio::OPEN_MODE_RDWR:   mode = ::File_system::READ_WRITE; break;
+				}
+
+				bool const create = sysio->open_in.mode & Sysio::OPEN_MODE_CREATE;
+
+				if (create)
+					PDBG("creation of file %s requested", file_name.base());
+
+				::File_system::Dir_handle dir = _fs.dir(dir_path.base(), false);
+				Fs_handle_guard dir_guard(_fs, dir);
+
+				Sysio::Open_error error = Sysio::OPEN_ERR_UNACCESSIBLE;
+				try {
+					::File_system::File_handle file = _fs.file(dir, file_name.base() + 1,
+					                                           mode, create);
+					return new (env()->heap()) Fs_vfs_handle(this, 0, file);
+				}
+				catch (::File_system::Permission_denied) {
+					error = Sysio::OPEN_ERR_NO_PERM; }
+				catch (::File_system::Invalid_handle) {
+					error = Sysio::OPEN_ERR_NO_PERM; }
+
+				sysio->error.open = error;
+
+				return 0;
+			}
+
+
+			/***************************
+			 ** File_system interface **
+			 ***************************/
+
+			char const *name() const { return "fs"; }
+
+
+			/********************************
+			 ** File I/O service interface **
+			 ********************************/
+
+			bool write(Sysio *sysio, Vfs_handle *vfs_handle)
+			{
+				Fs_vfs_handle const *handle = static_cast<Fs_vfs_handle *>(vfs_handle);
+
+				::File_system::Session::Tx::Source &source = *_fs.tx();
+
+				size_t const max_packet_size = source.bulk_buffer_size() / 2;
+				size_t const count = min(max_packet_size,
+				                         min(sizeof(sysio->write_in.chunk),
+				                             sysio->write_in.count));
+
+				::File_system::Packet_descriptor
+					packet(source.alloc_packet(count),
+					       0,
+					       handle->file_handle(),
+					       ::File_system::Packet_descriptor::WRITE,
+					       count,
+					       handle->seek());
+
+				memcpy(source.packet_content(packet), sysio->write_in.chunk, count);
+
+				/* pass packet to server side */
+				source.submit_packet(packet);
+				source.get_acked_packet();
+
+				sysio->write_out.count = count;
+
+				/*
+				 * XXX check if acked packet belongs to request,
+				 *     needed for thread safety
+				 */
+
+				source.release_packet(packet);
+
+				return true;
+			}
+
+			bool read(Sysio *sysio, Vfs_handle *vfs_handle)
+			{
+				Fs_vfs_handle const *handle = static_cast<Fs_vfs_handle *>(vfs_handle);
+
+				::File_system::Status status = _fs.status(handle->file_handle());
+				size_t const file_size = status.size;
+
+				size_t const file_bytes_left = file_size >= handle->seek()
+				                             ? file_size  - handle->seek() : 0;
+
+				::File_system::Session::Tx::Source &source = *_fs.tx();
+
+				size_t const max_packet_size = source.bulk_buffer_size() / 2;
+				size_t const count = min(max_packet_size,
+				                         min(file_bytes_left,
+				                             min(sizeof(sysio->read_out.chunk),
+				                                 sysio->read_in.count)));
+
+				::File_system::Packet_descriptor
+					packet(source.alloc_packet(count),
+					       0,
+					       handle->file_handle(),
+					       ::File_system::Packet_descriptor::READ,
+					       count,
+					       handle->seek());
+
+				/* pass packet to server side */
+				source.submit_packet(packet);
+				source.get_acked_packet();
+
+				memcpy(sysio->read_out.chunk, source.packet_content(packet), count);
+
+				sysio->read_out.count = count;
+
+				/*
+				 * XXX check if acked packet belongs to request,
+				 *     needed for thread safety
+				 */
+
+				source.release_packet(packet);
+				return true;
+			}
+	};
+}
+
+#endif /* _NOUX__FS_FILE_SYSTEM_H_ */
diff --git a/ports/src/noux/io_channel.h b/ports/src/noux/io_channel.h
index 2ecec4cbe9..9f5b3e7bf2 100644
--- a/ports/src/noux/io_channel.h
+++ b/ports/src/noux/io_channel.h
@@ -44,6 +44,10 @@ namespace Noux {
 
 		public:
 
+			bool close_on_execve;
+
+			Io_channel() : close_on_execve(false) { }
+
 			virtual ~Io_channel() { }
 
 			virtual bool  write(Sysio *sysio, size_t &count) { return false; }
@@ -53,6 +57,7 @@ namespace Noux {
 			virtual bool fchdir(Sysio *sysio, Pwd *pwd)      { return false; }
 			virtual bool dirent(Sysio *sysio)                { return false; }
 			virtual bool  ioctl(Sysio *sysio)                { return false; }
+			virtual bool  lseek(Sysio *sysio)                { return false; }
 
 			/**
 			 * Return true if an unblocking condition of the channel is satisfied
diff --git a/ports/src/noux/main.cc b/ports/src/noux/main.cc
index 9a387201f8..10b0888567 100644
--- a/ports/src/noux/main.cc
+++ b/ports/src/noux/main.cc
@@ -34,8 +34,8 @@
  * ;- import env into child (execve and fork)
  * ;- shell
  * - debug 'find'
- * - stacked file system infrastructure
- * - TMP file system
+ * ;- stacked file system infrastructure
+ * ;- TMP file system
  * ;- RAM service using a common quota pool
  */
 
@@ -49,8 +49,7 @@
 #include <terminal_io_channel.h>
 #include <dummy_input_io_channel.h>
 #include <pipe_io_channel.h>
-#include <root_file_system.h>
-#include <tar_file_system.h>
+#include <dir_file_system.h>
 
 
 enum { verbose_syscall = false };
@@ -121,8 +120,8 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
 		case SYSCALL_STAT:
 		case SYSCALL_LSTAT: /* XXX implement difference between 'lstat' and 'stat' */
 
-			return _vfs->stat(_sysio, Absolute_path(_sysio->stat_in.path,
-			                                        _env.pwd()).base());
+			return _root_dir->stat(_sysio, Absolute_path(_sysio->stat_in.path,
+			                                             _env.pwd()).base());
 
 		case SYSCALL_FSTAT:
 
@@ -130,28 +129,41 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
 
 		case SYSCALL_FCNTL:
 
+			if (_sysio->fcntl_in.cmd == Sysio::FCNTL_CMD_SET_FD_FLAGS) {
+
+				/* we assume that there is only the close-on-execve flag */
+				_lookup_channel(_sysio->fcntl_in.fd)->close_on_execve =
+					!!_sysio->fcntl_in.long_arg;
+				return true;
+			}
+
 			return _lookup_channel(_sysio->fcntl_in.fd)->fcntl(_sysio);
 
 		case SYSCALL_OPEN:
 			{
 				Absolute_path absolute_path(_sysio->open_in.path, _env.pwd());
 
-				PINF("open pwd=%s path=%s", _env.pwd(), _sysio->open_in.path);
-
-				/* remember mode only for debug output */
-				int const mode = _sysio->open_in.mode;
-
-				Vfs_handle *vfs_handle = _vfs->open(_sysio, absolute_path.base());
+				Vfs_handle *vfs_handle = _root_dir->open(_sysio, absolute_path.base());
 				if (!vfs_handle)
 					return false;
 
-				Shared_pointer<Io_channel> channel(new Vfs_io_channel(absolute_path.base(), _vfs, vfs_handle),
-				                                   Genode::env()->heap());
+				char const *leaf_path = _root_dir->leaf_path(absolute_path.base());
+
+				/*
+				 * File descriptors of opened directories are handled by
+				 * '_root_dir'. In this case, we use the absolute path as leaf
+				 * path because path operations always refer to the global
+				 * root.
+				 */
+				if (vfs_handle->ds() == _root_dir)
+					leaf_path = absolute_path.base();
+
+				Shared_pointer<Io_channel>
+					channel(new Vfs_io_channel(absolute_path.base(),
+					                           leaf_path, _root_dir, vfs_handle),
+					        Genode::env()->heap());
 
 				_sysio->open_out.fd = add_io_channel(channel);
-
-				PINF("open fd %d for \"%s\" with mode %o",
-				     _sysio->open_out.fd, absolute_path.base(), mode);
 				return true;
 			}
 
@@ -165,6 +177,10 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
 
 			return _lookup_channel(_sysio->ioctl_in.fd)->ioctl(_sysio);
 
+		case SYSCALL_LSEEK:
+
+			return _lookup_channel(_sysio->lseek_in.fd)->lseek(_sysio);
+
 		case SYSCALL_DIRENT:
 
 			return _lookup_channel(_sysio->dirent_in.fd)->dirent(_sysio);
@@ -175,7 +191,7 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
 
 		case SYSCALL_EXECVE:
 			{
-				char const *filename = _sysio->execve_in.filename;
+				Absolute_path absolute_path(_sysio->execve_in.filename, _env.pwd());
 
 				/*
 				 * Deserialize environment variable buffer into a
@@ -208,11 +224,11 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
 					j += strlen(src);
 				}
 
-				Child *child = new Child(filename,
+				Child *child = new Child(absolute_path.base(),
 				                         parent(),
 				                         pid(),
 				                         _sig_rec,
-				                         _vfs,
+				                         _root_dir,
 				                         Args(_sysio->execve_in.args,
 				                              sizeof(_sysio->execve_in.args)),
 				                         env,
@@ -350,7 +366,7 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
 				                         this,
 				                         new_pid,
 				                         _sig_rec,
-				                         _vfs,
+				                         _root_dir,
 				                         _args,
 				                         _env.env(),
 				                         _env.pwd(),
@@ -425,6 +441,23 @@ bool Noux::Child::syscall(Noux::Session::Syscall sc)
 				return true;
 			}
 
+		case SYSCALL_UNLINK:
+
+			return _root_dir->unlink(_sysio, Absolute_path(_sysio->unlink_in.path,
+			                                               _env.pwd()).base());
+
+		case SYSCALL_RENAME:
+
+			return _root_dir->rename(_sysio, Absolute_path(_sysio->rename_in.from_path,
+			                                               _env.pwd()).base(),
+			                                 Absolute_path(_sysio->rename_in.to_path,
+			                                               _env.pwd()).base());
+
+		case SYSCALL_MKDIR:
+
+			return _root_dir->mkdir(_sysio, Absolute_path(_sysio->mkdir_in.path,
+			                                              _env.pwd()).base());
+
 		case SYSCALL_INVALID: break;
 		}
 	}
@@ -568,15 +601,8 @@ int main(int argc, char **argv)
 	static Genode::Cap_connection cap;
 
 	/* initialize virtual file system */
-	static Vfs vfs;
-
-	try {
-		Genode::Xml_node fs = Genode::config()->xml_node().sub_node("fstab").sub_node();
-		for (; ; fs = fs.next()) {
-			if (fs.has_type("tar"))
-				vfs.add_file_system(new Tar_file_system(fs));
-		}
-	} catch (Genode::Xml_node::Nonexistent_sub_node) { }
+	static Dir_file_system
+		root_dir(config()->xml_node().sub_node("fstab"));
 
 	/*
 	 * Entrypoint used to virtualize child resources such as RAM, RM
@@ -591,7 +617,7 @@ int main(int argc, char **argv)
 	                             0,
 	                             pid_allocator()->alloc(),
 	                             &sig_rec,
-	                             &vfs,
+	                             &root_dir,
 	                             args_of_init_process(),
 	                             env_string_of_init_process(),
 	                             "/",
diff --git a/ports/src/noux/path.h b/ports/src/noux/path.h
index af483ebcf0..dcd99d8b71 100644
--- a/ports/src/noux/path.h
+++ b/ports/src/noux/path.h
@@ -59,9 +59,10 @@ namespace Noux {
 					path[i] = 0;
 			}
 
-			static char const *last_element(char const *path)
+			template <typename T>
+			static T *last_element(T *path)
 			{
-				char const *result = path;
+				T *result = path;
 				for (; *path; path++)
 					if (path[0] == '/' && path[1] != 0)
 						result = path;
@@ -199,10 +200,8 @@ namespace Noux {
 						_append_slash_if_needed();
 						_append(relative_path);
 					}
-//					PDBG("path: '%s'", _path);
 				}
 				_canonicalize();
-//				PDBG("canonical path: '%s'", _path);
 			}
 
 		public:
@@ -231,6 +230,11 @@ namespace Noux {
 				*dst = 0;
 			}
 
+			void strip_last_element()
+			{
+				last_element(_path)[1] = 0;
+			}
+
 			bool equals(Path_base const &ref) const { return strcmp(ref._path, _path) == 0; }
 
 			bool equals(char const *str) const { return strcmp(str, _path) == 0; }
@@ -269,7 +273,7 @@ namespace Noux {
 				return (num_slashes == 1) && !equals("/");
 			}
 
-			void append(char const *str) { _append(str); }
+			void append(char const *str) { _append(str); _canonicalize(); }
 	};
 
 
diff --git a/ports/src/noux/root_file_system.h b/ports/src/noux/root_file_system.h
deleted file mode 100644
index 063f654fd9..0000000000
--- a/ports/src/noux/root_file_system.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * \brief  Root file system
- * \author Norman Feske
- * \date   2011-02-17
- */
-
-/*
- * Copyright (C) 2011-2012 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.
- */
-
-#ifndef _NOUX__ROOT_FILE_SYSTEM_H_
-#define _NOUX__ROOT_FILE_SYSTEM_H_
-
-/* Genode includes */
-#include <base/printf.h>
-#include <base/lock.h>
-
-/* Noux includes */
-#include <noux_session/sysio.h>
-
-namespace Noux {
-
-	class Root_file_system : public File_system
-	{
-		private:
-
-			Lock _lock;
-
-		public:
-
-			Root_file_system() : File_system("/") { }
-
-
-			/*********************************
-			 ** Directory-service interface **
-			 *********************************/
-
-			bool stat(Sysio *sysio, char const *path)
-			{
-				sysio->stat_out.st.size = 1234;
-				sysio->stat_out.st.mode = Sysio::STAT_MODE_DIRECTORY | 0755;
-				sysio->stat_out.st.uid  = 13;
-				sysio->stat_out.st.gid  = 14;
-				return true;
-			}
-
-			bool dirent(Sysio *sysio, char const *path)
-			{
-				Lock::Guard guard(_lock);
-
-				int const index = sysio->dirent_in.index;
-				if (index) {
-					sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_END;
-					return true;
-				}
-				sysio->dirent_out.entry.fileno = 13;
-
-				strncpy(sysio->dirent_out.entry.name, "test",
-				        sizeof(sysio->dirent_out.entry.name));
-
-				sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_DIRECTORY;
-				return true;
-			}
-
-			Vfs_handle *open(Sysio *sysio, char const *path)
-			{
-				Lock::Guard guard(_lock);
-
-				if (strcmp(path, "/") == 0)
-					return new (env()->heap()) Vfs_handle(this, this, 0);
-
-				return 0;
-			}
-
-			void close(Vfs_handle *handle)
-			{
-				Lock::Guard guard(_lock);
-				destroy(env()->heap(), handle);
-			}
-
-
-			/********************************
-			 ** File I/O service interface **
-			 ********************************/
-
-			bool write(Sysio *sysio, Vfs_handle *handle) { return false; }
-	};
-}
-
-#endif /* _NOUX__ROOT_FILE_SYSTEM_H_ */
diff --git a/ports/src/noux/tar_file_system.h b/ports/src/noux/tar_file_system.h
index 813dbac248..e50cc7e337 100644
--- a/ports/src/noux/tar_file_system.h
+++ b/ports/src/noux/tar_file_system.h
@@ -22,6 +22,8 @@
 
 /* Noux includes */
 #include <noux_session/sysio.h>
+#include <file_system.h>
+#include <vfs_handle.h>
 
 namespace Noux {
 
@@ -227,13 +229,43 @@ namespace Noux {
 		}
 
 
+		struct Num_dirent_cache
+		{
+			Lock             lock;
+			Tar_file_system &tar_fs;
+			bool             valid;              /* true after first lookup */
+			char             key[256];           /* key used for lookup */
+			size_t           cached_num_dirent;  /* cached value */
+
+			Num_dirent_cache(Tar_file_system &tar_fs)
+			: tar_fs(tar_fs), valid(false), cached_num_dirent(0) { }
+
+			size_t num_dirent(char const *path)
+			{
+				Lock::Guard guard(lock);
+
+				/* check for cache miss */
+				if (!valid || strcmp(path, key) != 0) {
+
+					Lookup_member_of_path lookup_criterion(path, ~0);
+					tar_fs._lookup(&lookup_criterion);
+					strncpy(key, path, sizeof(key));
+					cached_num_dirent = lookup_criterion.cnt;
+					valid = true;
+				}
+				return cached_num_dirent;
+			}
+		} _cached_num_dirent;
+
+
 		public:
 
 			Tar_file_system(Xml_node config)
 			:
-				File_system(config), _rom_name(config), _rom(_rom_name.name),
+				_rom_name(config), _rom(_rom_name.name),
 				_tar_base(env()->rm_session()->attach(_rom.dataspace())),
-				_tar_size(Dataspace_client(_rom.dataspace()).size())
+				_tar_size(Dataspace_client(_rom.dataspace()).size()),
+				_cached_num_dirent(*this)
 			{
 				PINF("tar archive '%s' local at %p, size is %zd",
 				     _rom_name.name, _tar_base, _tar_size);
@@ -284,7 +316,7 @@ namespace Noux {
 				return Dataspace_capability();
 			}
 
-			void release(Dataspace_capability ds_cap)
+			void release(char const *, Dataspace_capability ds_cap)
 			{
 				env()->ram_session()->free(static_cap_cast<Ram_dataspace>(ds_cap));
 			}
@@ -315,12 +347,10 @@ namespace Noux {
 				return true;
 			}
 
-			bool dirent(Sysio *sysio, char const *path)
+			bool dirent(Sysio *sysio, char const *path, off_t index)
 			{
 				Lock::Guard guard(_lock);
 
-				int const index = sysio->dirent_in.index;
-
 				Lookup_member_of_path lookup_criterion(path, index);
 				Record *record = _lookup(&lookup_criterion);
 				if (!record) {
@@ -345,16 +375,43 @@ namespace Noux {
 				        absolute_path.base() + 1,
 				        sizeof(sysio->dirent_out.entry.name));
 
-				PWRN("direntry in %s: %s", path, absolute_path.base() + 1);
 				return true;
 			}
 
+			bool unlink(Sysio *, char const *) { return false; }
+
+			bool rename(Sysio *, char const *, char const *) { return false; }
+
+			bool mkdir(Sysio *, char const *) { return false; }
+
+			size_t num_dirent(char const *path)
+			{
+				return _cached_num_dirent.num_dirent(path);
+			}
+
+			bool is_directory(char const *path)
+			{
+				Lookup_exact lookup_criterion(path);
+				Record const * record = _lookup(&lookup_criterion);
+				return record && (record->type() == Record::TYPE_DIR);
+			}
+
+			char const *leaf_path(char const *path)
+			{
+				/*
+				 * Check if path exists within the file system. If this is the
+				 * case, return the whole path, which is relative to the root
+				 * of this file system.
+				 */
+				Lookup_exact lookup_criterion(path);
+				Record const * record = _lookup(&lookup_criterion);
+				return record ? path : 0;
+			}
+
 			Vfs_handle *open(Sysio *sysio, char const *path)
 			{
 				Lock::Guard guard(_lock);
 
-				PDBG("open %s", path);
-
 				Lookup_exact lookup_criterion(path);
 				Record *record = 0;
 				if ((record = _lookup(&lookup_criterion)))
@@ -365,11 +422,12 @@ namespace Noux {
 				return 0;
 			}
 
-			void close(Vfs_handle *handle)
-			{
-				Lock::Guard guard(_lock);
-				destroy(env()->heap(), handle);
-			}
+
+			/***************************
+			 ** File_system interface **
+			 ***************************/
+
+			char const *name() const { return "tar"; }
 
 
 			/********************************
diff --git a/ports/src/noux/vfs.h b/ports/src/noux/vfs.h
deleted file mode 100644
index 0993805a7f..0000000000
--- a/ports/src/noux/vfs.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * \brief  Management of file systems within virtual directory tree
- * \author Norman Feske
- * \date   2011-02-17
- */
-
-/*
- * Copyright (C) 2011-2012 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.
- */
-
-#ifndef _NOUX__VFS_H_
-#define _NOUX__VFS_H_
-
-/* Genode includes */
-#include <util/list.h>
-#include <dataspace/capability.h>
-
-/* Noux includes */
-#include <path.h>
-#include <pwd.h>
-#include <vfs_handle.h>
-#include <file_system.h>
-#include <noux_session/sysio.h>
-
-namespace Noux {
-
-	class Vfs
-	{
-		private:
-
-			List<File_system> _file_systems;
-
-		public:
-
-			Dataspace_capability dataspace_from_file(char const *filename)
-			{
-				for (File_system *fs = _file_systems.first(); fs; fs = fs->next()) {
-					char const *fs_local_path = fs->local_path(filename);
-					Dataspace_capability ds_cap;
-					if (fs_local_path && (ds_cap = fs->dataspace(fs_local_path)).valid())
-						return ds_cap;
-				}
-				return Dataspace_capability();
-			}
-
-			void release_dataspace_for_file(char const *filename,
-			                                Dataspace_capability ds_cap)
-			{
-				for (File_system *fs = _file_systems.first(); fs; fs = fs->next()) {
-					char const *fs_local_path = fs->local_path(filename);
-					if (fs_local_path)
-						fs->release(ds_cap);
-				}
-			}
-
-			void add_file_system(File_system *file_system)
-			{
-				_file_systems.insert(file_system);
-			}
-
-			bool stat(Sysio *sysio, char const *path)
-			{
-				for (File_system *fs = _file_systems.first(); fs; fs = fs->next()) {
-					char const *fs_local_path = fs->local_path(path);
-					if (fs_local_path && fs->stat(sysio, fs_local_path))
-						return true;
-				}
-
-				sysio->error.stat = Sysio::STAT_ERR_NO_ENTRY;
-				return false;
-			}
-
-			Vfs_handle *open(Sysio *sysio, char const *path)
-			{
-				Vfs_handle *handle;
-				for (File_system *fs = _file_systems.first(); fs; fs = fs->next()) {
-					char const *fs_local_path = fs->local_path(path);
-					if (fs_local_path && (handle = fs->open(sysio, fs_local_path)))
-						return handle;
-				}
-
-				PWRN("no file system for \"%s\"", path);
-				sysio->error.open = Sysio::OPEN_ERR_UNACCESSIBLE;
-				return 0;
-			}
-
-			void close(Vfs_handle *handle)
-			{
-				handle->ds()->close(handle);
-			}
-	};
-}
-
-#endif /* _NOUX__VIRTUAL_DIRECTORY_SERVICE_H_ */
diff --git a/ports/src/noux/vfs_handle.h b/ports/src/noux/vfs_handle.h
index 3fab73ae92..3a3ec50a02 100644
--- a/ports/src/noux/vfs_handle.h
+++ b/ports/src/noux/vfs_handle.h
@@ -51,12 +51,17 @@ namespace Noux {
 						return Dataspace_capability();
 					}
 
-					void release(Dataspace_capability) { }
+					void release(char const *, Dataspace_capability) { }
 
-					bool        stat(Sysio *, char const *)   { return _msg("stat"); }
-					Vfs_handle *open(Sysio *, char const *)   { _msg("open"); return 0; }
-					void        close(Vfs_handle *)           { _msg("close"); }
-					bool        dirent(Sysio *, char const *) { return _msg("dirent"); }
+					bool        stat(Sysio *, char const *)                 { return _msg("stat"); }
+					Vfs_handle *open(Sysio *, char const *)                 { _msg("open"); return 0; }
+					bool        dirent(Sysio *, char const *, off_t)        { return _msg("dirent"); }
+					bool        unlink(Sysio *, char const *)               { return _msg("unlink"); }
+					bool        rename(Sysio *, char const *, char const *) { return _msg("rename"); }
+					bool        mkdir(Sysio *, char const *)                { return _msg("mkdir"); }
+					size_t      num_dirent(char const *)                    { return 0; }
+					bool        is_directory(char const *)                  { return false; }
+					char const *leaf_path(char const *path)                 { return 0; }
 				};
 				static Pseudo_directory_service ds;
 				return &ds;
diff --git a/ports/src/noux/vfs_io_channel.h b/ports/src/noux/vfs_io_channel.h
index 2204468336..2b8619fa49 100644
--- a/ports/src/noux/vfs_io_channel.h
+++ b/ports/src/noux/vfs_io_channel.h
@@ -17,23 +17,33 @@
 /* Noux includes */
 #include <io_channel.h>
 #include <file_system.h>
+#include <dir_file_system.h>
 #include <pwd.h>
 
 namespace Noux {
 
 	struct Vfs_io_channel : public Io_channel
 	{
-		Vfs        *_vfs;
 		Vfs_handle *_fh;
 
 		Absolute_path _path;
+		Absolute_path _leaf_path;
 
-		Vfs_io_channel(char const *path, Vfs *vfs, Vfs_handle *vfs_handle)
-		: _vfs(vfs), _fh(vfs_handle), _path(path) { }
+		Vfs_io_channel(char const *path, char const *leaf_path,
+		               Dir_file_system *root_dir, Vfs_handle *vfs_handle)
+		: _fh(vfs_handle), _path(path), _leaf_path(leaf_path) { }
 
-		~Vfs_io_channel() { _vfs->close(_fh); }
+		~Vfs_io_channel() { destroy(env()->heap(), _fh); }
 
-		bool write(Sysio *sysio) { return _fh->fs()->write(sysio, _fh); }
+		bool write(Sysio *sysio, size_t &count)
+		{
+			if (!_fh->fs()->write(sysio, _fh))
+				return false;
+
+			count = sysio->write_out.count;
+			_fh->_seek += count;
+			return true;
+		}
 
 		bool read(Sysio *sysio)
 		{
@@ -44,16 +54,15 @@ namespace Noux {
 			return true;
 		}
 
-		bool fstat(Sysio *sysio) { return _fh->ds()->stat(sysio, _path.base()); }
+		bool fstat(Sysio *sysio)
+		{
+			return _fh->ds()->stat(sysio, _leaf_path.base());
+		}
 
 		bool fcntl(Sysio *sysio)
 		{
 			switch (sysio->fcntl_in.cmd) {
 
-			case Sysio::FCNTL_CMD_SET_FD_FLAGS:
-
-				return true;
-
 			case Sysio::FCNTL_CMD_GET_FILE_STATUS_FLAGS:
 
 				sysio->fcntl_out.result = _fh->status_flags();
@@ -73,12 +82,18 @@ namespace Noux {
 			return true;
 		}
 
+		/*
+		 * The 'dirent' function for the root directory only (the
+		 * 'Dir_file_system::open()' function handles all requests referring
+		 * to directories). Hence, '_path' is the absolute path of the
+		 * directory to inspect.
+		 */
 		bool dirent(Sysio *sysio)
 		{
 			/*
 			 * Return artificial dir entries for "." and ".."
 			 */
-			unsigned const index = sysio->dirent_in.index;
+			unsigned const index = _fh->seek() / sizeof(Sysio::Dirent);
 			if (index < 2) {
 				sysio->dirent_out.entry.type = Sysio::DIRENT_TYPE_DIRECTORY;
 				strncpy(sysio->dirent_out.entry.name,
@@ -86,15 +101,46 @@ namespace Noux {
 				        sizeof(sysio->dirent_out.entry.name));
 
 				sysio->dirent_out.entry.fileno = 1;
+				_fh->_seek += sizeof(Sysio::Dirent);
 				return true;
 			}
 
 			/*
-			 * Delegate remaining dir-entry request to the actual file
-			 * system.
+			 * Delegate remaining dir-entry request to the actual file system.
+			 * Align index range to zero when calling the directory service.
 			 */
-			sysio->dirent_in.index -= 2;
-			return _fh->ds()->dirent(sysio, _path.base());
+
+			if (!_fh->ds()->dirent(sysio, _path.base(), index - 2))
+				return false;
+
+			_fh->_seek += sizeof(Sysio::Dirent);
+			return true;
+		}
+
+		/**
+		 * Return size of file that the I/O channel refers to
+		 *
+		 * Note that this function overwrites the 'sysio' argument. Do not
+		 * call it prior saving all input arguments from the original sysio
+		 * structure.
+		 */
+		size_t size(Sysio *sysio)
+		{
+			if (fstat(sysio))
+				return sysio->stat_out.st.size;
+
+			return 0;
+		}
+
+		bool lseek(Sysio *sysio)
+		{
+			switch (sysio->lseek_in.whence) {
+			case Sysio::LSEEK_SET: _fh->_seek = sysio->lseek_in.offset; break;
+			case Sysio::LSEEK_CUR:  break;
+			case Sysio::LSEEK_END: _fh->_seek = size(sysio); break;
+			}
+			sysio->lseek_out.offset = _fh->_seek;
+			return true;
 		}
 
 		bool check_unblock(bool rd, bool wr, bool ex) const