diff --git a/repos/os/recipes/src/chroot/content.mk b/repos/os/recipes/src/chroot/content.mk
new file mode 100644
index 0000000000..e533091f14
--- /dev/null
+++ b/repos/os/recipes/src/chroot/content.mk
@@ -0,0 +1,8 @@
+content: include/file_system
+
+SRC_DIR = src/server/chroot
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
+
+include/file_system:
+ mkdir -p $@
+ cp $(GENODE_DIR)/repos/os/include/file_system/* $@
diff --git a/repos/os/recipes/src/chroot/hash b/repos/os/recipes/src/chroot/hash
new file mode 100644
index 0000000000..128d3c8cbe
--- /dev/null
+++ b/repos/os/recipes/src/chroot/hash
@@ -0,0 +1 @@
+2017-09-26 7eba5980fe297d6231ef4c4f9894b88234b897d2
diff --git a/repos/os/recipes/src/chroot/used_apis b/repos/os/recipes/src/chroot/used_apis
new file mode 100644
index 0000000000..b892d66b55
--- /dev/null
+++ b/repos/os/recipes/src/chroot/used_apis
@@ -0,0 +1,3 @@
+base
+file_system_session
+os
diff --git a/repos/os/src/server/chroot/README b/repos/os/src/server/chroot/README
new file mode 100644
index 0000000000..ada05dbfcd
--- /dev/null
+++ b/repos/os/src/server/chroot/README
@@ -0,0 +1,33 @@
+This component intercepts File_system requests and changes
+the root directory of the request using the session label.
+
+In this example if cli_monitor had a child named "X", every
+file system session from "X" would be rooted to the directory
+"/cli_monitor/X" at "fs_server".
+
+!
+!
+! ...
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+!
+! ...
+!
+!
+!
+!
+!
+!
+!
+!
+! ...
+!
diff --git a/repos/os/src/server/chroot/component.cc b/repos/os/src/server/chroot/component.cc
new file mode 100644
index 0000000000..3e08627280
--- /dev/null
+++ b/repos/os/src/server/chroot/component.cc
@@ -0,0 +1,251 @@
+/*
+ * \brief Change session root server
+ * \author Emery Hemingway
+ * \date 2016-03-10
+ */
+
+/*
+ * Copyright (C) 2016 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
+#include
+#include
+
+namespace Chroot {
+ using namespace Genode;
+ struct Main;
+}
+
+
+struct Chroot::Main
+{
+ enum { PATH_MAX_LEN = 128 };
+ typedef Genode::Path Path;
+
+ /**
+ * Object to bind ids between parent and client space.
+ */
+ struct Session : Parent::Server
+ {
+ Parent::Client parent_client;
+
+ Id_space::Element client_id;
+ Id_space::Element server_id;
+
+ Session(Id_space &client_space,
+ Id_space &server_space,
+ Parent::Server::Id server_id)
+ :
+ client_id(parent_client, client_space),
+ server_id(*this, server_space, server_id) { }
+ };
+
+ Genode::Env &env;
+
+ Id_space server_id_space;
+
+ Heap heap { env.ram(), env.rm() };
+
+ Allocator_avl fs_tx_block_alloc { &heap };
+
+ /**
+ * File-system session for creating new chroot directories
+ */
+ File_system::Connection fs {
+ env, fs_tx_block_alloc, "", "/", true, 1 };
+
+ Attached_rom_dataspace session_requests { env, "session_requests" };
+
+ Attached_rom_dataspace config_rom { env, "config" };
+
+ void handle_config_update() { config_rom.update(); }
+
+ void handle_session_request(Xml_node request);
+
+ void handle_session_requests()
+ {
+ session_requests.update();
+
+ Xml_node const requests = session_requests.xml();
+
+ requests.for_each_sub_node([&] (Xml_node request) {
+ handle_session_request(request);
+ });
+ }
+
+ Signal_handler config_update_handler {
+ env.ep(), *this, &Main::handle_config_update };
+
+ Signal_handler session_request_handler {
+ env.ep(), *this, &Main::handle_session_requests };
+
+ /**
+ * Constructor
+ */
+ Main(Genode::Env &env) : env(env)
+ {
+ config_rom.sigh(config_update_handler);
+ session_requests.sigh(session_request_handler);
+
+ /* handle requests that have queued before or during construction */
+ handle_session_requests();
+ }
+
+ Session_capability request_session(Parent::Client::Id const &id,
+ Session_state::Args const &args)
+ {
+ char tmp[PATH_MAX_LEN];
+ Path root_path;
+
+ Session_label const label = label_from_args(args.string());
+ Session_policy const policy(label, config_rom.xml());
+
+ /* Use a chroot path from policy */
+ if (policy.has_attribute("path")) {
+ policy.attribute("path").value(tmp, sizeof(tmp));
+ root_path.import(tmp);
+ }
+
+ /* if policy specifies a merge, use a truncated label */
+ else if (policy.has_attribute("label_prefix")
+ && policy.attribute_value("merge", false))
+ {
+ /* merge at the next element */
+ size_t offset = policy.attribute("label_prefix").value_size();
+ for (size_t i = offset; i < label.length()-4; ++i) {
+ if (strcmp(label.string()+i, " -> ", 4))
+ continue;
+
+ strncpy(tmp, label.string(), min(sizeof(tmp), i+1));
+ break;
+ }
+ root_path = path_from_label(tmp);
+ }
+
+ /* use an implicit chroot path from the label */
+ else {
+ root_path = path_from_label(label.string());
+ }
+
+ /* extract and append the orginal root */
+ Arg_string::find_arg(args.string(), "root").string(
+ tmp, sizeof(tmp), "/");
+ root_path.append_element(tmp);
+ root_path.remove_trailing('/');
+
+ char const *new_root = root_path.base();
+
+ using namespace File_system;
+
+ /* create the new root directory if it is missing */
+ try { fs.close(ensure_dir(fs, new_root)); }
+ catch (Node_already_exists) { }
+ catch (Permission_denied) {
+ Genode::error(new_root,": permission denied"); throw; }
+ catch (Name_too_long) {
+ Genode::error(new_root,": new root too long"); throw; }
+ catch (No_space) {
+ Genode::error(new_root,": no space"); throw; }
+ catch (...) {
+ Genode::error(new_root,": unknown error"); throw; }
+
+ /* rewrite the root session argument */
+ enum { ARGS_MAX_LEN = 256 };
+ char new_args[ARGS_MAX_LEN];
+
+ strncpy(new_args, args.string(), ARGS_MAX_LEN);
+
+ /* sacrifice the label to make space for the root argument */
+ Arg_string::remove_arg(new_args, "label");
+
+ Arg_string::set_arg_string(new_args, ARGS_MAX_LEN, "root", new_root);
+
+ Affinity affinity;
+ return env.session("File_system", id, new_args, affinity);
+ }
+};
+
+
+void Chroot::Main::handle_session_request(Xml_node request)
+{
+ if (!request.has_attribute("id"))
+ return;
+
+ Parent::Server::Id const server_id { request.attribute_value("id", 0UL) };
+
+ if (request.has_type("create")) {
+
+ if (!request.has_sub_node("args"))
+ return;
+
+ typedef Session_state::Args Args;
+ Args const args = request.sub_node("args").decoded_content();
+
+ Session *session = nullptr;
+ try {
+ session = new (heap)
+ Session(env.id_space(), server_id_space, server_id);
+ Session_capability cap = request_session(session->client_id.id(), args);
+
+ env.parent().deliver_session_cap(server_id, cap);
+ }
+
+ catch (Session_policy::No_policy_defined) {
+ Genode::error("no policy defined for '", label_from_args(args.string()), "'");
+ env.parent().session_response(server_id, Parent::SERVICE_DENIED);
+ }
+
+ catch (...) {
+ if (session)
+ destroy(heap, session);
+ env.parent().session_response(server_id, Parent::SERVICE_DENIED);
+ }
+ }
+
+ if (request.has_type("upgrade")) {
+
+ server_id_space.apply(server_id, [&] (Session &session) {
+
+ size_t ram_quota = request.attribute_value("ram_quota", 0UL);
+
+ String<64> args("ram_quota=", ram_quota);
+
+ env.upgrade(session.client_id.id(), args.string());
+ env.parent().session_response(server_id, Parent::SESSION_OK);
+ });
+ }
+
+ if (request.has_type("close")) {
+ server_id_space.apply(server_id, [&] (Session &session) {
+ env.close(session.client_id.id());
+ destroy(heap, &session);
+ env.parent().session_response(server_id, Parent::SESSION_CLOSED);
+ });
+ }
+}
+
+
+/***************
+ ** Component **
+ ***************/
+
+Genode::size_t Component::stack_size() {
+ return 2*1024*sizeof(Genode::addr_t); }
+
+void Component::construct(Genode::Env &env)
+{
+ static Chroot::Main inst(env);
+ env.parent().announce("File_system");
+}
diff --git a/repos/os/src/server/chroot/target.mk b/repos/os/src/server/chroot/target.mk
new file mode 100644
index 0000000000..7fd2af69ed
--- /dev/null
+++ b/repos/os/src/server/chroot/target.mk
@@ -0,0 +1,3 @@
+TARGET = chroot
+SRC_CC = component.cc
+LIBS = base
\ No newline at end of file