diff --git a/repos/gems/lib/mk/vfs_audit.mk b/repos/gems/lib/mk/vfs_audit.mk
new file mode 100644
index 0000000000..50c27ff86c
--- /dev/null
+++ b/repos/gems/lib/mk/vfs_audit.mk
@@ -0,0 +1,5 @@
+SRC_CC = vfs_audit.cc
+
+vpath %.cc $(REP_DIR)/src/lib/vfs/audit
+
+SHARED_LIB = yes
diff --git a/repos/gems/run/libc_vfs_audit.run b/repos/gems/run/libc_vfs_audit.run
new file mode 100644
index 0000000000..ca4a67196f
--- /dev/null
+++ b/repos/gems/run/libc_vfs_audit.run
@@ -0,0 +1,80 @@
+#
+# \brief Test for auditing the vfs
+# \author Emery Hemingway
+# \date 2018-03-22
+#
+
+#
+# Build
+#
+
+build { core init server/vfs test/libc_vfs lib/vfs/audit }
+
+create_boot_directory
+
+#
+# Generate config
+#
+
+set config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }
+append_if [have_include "power_on/qemu"] config {
+ }
+append config {
+
+
+
+
+
+
+
+
+}
+
+install_config $config
+
+#
+# Boot modules
+#
+
+build_boot_image {
+ core init vfs
+ ld.lib.so libc.lib.so
+ test-libc_vfs
+ vfs_audit.lib.so
+}
+
+#
+# Execute test case
+#
+
+append qemu_args " -nographic "
+run_genode_until {.*child "test-libc_vfs" exited with exit value 0.*} 60
+
+# vi: set ft=tcl :
diff --git a/repos/gems/run/noux_vfs_audit.run b/repos/gems/run/noux_vfs_audit.run
new file mode 100644
index 0000000000..9d51005de8
--- /dev/null
+++ b/repos/gems/run/noux_vfs_audit.run
@@ -0,0 +1,74 @@
+build {
+ core init
+ app/sequence
+ drivers/timer
+ lib/libc_noux
+ noux/minimal
+ noux-pkg/coreutils
+ server/vfs
+ lib/vfs/audit
+}
+
+create_boot_directory
+
+install_config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+build_boot_image {
+ core init ld.lib.so
+ coreutils.tar
+ libc_noux.lib.so
+ libm.lib.so
+ noux libc.lib.so
+ posix.lib.so
+ sequence
+ timer
+ vfs_audit.lib.so
+ vfs
+}
+
+append qemu_args " -nographic -serial mon:stdio "
+
+# coreutils.tar is really huge when built for x86_64
+
+run_genode_until {child "sequence" exited with exit value 0.*\n} 30
diff --git a/repos/gems/src/lib/vfs/audit/target.mk b/repos/gems/src/lib/vfs/audit/target.mk
new file mode 100644
index 0000000000..0a6334bd97
--- /dev/null
+++ b/repos/gems/src/lib/vfs/audit/target.mk
@@ -0,0 +1,2 @@
+TARGET = dummy-vfs_audit
+LIBS = vfs_audit
diff --git a/repos/gems/src/lib/vfs/audit/vfs_audit.cc b/repos/gems/src/lib/vfs/audit/vfs_audit.cc
new file mode 100644
index 0000000000..5a755ca5e0
--- /dev/null
+++ b/repos/gems/src/lib/vfs/audit/vfs_audit.cc
@@ -0,0 +1,311 @@
+/*
+ * \brief VFS audit plugin
+ * \author Emery Hemingway
+ * \date 2018-03-12
+ */
+
+/*
+ * Copyright (C) 2018 Genode Labs GmbH
+ *
+ * This file is part of the Genode OS framework, which is distributed
+ * under the terms of the GNU Affero General Public License version 3.
+ */
+
+#include
+#include
+#include
+
+namespace Vfs_audit {
+ using namespace Vfs;
+ class File_system;
+}
+
+class Vfs_audit::File_system : public Vfs::File_system
+{
+ private:
+
+ class Log : public Genode::Output
+ {
+ private:
+
+ enum { BUF_SIZE = Genode::Log_session::MAX_STRING_LEN };
+
+ Genode::Log_connection _log;
+
+ char _buf[BUF_SIZE];
+ unsigned _num_chars = 0;
+
+ void _flush()
+ {
+ _buf[_num_chars] = '\0';
+ _log.write(Genode::Log_session::String(_buf, _num_chars+1));
+ _num_chars = 0;
+ }
+
+ public:
+
+ Log(Genode::Env &env, char const *label)
+ : _log(env, label) { }
+
+ void out_char(char c) override
+ {
+ _buf[_num_chars++] = c;
+ if (_num_chars >= sizeof(_buf)-1)
+ _flush();
+ }
+
+ template
+ void log(ARGS &&... args)
+ {
+ Output::out_args(*this, args...);
+ _flush();
+ }
+
+ } _audit_log;
+
+ template
+ inline void _log(ARGS &&... args) { _audit_log.log(args...); }
+
+ Vfs::File_system &_root_dir;
+
+ Absolute_path const _audit_path;
+
+ /**
+ * Expand a path to lay within the audit path
+ */
+ inline Absolute_path _expand(char const *path) {
+ return Absolute_path(path+1, _audit_path.string()); }
+
+ struct Handle final : Vfs_handle
+ {
+ Handle(Handle const &);
+ Handle &operator = (Handle const &);
+
+ Absolute_path const path;
+ Vfs_handle *audit = nullptr;
+
+ void sync_state()
+ {
+ audit->seek(Vfs_handle::seek());
+ audit->context = context;
+ }
+
+ Handle(Vfs_audit::File_system &fs,
+ Genode::Allocator &alloc,
+ int flags,
+ char const *path)
+ : Vfs_handle(fs, fs, alloc, flags), path(path) { };
+ };
+
+ public:
+
+ File_system(Genode::Env &env,
+ Genode::Allocator &alloc,
+ Genode::Xml_node config,
+ Vfs::Io_response_handler &io_handler,
+ Vfs::File_system &root_dir)
+ :
+ _audit_log(env, config.attribute_value("label", Genode::String<64>("audit")).string()),
+ _root_dir(root_dir),
+ _audit_path(config.attribute_value(
+ "path", Genode::String()).string())
+ {
+ (void)env;
+ (void)alloc;
+ (void)io_handler;
+ }
+
+ const char* type() override { return "audit"; }
+
+ /***********************
+ ** Directory service **
+ ***********************/
+
+ Genode::Dataspace_capability dataspace(const char *path) override
+ {
+ _log(__func__, " ", path);
+ return _root_dir.dataspace(_expand(path).string());
+ }
+
+ void release(char const *path, Dataspace_capability ds) override
+ {
+ _log(__func__, " ", path);
+ return _root_dir.release(_expand(path).string(), ds);
+ }
+
+ Open_result open(const char *path, unsigned int mode, Vfs::Vfs_handle **out, Genode::Allocator &alloc) override
+ {
+ _log(__func__, " ", path, " ", Genode::Hex(mode, Genode::Hex::OMIT_PREFIX, Genode::Hex::PAD));
+
+ Handle *local_handle;
+ try { local_handle = new (alloc) Handle(*this, alloc, mode, path); }
+ catch (Genode::Out_of_ram) { return OPEN_ERR_OUT_OF_RAM; }
+ catch (Genode::Out_of_caps) { return OPEN_ERR_OUT_OF_CAPS; }
+
+ Open_result r = _root_dir.open(
+ _expand(path).string(), mode, &local_handle->audit, alloc);
+
+ if (r == OPEN_OK)
+ *out = local_handle;
+ else
+ destroy(alloc, local_handle);
+ return r;
+ }
+
+ Opendir_result opendir(char const *path, bool create,
+ Vfs_handle **out, Allocator &alloc) override
+ {
+ _log(__func__, " ", path, create ? " create " : "");
+
+ Handle *local_handle;
+ try { local_handle = new (alloc) Handle(*this, alloc, 0, path); }
+ catch (Genode::Out_of_ram) { return OPENDIR_ERR_OUT_OF_RAM; }
+ catch (Genode::Out_of_caps) { return OPENDIR_ERR_OUT_OF_CAPS; }
+
+ Opendir_result r = _root_dir.opendir(
+ _expand(path).string(), create, &local_handle->audit, alloc);
+
+ if (r == OPENDIR_OK)
+ *out = local_handle;
+ else
+ destroy(alloc, local_handle);
+ return r;
+ }
+
+ void close(Vfs::Vfs_handle *vfs_handle) override
+ {
+ Handle *h = static_cast(vfs_handle);
+ _log(__func__, " ", h->path);
+ if (h) {
+ h->audit->ds().close(h->audit);
+ destroy(h->alloc(), h);
+ }
+ }
+
+ Stat_result stat(const char *path, Vfs::Directory_service::Stat &buf) override
+ {
+ _log(__func__, " ", path);
+ return _root_dir.stat(_expand(path).string(), buf);
+ }
+
+ Unlink_result unlink(const char *path) override
+ {
+ _log(__func__, " ", path);
+ return _root_dir.unlink(_expand(path).string());
+ }
+
+ Rename_result rename(const char *from , const char *to) override
+ {
+ _log(__func__, " ", from, " ", to);
+ return _root_dir.rename(_expand(from).string(), _expand(to).string());
+ }
+
+ file_size num_dirent(const char *path) override
+ {
+ return _root_dir.num_dirent(_expand(path).string());
+ }
+
+ bool directory(char const *path) override
+ {
+ return _root_dir.directory(_expand(path).string());
+ }
+
+ const char* leaf_path(const char *path) override
+ {
+ return _root_dir.leaf_path(_expand(path).string());
+ }
+
+ /**********************
+ ** File I/O service **
+ **********************/
+
+ Write_result write(Vfs_handle *vfs_handle,
+ const char *buf, file_size len,
+ file_size &out) override
+ {
+ Handle &h = *static_cast(vfs_handle);
+ h.sync_state();
+ return h.audit->fs().write(h.audit, buf, len, out);
+ }
+
+ bool queue_read(Vfs_handle *vfs_handle, file_size len) override
+ {
+ Handle &h = *static_cast(vfs_handle);
+ h.sync_state();
+ return h.audit->fs().queue_read(h.audit, len);
+ }
+
+ Read_result complete_read(Vfs_handle *vfs_handle,
+ char *buf, file_size len,
+ file_size &out) override
+ {
+ Handle &h = *static_cast(vfs_handle);
+ h.sync_state();
+ return h.audit->fs().complete_read(h.audit, buf, len, out);
+ }
+
+ bool read_ready(Vfs_handle *vfs_handle) override
+ {
+ Handle &h = *static_cast(vfs_handle);
+ h.sync_state();
+ return h.audit->fs().read_ready(h.audit);
+ }
+
+ bool notify_read_ready(Vfs_handle *vfs_handle) override
+ {
+ Handle &h = *static_cast(vfs_handle);
+ h.sync_state();
+ return h.audit->fs().notify_read_ready(h.audit);
+ }
+
+ Ftruncate_result ftruncate(Vfs_handle *vfs_handle,
+ file_size len) override
+ {
+ Handle &h = *static_cast(vfs_handle);
+ h.sync_state();
+ _log(__func__, " ", h.path, " ", len);
+ return h.audit->fs().ftruncate(h.audit, len);
+ }
+
+ bool check_unblock(Vfs_handle *vfs_handle, bool rd, bool wr, bool ex) override
+ {
+ Handle &h = *static_cast(vfs_handle);
+ h.sync_state();
+ return h.audit->fs().check_unblock(h.audit, rd, wr, ex);
+ }
+
+ void register_read_ready_sigh(Vfs_handle *vfs_handle, Signal_context_capability sigh) override
+ {
+ Handle &h = *static_cast(vfs_handle);
+ h.sync_state();
+ return h.audit->fs().register_read_ready_sigh(h.audit, sigh);
+ }
+
+ Sync_result complete_sync(Vfs_handle *vfs_handle) override
+ {
+ Handle &h = *static_cast(vfs_handle);
+ h.sync_state();
+ _log("sync ", h.path);
+ return h.audit->fs().complete_sync(h.audit);
+ }
+};
+
+
+extern "C" Vfs::File_system_factory *vfs_file_system_factory(void)
+{
+ struct Factory : Vfs::File_system_factory
+ {
+ Vfs::File_system *create(Genode::Env &env,
+ Genode::Allocator &alloc,
+ Genode::Xml_node config,
+ Vfs::Io_response_handler &io_handler,
+ Vfs::File_system &root_dir) override
+ {
+ return new (alloc)
+ Vfs_audit::File_system(env, alloc, config, io_handler, root_dir);
+ }
+ };
+
+ static Factory f;
+ return &f;
+}