diff --git a/repos/libports/src/server/fs_log/README b/repos/libports/src/server/fs_log/README
deleted file mode 100644
index 315d96bbd..000000000
--- a/repos/libports/src/server/fs_log/README
+++ /dev/null
@@ -1,18 +0,0 @@
-LOG server that writes log messages onto a file system.
-
-Using this component, log messages of different processes can be redirected
-to files on a file-system service. The assignment of processes to files can
-be expressed in the configuration as follows:
-
-!
-!
-!
-!
-!
-!
-!
-!
-
-In this example, all messages originating from the noux process are directed
-to the file '/noux.log'. All messages originating from children of the noux
-process end up in the file '/noux_process.log'.
diff --git a/repos/libports/src/server/fs_log/main.cc b/repos/libports/src/server/fs_log/main.cc
deleted file mode 100644
index 8f290ffb1..000000000
--- a/repos/libports/src/server/fs_log/main.cc
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * \brief LOG service that writes to a file
- * \author Alexander Tarasikov
- * \author Norman Feske
- * \date 2013-05-07
- */
-
-/*
- * Copyright (C) 2013 Ksys Labs LLC
- * Copyright (C) 2012-2013 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
-#include
-#include
-#include
-#include
-
-/* libc includes */
-#include
-#include
-
-
-class Log_component : public Genode::Rpc_object
-{
- public:
-
- enum { LABEL_LEN = 64 };
-
- private:
-
- typedef Genode::size_t size_t;
-
- char _label[LABEL_LEN];
- size_t _label_len;
- int _fd;
-
- public:
-
- /**
- * Constructor
- */
- Log_component(const char *label, char const *filename)
- :
- _fd(::open(filename, O_CREAT | O_WRONLY | O_APPEND))
- {
- using namespace Genode;
-
- if (_fd < 0) {
- PERR("unable to open \"%s\"", filename);
- throw Root::Unavailable();
- }
-
- snprintf(_label, LABEL_LEN, "[%s] ", label);
- _label_len = strlen(_label);
-
- PINF("log client \"%s\" to file \"%s\")", label, filename);
- }
-
- /**
- * Destructor
- */
- ~Log_component() { ::close(_fd); }
-
-
- /*****************
- ** Log session **
- *****************/
-
- /**
- * Write a log-message to the file.
- */
- size_t write(String const &string_buf)
- {
- if (!(string_buf.is_valid_string())) {
- PERR("corrupted string");
- return 0;
- }
-
- char const *string = string_buf.string();
- Genode::size_t const len = Genode::strlen(string);
-
- ::write(_fd, _label, _label_len);
- ::write(_fd, string, len);
-
- return len;
- }
-};
-
-
-class Log_root : public Genode::Root_component
-{
- private:
-
- int _fd;
-
- protected:
-
- /**
- * Root component interface
- */
- Log_component *_create_session(const char *args)
- {
- using namespace Genode;
-
- char label_buf[Log_component::LABEL_LEN];
- Arg label_arg = Arg_string::find_arg(args, "label");
- label_arg.string(label_buf, sizeof(label_buf), "");
-
- /* obtain file name from configured policy */
- enum { FILENAME_MAX_LEN = 256 };
- char filename[FILENAME_MAX_LEN];
- try {
- Session_label label(args);
- Session_policy policy(label);
- policy.attribute("file").value(filename, sizeof(filename));
- } catch (...) {
- PERR("Invalid session request, no matching policy");
- throw Root::Unavailable();
- }
-
- return new (md_alloc()) Log_component(label_buf, filename);
- }
-
- public:
-
- /**
- * Constructor
- *
- * \param session_ep entry point for managing cpu session objects
- * \param md_alloc meta-data allocator to be used by root component
- */
- Log_root(Genode::Rpc_entrypoint *session_ep, Genode::Allocator *md_alloc)
- : Genode::Root_component(session_ep, md_alloc) { }
-};
-
-
-int main(int argc, char **argv)
-{
- using namespace Genode;
-
- /*
- * Initialize server entry point
- *
- * Use a large stack because we are calling libc functions from the
- * context of the entrypoint.
- */
- enum { STACK_SIZE = sizeof(addr_t)*16*1024 };
- static Cap_connection cap;
- static Rpc_entrypoint ep(&cap, STACK_SIZE, "fs_log_ep");
-
- static Log_root log_root(&ep, env()->heap());
-
- /*
- * Announce services
- */
- env()->parent()->announce(ep.manage(&log_root));
-
- /**
- * Got to sleep forever
- */
- sleep_forever();
- return 0;
-}
diff --git a/repos/os/src/server/fs_log/README b/repos/os/src/server/fs_log/README
new file mode 100644
index 000000000..abfa908de
--- /dev/null
+++ b/repos/os/src/server/fs_log/README
@@ -0,0 +1,20 @@
+LOG server that writes log messages onto a file system.
+
+Log files are creating in a directory tree formed from session labels.
+As an example the session label "init -> nitpicker" would create
+a log file at "init/nitpicker.log". The behavior of opening two LOG
+sessions with the same label is undefined.
+
+The only configuration and policy available is the option to truncate
+files at the start of the LOG session, which is disabled by default.
+
+:Example configuration:
+!
+!
+!
+!
+!
+!
+!
+!
+
diff --git a/repos/os/src/server/fs_log/main.cc b/repos/os/src/server/fs_log/main.cc
new file mode 100644
index 000000000..ac1f4e391
--- /dev/null
+++ b/repos/os/src/server/fs_log/main.cc
@@ -0,0 +1,184 @@
+/*
+ * \brief Server that writes log messages to a file system.
+ * \author Emery Hemingway
+ * \date 2015-05-13
+ */
+
+/*
+ * Copyright (C) 2015 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
+#include
+#include
+
+/* Local includes */
+#include "session.h"
+
+namespace Fs_log {
+
+ using namespace Genode;
+
+ class Root_component;
+ struct Main;
+
+ enum {
+ BLOCK_SIZE = Log_session::String::MAX_SIZE,
+ QUEUE_SIZE = File_system::Session::TX_QUEUE_SIZE,
+ TX_BUF_SIZE = BLOCK_SIZE * QUEUE_SIZE
+ };
+
+}
+
+class Fs_log::Root_component : public Genode::Root_component
+{
+ private:
+
+ Allocator_avl _write_alloc;
+ File_system::Connection _fs;
+
+ File_system::File_handle open_file(File_system::Dir_handle &dir_handle,
+ char const *name)
+ {
+ try {
+ return _fs.file(dir_handle, name, File_system::WRITE_ONLY, false);
+ } catch (File_system::Lookup_failed) {
+ return _fs.file(dir_handle, name, File_system::WRITE_ONLY, true);
+ }
+ }
+
+ protected:
+
+ Session_component *_create_session(const char *args)
+ {
+ using namespace File_system;
+
+ char path[MAX_PATH_LEN];
+ path[0] = '/';
+ char name[MAX_NAME_LEN];
+
+ Session_label session_label(args);
+ strncpy(path+1, session_label.string(), sizeof(path)-1);
+
+ bool truncate = false;
+ try {
+ Session_policy policy(session_label);
+ try {
+ truncate = policy.attribute("truncate").has_value("yes");
+ } catch (Xml_node::Nonexistent_attribute) { }
+
+ } catch (Session_policy::No_policy_defined) { }
+
+ size_t len = strlen(path);
+ size_t start = 1;
+ for (size_t i = 1; i < len;) {
+ /* Replace any slashes in label elements. */
+ if (path[i] == '/') path[i] = '_';
+ if (strcmp(" -> ", path+i, 4) == 0) {
+ path[i++] = '/';
+ strncpy(path+i, path+i+3, sizeof(path)-i);
+ start = i;
+ i += 3;
+ } else ++i;
+ }
+
+ snprintf(name, sizeof(name), "%s.log", path+start);
+ path[(start == 1) ? start : start-1] = '\0';
+
+ /* Rewrite any slashes in the name. */
+ for (char *p = name; *p; ++p)
+ if (*p == '/') *p = '_';
+
+ File_handle file_handle;
+ seek_off_t offset = 0;
+ try {
+ Dir_handle dir_handle = ensure_dir(_fs, path);
+ Handle_guard dir_guard(_fs, dir_handle);
+
+ file_handle = open_file(dir_handle, name);
+
+ if (truncate)
+ _fs.truncate(file_handle, 0);
+ else
+ offset = _fs.status(file_handle).size;
+
+ } catch (Permission_denied) {
+ PERR("%s/%s: permission denied", path, name);
+ throw Root::Unavailable();
+
+ } catch (Name_too_long) {
+ PERR("%s/%s: name too long", path, name);
+ throw Root::Unavailable();
+
+ } catch (No_space) {
+ PERR("%s/%s: no space", path, name);
+ throw Root::Unavailable();
+
+ } catch (Out_of_node_handles) {
+ PERR("%s/%s: out of node handles", path, name);
+ throw Root::Unavailable();
+
+ } catch (Invalid_name) {
+ PERR("%s/%s: invalid_name", path, name);
+ throw Root::Unavailable();
+
+ } catch (Size_limit_reached) {
+ PERR("%s/%s: size limit reached", path, name);
+ throw Root::Unavailable();
+
+ } catch (...) {
+ PERR("%s/%s: unknown error", path, name);
+ throw Root::Unavailable();
+ }
+
+ return new (md_alloc()) Session_component(_fs, file_handle, offset);
+ }
+
+ public:
+
+ /**
+ * Constructor
+ */
+ Root_component(Server::Entrypoint &ep, Allocator &alloc)
+ :
+ Genode::Root_component(&ep.rpc_ep(), &alloc),
+ _write_alloc(env()->heap()),
+ _fs(_write_alloc, TX_BUF_SIZE)
+ { }
+
+};
+
+struct Fs_log::Main
+{
+ Server::Entrypoint &ep;
+
+ Sliced_heap sliced_heap = { env()->ram_session(), env()->rm_session() };
+
+ Root_component root { ep, sliced_heap };
+
+ Main(Server::Entrypoint &ep)
+ : ep(ep)
+ { Genode::env()->parent()->announce(ep.manage(root)); }
+};
+
+
+/************
+ ** Server **
+ ************/
+
+namespace Server {
+
+ char const* name() { return "fs_log_ep"; }
+
+ size_t stack_size() { return 3*512*sizeof(long); }
+
+ void construct(Entrypoint &ep) { static Fs_log::Main inst(ep); }
+
+}
diff --git a/repos/os/src/server/fs_log/session.h b/repos/os/src/server/fs_log/session.h
new file mode 100644
index 000000000..ebe289446
--- /dev/null
+++ b/repos/os/src/server/fs_log/session.h
@@ -0,0 +1,100 @@
+/*
+ * \brief Log session that writes messages to a file system.
+ * \author Emery Hemingway
+ * \date 2015-05-16
+ */
+
+/*
+ * Copyright (C) 2015 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 _FS_LOG__SESSION_H_
+#define _FS_LOG__SESSION_H_
+
+/* Genode includes */
+#include
+#include
+#include
+
+namespace Fs_log {
+
+ using namespace Genode;
+
+ class Session_component;
+
+}
+
+/**
+ * A log session that writes messages to a file system node.
+ *
+ * Message writing is fire-and-forget to prevent
+ * logging from becoming I/O bound.
+ */
+class Fs_log::Session_component : public Rpc_object
+{
+ private:
+
+ File_system::Session &_fs;
+ File_system::File_handle _file_handle;
+ File_system::seek_off_t _offset;
+
+ public:
+
+ /**
+ * Constructor
+ */
+ Session_component(File_system::Session &fs,
+ File_system::File_handle fh,
+ File_system::seek_off_t offset)
+ : _fs(fs), _file_handle(fh), _offset(offset) { }
+
+ /*****************
+ ** Log session **
+ *****************/
+
+ size_t write(Log_session::String const &string)
+ {
+ if (!(string.is_valid_string())) {
+ PERR("corrupted string");
+ return 0;
+ }
+
+ File_system::Session::Tx::Source &source = *_fs.tx();
+
+ char const *msg = string.string();
+ size_t msg_len = Genode::strlen(msg);
+ size_t write_len = msg_len;
+
+ /*
+ * If the message did not fill the incoming buffer
+ * make space to add a newline.
+ */
+ if ((msg_len < Log_session::String::MAX_SIZE) &&
+ (msg[msg_len-1] != '\n'))
+ ++write_len;
+
+ while (source.ack_avail())
+ source.release_packet(source.get_acked_packet());
+
+ File_system::Packet_descriptor
+ packet(source.alloc_packet(Log_session::String::MAX_SIZE),
+ 0, /* The result struct. */
+ _file_handle, File_system::Packet_descriptor::WRITE,
+ write_len, _offset);
+ _offset += write_len;
+
+ char *buf = source.packet_content(packet);
+ memcpy(buf, msg, msg_len);
+
+ if (msg_len != write_len)
+ buf[msg_len] = '\n';
+
+ source.submit_packet(packet);
+ return msg_len;
+ }
+};
+
+#endif
diff --git a/repos/libports/src/server/fs_log/target.mk b/repos/os/src/server/fs_log/target.mk
similarity index 54%
rename from repos/libports/src/server/fs_log/target.mk
rename to repos/os/src/server/fs_log/target.mk
index 40f3b0a18..372c6082e 100644
--- a/repos/libports/src/server/fs_log/target.mk
+++ b/repos/os/src/server/fs_log/target.mk
@@ -1,3 +1,3 @@
TARGET = fs_log
SRC_CC = main.cc
-LIBS = base libc
+LIBS = base config server