diff --git a/recipes/src/xml_editor/content.mk b/recipes/src/xml_editor/content.mk
new file mode 100644
index 0000000..262e7ad
--- /dev/null
+++ b/recipes/src/xml_editor/content.mk
@@ -0,0 +1,2 @@
+SRC_DIR := src/app/xml_editor
+include $(GENODE_DIR)/repos/base/recipes/src/content.inc
diff --git a/recipes/src/xml_editor/hash b/recipes/src/xml_editor/hash
new file mode 100644
index 0000000..2e349e2
--- /dev/null
+++ b/recipes/src/xml_editor/hash
@@ -0,0 +1 @@
+2017-06-08 29beffb8c1589ab3398db5b17f432e32ade2c0f2
diff --git a/recipes/src/xml_editor/used_apis b/recipes/src/xml_editor/used_apis
new file mode 100644
index 0000000..0d27980
--- /dev/null
+++ b/recipes/src/xml_editor/used_apis
@@ -0,0 +1,8 @@
+base
+block_session
+file_system_session
+os
+report_session
+rtc_session
+terminal_session
+vfs
diff --git a/recipes/src/xml_term_edit/content.mk b/recipes/src/xml_term_edit/content.mk
new file mode 100644
index 0000000..2acb24d
--- /dev/null
+++ b/recipes/src/xml_term_edit/content.mk
@@ -0,0 +1,14 @@
+SRC_DIR := src/app/xml_term_edit
+
+content: $(SRC_DIR) LICENSE
+
+$(SRC_DIR):
+ mkdir -p $@
+ cp -r $(REP_DIR)/$@/* $@/
+ cp \
+ $(GENODE_DIR)/repos/os/src/app/cli_monitor/command_line.h \
+ $(GENODE_DIR)/repos/os/src/app/cli_monitor/line_editor.h \
+ $@/
+
+LICENSE:
+ cp $(GENODE_DIR)/LICENSE $@
diff --git a/recipes/src/xml_term_edit/hash b/recipes/src/xml_term_edit/hash
new file mode 100644
index 0000000..6cd4c2d
--- /dev/null
+++ b/recipes/src/xml_term_edit/hash
@@ -0,0 +1 @@
+2017-06-08 1c40e2580a6a1abf3c72a9272cb3d9d7a1eab81d
diff --git a/recipes/src/xml_term_edit/used_apis b/recipes/src/xml_term_edit/used_apis
new file mode 100644
index 0000000..0d27980
--- /dev/null
+++ b/recipes/src/xml_term_edit/used_apis
@@ -0,0 +1,8 @@
+base
+block_session
+file_system_session
+os
+report_session
+rtc_session
+terminal_session
+vfs
diff --git a/run/xml_term_edit.run b/run/xml_term_edit.run
new file mode 100644
index 0000000..3c56c42
--- /dev/null
+++ b/run/xml_term_edit.run
@@ -0,0 +1,163 @@
+create_boot_directory
+
+import_from_depot \
+ genodelabs/src/[base_src] \
+ genodelabs/pkg/[drivers_interactive_pkg] \
+ ehmry/src/xml_editor \
+ ehmry/src/xml_term_edit \
+
+set build_components {
+ core init
+ drivers/timer
+ server/fs_rom
+ server/terminal
+ server/ram_fs
+ test/log
+ test/timer
+}
+
+build $build_components
+
+append config {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+install_config $config
+
+#
+# Boot modules
+#
+
+# generic modules
+append boot_modules {
+ core ld.lib.so init
+ fs_rom
+ libc.lib.so
+ libm.lib.so
+ ram_fs
+ terminal
+ test-log
+ test-timer
+ timer
+}
+
+build_boot_image $boot_modules
+
+run_genode_until forever
diff --git a/src/app/xml_editor/README b/src/app/xml_editor/README
new file mode 100644
index 0000000..873de6e
--- /dev/null
+++ b/src/app/xml_editor/README
@@ -0,0 +1,25 @@
+This component edits a single XML file as instructed by clients via
+Report sessions. It is designed to allow insertion and removal of XML
+nodes without revealing the content the file being edited.
+
+The two edit actions are _add_ and _remove_, both operate over XML
+nodes under the parent node in the file being edited. The content of
+edits are verified for corrent syntax and XML node type and _name_
+attribute are used to prevent the same node from being inserted
+twice or to find nodes to remove.
+
+Add action
+----------
+!
+! <... name="...">
+! ...
+!
+!
+
+Remove action
+-------------
+!
+! <... name="...">
+! ...
+!
+!
diff --git a/src/app/xml_editor/component.cc b/src/app/xml_editor/component.cc
new file mode 100644
index 0000000..cfad04c
--- /dev/null
+++ b/src/app/xml_editor/component.cc
@@ -0,0 +1,536 @@
+/*
+ * \brief Transactional XML editor
+ * \author Emery Hemingway
+ * \date 2017-04-29
+ */
+
+/*
+ * Copyright (C) 2017 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
+#include
+
+namespace Xml_editor {
+ using namespace Genode;
+
+ typedef Path Path;
+ typedef Genode::String<64> Name;
+
+ struct Xml_file;
+ struct Report_session_component;
+ class Report_root_component;
+ struct Main;
+}
+
+#define REVISION_ATTR_NAME "edit_rev"
+
+struct Xml_editor::Xml_file
+{
+ Genode::Allocator &alloc;
+ Vfs::Vfs_handle &vfs_handle;
+
+ Path const path;
+ unsigned revision = 0;
+
+
+ /*****************************
+ ** Current and next buffer **
+ *****************************/
+
+ struct Buffer {
+ char *ptr = nullptr;
+ size_t size = 0;
+ Buffer *next;
+ };
+
+ Buffer yin;
+ Buffer yang;
+
+ Buffer *buffer = &yin;
+
+ Buffer &next_buffer() { return *buffer->next; }
+
+ /**
+ * Return the next buffer, zeroed, and reallocated
+ * if necessary
+ */
+ Buffer &next_buffer(size_t min_size)
+ {
+ Buffer &next = *buffer->next;
+ if (next.size < min_size) {
+ if (next.ptr)
+ alloc.free(next.ptr, next.size);
+ next.ptr = (char *)alloc.alloc(min_size);
+ next.size = min_size;
+ }
+ memset(next.ptr, 0x00, next.size);
+ return next;
+ }
+
+ template
+ Xml_generator generate(char const *type, size_t min_size, FUNC const &fn)
+ {
+ Buffer &next = next_buffer(min_size);
+
+ return Xml_generator(next.ptr, next.size, type, fn);
+ }
+
+ Xml_node xml() const {
+ return Xml_node(buffer->ptr, buffer->size); }
+
+ /**
+ * Flush file changes
+ */
+ void sync() {
+ vfs_handle.ds().sync(path.base()); }
+
+ void read_file()
+ {
+ using namespace Vfs;
+
+ Directory_service::Stat sb;
+ vfs_handle.ds().stat(path.base(), sb);
+ file_size const total = sb.size;
+
+ Buffer &next = next_buffer(total ? total : 4096);
+
+ if (total == 0) {
+ strncpy(next.ptr, "", next.size);
+ } else {
+
+ file_size offset = 0;
+ while (offset < total) {
+ vfs_handle.seek(offset);
+ file_size out = 0;
+ vfs_handle.fs().read(
+ &vfs_handle,
+ next.ptr + offset,
+ total - offset,
+ out);
+ offset += out;
+ }
+ }
+
+ buffer = &next;
+ }
+
+ void write_file(Vfs::file_size length)
+ {
+ using namespace Vfs;
+
+ Buffer &next = next_buffer();
+
+ vfs_handle.fs().ftruncate(&vfs_handle, length);
+
+ file_size offset = 0;
+ while (offset < length) {
+ vfs_handle.seek(offset);
+ file_size n = 0;
+ vfs_handle.fs().write(
+ &vfs_handle,
+ next.ptr + offset,
+ length - offset,
+ n);
+ offset += n;
+ }
+
+ buffer = &next;
+ }
+
+ Xml_file(Genode::Allocator &alloc,
+ Vfs::Vfs_handle &handle,
+ Path const &path)
+ :
+ alloc(alloc), vfs_handle(handle), path(path)
+ {
+ yin.next = &yang;
+ yang.next = &yin;
+
+ read_file();
+
+ try {
+ Xml_node const editor_node = xml().sub_node("xml_editor");
+ revision = editor_node.attribute_value("rev", 0U);
+ }
+ catch (Xml_node::Nonexistent_sub_node) { }
+ catch (Xml_node::Invalid_syntax) {
+ Genode::error("invalid XML at '", path, "'");
+ }
+ }
+
+ ~Xml_file()
+ {
+ if (yin.ptr)
+ alloc.free(yin.ptr, yin.size);
+ if (yang.ptr)
+ alloc.free(yang.ptr, yang.size);
+ }
+
+ size_t add(Xml_node const &new_node)
+ {
+ Xml_node const current = xml();
+
+ Name const new_name =
+ new_node.attribute_value("name", Name());
+
+ current.for_each_sub_node(new_node.type().string(),
+ [&] (Xml_node const &existing_start) {
+ if (existing_start.attribute_value("name", Name()) == new_name) {
+ error(new_name, " start node already present in config");
+ }
+ });
+
+
+ Xml_generator gen = generate(current.type().string(),
+ current.size()+new_node.size()*2,
+ [&] () {
+ try {
+ Xml_attribute attr = current.attribute(0U);
+ while (true) {
+ auto attr_name = attr.name();
+ if (attr_name != REVISION_ATTR_NAME) {
+ Genode::String<256> data;
+ attr.value(&data);
+ gen.attribute(attr_name.string(), data.string());
+ }
+ attr = attr.next();
+ }
+ } catch (Xml_node::Nonexistent_attribute) { }
+
+ /* set revision info as a top-level attribute */
+ gen.attribute("edit_rev", ++revision);
+
+ /* add new content node */
+ gen.append(new_node.addr(), new_node.size());
+
+ try {
+ /* add existing nodes */
+ Xml_node node = current.sub_node();
+ while (true) {
+ gen.append(node.addr(), node.size());
+ node = node.next();
+ }
+
+ } catch (Xml_node::Nonexistent_sub_node) {
+ }
+ gen.append("\n");
+ });
+
+ return gen.used();
+ }
+
+ size_t remove(Xml_node const &node)
+ {
+ Name const remove_name =
+ node.attribute_value("name", Name());
+
+ Xml_node const current = xml();
+
+ Xml_generator gen = generate(current.type().string(),
+ current.size()*4,
+ [&] () {
+ try {
+ Xml_attribute attr = current.attribute(0U);
+ while (true) {
+ auto attr_name = attr.name();
+ if (attr_name != REVISION_ATTR_NAME) {
+ Genode::String<256> data;
+ attr.value(&data);
+ gen.attribute(attr_name.string(), data.string());
+ }
+ attr = attr.next();
+ }
+ } catch (Xml_node::Nonexistent_attribute) { }
+
+ /* set revision info as a top-level attribute */
+ gen.attribute("edit_rev", ++revision);
+
+ current.for_each_sub_node([&] (Xml_node const &node) {
+ /* skip the node we are removing */
+ auto name = node.attribute_value("name", Name());
+ if (name != remove_name)
+ {
+ gen.append(node.addr(), node.size());
+ gen.append("\n");
+ }
+ });
+ gen.append("\n");
+ });
+
+ return gen.used();
+ }
+};
+
+
+struct Xml_editor::Report_session_component : Genode::Rpc_object
+{
+ Session_label const label;
+
+ Attached_ram_dataspace ram_ds;
+
+ Xml_file &xml_file;
+
+ bool const verbose = true;
+
+ Report_session_component(Genode::Env &env, size_t buffer_size,
+ Xml_file &file,
+ Session_label const &session_label)
+ :
+ label(session_label),
+ ram_ds(env.ram(), env.rm(), buffer_size),
+ xml_file(file)
+ { }
+
+
+ /******************************
+ ** Report session interface **
+ ******************************/
+
+ Dataspace_capability dataspace() override {
+ return ram_ds.cap(); }
+
+ void submit(size_t length) override
+ {
+ size_t content_size = 0;
+
+ auto add_fn = [&] (Xml_node const &action) {
+ action.for_each_sub_node([&] (Xml_node const &subnode) {
+ if (verbose)
+ log("'", label, "' adds '", subnode.attribute_value("name", Name()), "'");
+ content_size = xml_file.add(subnode);
+ });
+ };
+
+ auto remove_fn = [&] (Xml_node const &action) {
+ action.for_each_sub_node([&] (Xml_node const &subnode) {
+ if (verbose)
+ log("'", label, "' removes '", subnode.attribute_value("name", Name()), "'");
+ content_size = xml_file.remove(subnode);
+ });
+ };
+
+ try {
+ Xml_node edit_node(ram_ds.local_addr(), length);
+
+ edit_node.for_each_sub_node("remove", remove_fn);
+ edit_node.for_each_sub_node("add", add_fn);
+ if (content_size) {
+ xml_file.write_file(content_size);
+ xml_file.sync();
+ }
+ } catch (Xml_node::Invalid_syntax) {
+ error("invalid XML received from '", label, "'");
+ } catch (Genode::Xml_generator::Buffer_exceeded) {
+ error("Genode::Xml_generator::Buffer_exceeded");
+ } catch (...) {
+ error("failed to process action from '", label, "'");
+ throw;
+ }
+ }
+
+ void response_sigh(Signal_context_capability) override
+ {
+ warning(__func__, " not implemented");
+ }
+
+ size_t obtain_response()
+ {
+ warning(__func__, " not implemented");
+ return 0;
+ }
+};
+
+
+class Xml_editor::Report_root_component :
+ public Genode::Root_component
+{
+ private:
+
+ Genode::Env &_env;
+
+ Attached_rom_dataspace &_config;
+
+ Xml_file &_xml_file;
+
+ protected:
+
+ Report_session_component *_create_session(char const *args) override
+ {
+ using namespace Genode;
+
+ size_t const ram_quota =
+ Arg_string::find_arg(args, "ram_quota").aligned_size();
+
+ /* read report buffer size from session arguments */
+ size_t const buffer_size =
+ Arg_string::find_arg(args, "buffer_size").aligned_size();
+
+ size_t const session_size =
+ max(sizeof(Report_session_component), 4096U) + buffer_size;
+
+ Session_label const label = label_from_args(args);
+
+ if (ram_quota < session_size) {
+ Genode::error("insufficient ram donation from ", label);
+ throw Insufficient_ram_quota();
+ }
+
+ if (buffer_size == 0) {
+ Genode::error("zero-length report requested by ", label);
+ throw Service_denied();
+ }
+
+ try {
+ return new (md_alloc())
+ Xml_editor::Report_session_component(
+ _env, buffer_size, _xml_file, label);
+ }
+ catch (Out_of_ram) { error("Out_of_ram"); }
+ catch (Out_of_caps) { error("Out_of_caps"); }
+ catch (Service_denied) { error("Service_denied"); }
+ catch (Insufficient_cap_quota) { error("Insufficient_cap_quota"); }
+ catch (Insufficient_ram_quota) { error("Insufficient_ram_quota"); }
+ throw ~0;
+ }
+
+ public:
+
+ Report_root_component(Genode::Env &env,
+ Genode::Allocator &md_alloc,
+ Attached_rom_dataspace &config,
+ Xml_file &file)
+ :
+ Root_component(env.ep(), md_alloc),
+ _env(env), _config(config), _xml_file(file)
+ { }
+};
+
+
+struct Xml_editor::Main
+{
+ Genode::Env &env;
+
+ Attached_rom_dataspace config { env, "config" };
+
+ Heap heap { env.ram(), env.rm() };
+
+ void die(char const *msg)
+ {
+ error(msg);
+ env.parent().exit(~0);
+ sleep_forever();
+ }
+
+ /*********
+ ** VFS **
+ *********/
+
+ Xml_node vfs_config()
+ {
+ try {
+ return config.xml().sub_node("vfs");
+ } catch (Xml_node::Nonexistent_sub_node) {
+ /* XXX: spin for config update? */
+ die("no VFS configuration defined");
+ throw;
+ }
+ }
+
+ struct Io_response_handler : Vfs::Io_response_handler
+ {
+ void handle_io_response(Vfs::Vfs_handle::Context *) override { }
+ } io_response_handler;
+
+ Vfs::Global_file_system_factory vfs_factory { heap };
+
+ Vfs::Dir_file_system vfs_root {
+ env, heap, vfs_config(), io_response_handler, vfs_factory };
+
+ /* Handle on the output file */
+ Vfs::Vfs_handle *vfs_handle;
+
+
+ /************************
+ ** Init configuration **
+ ************************/
+
+ Path parse_xml_file_path()
+ {
+ try {
+ /* get user string */
+ Genode::String raw;
+ Xml_attribute attr = config.xml().attribute("output");
+ attr.value(&raw);
+ /* return canonicalized path */
+ return Path(raw.string());
+ } catch (Xml_node::Nonexistent_attribute) {
+ /* XXX: spin for config update? */
+ die("output file must be defined with ");
+ throw;
+ }
+ }
+
+ Path const xml_file_path = parse_xml_file_path();
+
+ Vfs::Vfs_handle &open_output_handle()
+ {
+ using namespace Vfs;
+ typedef Directory_service::Open_result Open_result;
+
+ unsigned mode = Directory_service::OPEN_MODE_RDWR;
+
+ Vfs_handle *handle;
+
+ Open_result res = vfs_root.open(xml_file_path.base(), mode, &handle, heap);
+ if (res == Open_result::OPEN_ERR_UNACCESSIBLE) {
+ mode |= Directory_service::OPEN_MODE_CREATE;
+ res = vfs_root.open(xml_file_path.base(), mode, &handle, heap);
+ }
+
+ switch (res) {
+ case Open_result::OPEN_OK:
+ return *handle;
+
+ case Open_result::OPEN_ERR_UNACCESSIBLE:
+ die("OPEN_ERR_UNACCESSIBLE"); break;
+ case Open_result::OPEN_ERR_NO_PERM:
+ die("OPEN_ERR_NO_PERM"); break;
+ case Open_result::OPEN_ERR_EXISTS:
+ die("OPEN_ERR_EXISTS"); break;
+ case Open_result::OPEN_ERR_NAME_TOO_LONG:
+ die("OPEN_ERR_NAME_TOO_LONG"); break;
+ case Open_result::OPEN_ERR_NO_SPACE:
+ die("OPEN_ERR_NO_SPACE"); break;
+ }
+ throw ~0;
+ }
+
+ Xml_file xml_file { heap, open_output_handle(), xml_file_path };
+
+ Sliced_heap report_heap { env.ram(), env.rm() };
+
+ Report_root_component report_root { env, report_heap, config, xml_file };
+
+ Main(Genode::Env &env) : env(env)
+ {
+ env.parent().announce(env.ep().manage(report_root));
+ }
+};
+
+
+void Component::construct(Genode::Env &env) {
+ static Xml_editor::Main inst(env); }
diff --git a/src/app/xml_editor/target.mk b/src/app/xml_editor/target.mk
new file mode 100644
index 0000000..2f24d67
--- /dev/null
+++ b/src/app/xml_editor/target.mk
@@ -0,0 +1,3 @@
+TARGET = xml_editor
+SRC_CC = component.cc
+LIBS = base vfs
diff --git a/src/app/xml_term_edit/README b/src/app/xml_term_edit/README
new file mode 100644
index 0000000..9733303
--- /dev/null
+++ b/src/app/xml_term_edit/README
@@ -0,0 +1,11 @@
+This component is a simple terminal frontend to _xml_editor_.
+
+The _add_ command opens and reads a file path passed as an agument
+and submits it to _xml_editor_ for inclusion into the file being
+edited.
+
+The _del_ command does the same, but submits a _remove_ edit action
+and _xml_editor_ will remove nodes from the file being edited using
+the XML node type and _name_ attribute.
+
+See _run/xml_term_edit.run_ for an example.
diff --git a/src/app/xml_term_edit/component.cc b/src/app/xml_term_edit/component.cc
new file mode 100644
index 0000000..a019cd5
--- /dev/null
+++ b/src/app/xml_term_edit/component.cc
@@ -0,0 +1,266 @@
+/*
+ * \brief Transactional XML editor terminal frontend
+ * \author Emery Hemingway
+ * \date 2017-04-29
+ */
+
+/*
+ * Copyright (C) 2017 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
+
+/* Cli_monitor includes */
+#include
+#include
+
+
+namespace Xml_term_edit {
+ using namespace Genode;
+ using namespace Cli_monitor;
+
+ struct Command;
+ struct Add_command;
+ struct Del_command;
+ struct Exit_command;
+ struct Main;
+}
+
+
+struct Xml_term_edit::Command : Cli_monitor::Command
+{
+ Vfs::Dir_file_system &vfs;
+ Genode::Allocator &alloc;
+ Reporter &report;
+
+ Command(char const *name,
+ char const *desc,
+ Command_registry &cmds,
+ Vfs::Dir_file_system &vfs,
+ Genode::Allocator &alloc,
+ Reporter &report)
+ :
+ Cli_monitor::Command(name, desc),
+ vfs(vfs), alloc(alloc), report(report)
+ {
+ cmds.insert(this);
+ }
+
+ void _for_each_argument(Argument_fn const &fn) const override
+ {
+ Vfs::Directory_service::Dirent de;
+ for (Vfs::file_offset i = 0; ; ++i) {
+ if (vfs.dirent("/", i, de) != Vfs::Directory_service::DIRENT_OK)
+ break;
+ switch (de.type) {
+ case Vfs::Directory_service::DIRENT_TYPE_FILE:
+ fn(Argument(de.name, ""));
+ break;
+ case Vfs::Directory_service::DIRENT_TYPE_END:
+ return;
+ default:
+ break;
+ }
+ }
+ }
+
+ void insert_file_content(Command_line &cmd, Xml_generator gen)
+ {
+ using namespace Vfs;
+
+ Path<128> path;
+ Vfs_handle *handle;
+ {
+ char name[128] = { '\0' };
+ if (cmd.argument(0, name, sizeof(name)) == false) {
+ error("Error: no configuration name specified\n");
+ return;
+ }
+ path.import(name);
+ }
+
+ Directory_service::Stat sb;
+ vfs.stat(path.base(), sb);
+
+ /* XXX: error handling */
+ if (!sb.size)
+ return;
+
+ typedef Directory_service::Open_result Open_result;
+ Open_result res = vfs.open(
+ path.base(),
+ Directory_service::OPEN_MODE_RDONLY,
+ &handle,
+ alloc);
+ switch (res) {
+ case Open_result::OPEN_OK:
+ break;
+ default:
+ error("failed to open '", path, "'");
+ /* XXX: log and write error info to the terminal */
+ return;
+ }
+ Vfs_handle::Guard guard(handle);
+
+ char buf[1024];
+ file_size offset = 0;
+ while (offset < sb.size) {
+ file_size n = 0;
+ file_size count = min(sizeof(buf), sb.size-offset);
+ handle->fs().read(handle, buf, count, n);
+ if (!n)
+ return;
+ gen.append(buf, n);
+ offset += n;
+ handle->advance_seek(n);
+ }
+ }
+};
+
+
+struct Xml_term_edit::Add_command : Xml_term_edit::Command
+{
+ Add_command(Command_registry &cmds,
+ Vfs::Dir_file_system &vfs,
+ Genode::Allocator &alloc,
+ Reporter &report)
+ : Command("add", "add a new subsystem to init", cmds, vfs, alloc, report)
+ { }
+
+ void execute(Command_line &cmd, Terminal::Session &terminal) override
+ {
+ Reporter::Xml_generator gen(report, [&] () {
+ gen.node("add", [&] () {
+ insert_file_content(cmd, gen); }); });
+ }
+};
+
+
+struct Xml_term_edit::Del_command : Xml_term_edit::Command
+{
+ Del_command(Command_registry &cmds,
+ Vfs::Dir_file_system &vfs,
+ Genode::Allocator &alloc,
+ Reporter &report)
+ : Command("del", "delete a subsystem from init", cmds, vfs, alloc, report)
+ { }
+
+ void execute(Command_line &cmd, Terminal::Session &terminal) override
+ {
+ Reporter::Xml_generator gen(report, [&] () {
+ gen.node("remove", [&] () {
+ insert_file_content(cmd, gen); }); });
+ }
+};
+
+
+struct Xml_term_edit::Exit_command : Cli_monitor::Command
+{
+ Genode::Parent &parent;
+
+ Exit_command(Command_registry &cmds, Genode::Parent &parent)
+ : Cli_monitor::Command("exit", ""), parent(parent) {
+ cmds.insert(this); }
+
+ void execute(Command_line &cmd, Terminal::Session &terminal) override {
+ parent.exit(0); }
+};
+
+
+struct Xml_term_edit::Main
+{
+ Genode::Env &env;
+
+ Attached_rom_dataspace config { env, "config" };
+
+ Xml_node vfs_config()
+ {
+ try {
+ return config.xml().sub_node("vfs");
+ } catch (Xml_node::Nonexistent_sub_node) {
+ warning("no VFS configuration defined");
+ return Xml_node("");
+ }
+ }
+
+ struct Io_response_handler : Vfs::Io_response_handler
+ {
+ void handle_io_response(Vfs::Vfs_handle::Context *) override { }
+ } io_response_handler;
+
+ Heap heap { env.ram(), env.rm() };
+
+ Vfs::Global_file_system_factory vfs_factory { heap };
+
+ Vfs::Dir_file_system vfs_root {
+ env, heap, vfs_config(), io_response_handler, vfs_factory };
+
+ Terminal::Connection term { env, "edit" };
+
+ Reporter reporter { env, "xml_editor", "edit", env.ram().avail_ram().value / 2 };
+
+ Command_registry cmds;
+
+ Cli_monitor::Command *lookup_command(char const *buf)
+ {
+ Cli_monitor::Token token(buf);
+ for (Cli_monitor::Command *curr = cmds.first(); curr; curr = curr->next())
+ if (strcmp(token.start(), curr->name().string(), token.len()) == 0
+ && strlen(curr->name().string()) == token.len())
+ return curr;
+ return nullptr;
+ }
+
+ Add_command add_command { cmds, vfs_root, heap, reporter };
+ Del_command del_command { cmds, vfs_root, heap, reporter };
+ Exit_command exit_command { cmds, env.parent() };
+
+ enum { COMMAND_MAX_LEN = 1024 };
+ char cmd_buf[COMMAND_MAX_LEN];
+
+ Line_editor editor {
+ "> ", cmd_buf, sizeof(cmd_buf), term, cmds };
+
+ void handle_term();
+
+ Signal_handler term_handler {
+ env.ep(), *this, &Main::handle_term };
+
+ Main(Genode::Env &env) : env(env)
+ {
+ reporter.enabled(true);
+ term.read_avail_sigh(term_handler);
+ }
+};
+
+
+void Xml_term_edit::Main::handle_term()
+{
+ while (term.avail() && !editor.completed()) {
+ char c = 0;
+ term.read(&c, 1);
+ editor.submit_input(c);
+ }
+
+ if (editor.completed()) {
+ auto *cmd = lookup_command(cmd_buf);
+ if (cmd) {
+ Cli_monitor::Command_line cmd_line(cmd_buf, *cmd);
+ cmd->execute(cmd_line, term);
+ }
+ editor.reset();
+ }
+}
+
+void Component::construct(Genode::Env &env) {
+ static Xml_term_edit::Main inst(env); }
diff --git a/src/app/xml_term_edit/target.mk b/src/app/xml_term_edit/target.mk
new file mode 100644
index 0000000..1f6f2e1
--- /dev/null
+++ b/src/app/xml_term_edit/target.mk
@@ -0,0 +1,5 @@
+TARGET = xml_term_edit
+SRC_CC = component.cc
+LIBS = base vfs
+
+INC_DIR += $(PRG_DIR) $(call select_from_repositories,src/app/cli_monitor)