From 47259f7854ac327af393547ed4ea65ace1dbe5b1 Mon Sep 17 00:00:00 2001 From: Emery Hemingway Date: Thu, 1 Jun 2017 20:41:15 -0500 Subject: [PATCH] Transactional XML editor This patch introduces a single-file XML editor driven by Report clients. The xml_editor component manages edits to a file and the xml_term_edit component is a terminal-based frontend. Fix #75 --- recipes/src/xml_editor/content.mk | 2 + recipes/src/xml_editor/hash | 1 + recipes/src/xml_editor/used_apis | 8 + recipes/src/xml_term_edit/content.mk | 14 + recipes/src/xml_term_edit/hash | 1 + recipes/src/xml_term_edit/used_apis | 8 + run/xml_term_edit.run | 163 ++++++++ src/app/xml_editor/README | 25 ++ src/app/xml_editor/component.cc | 536 +++++++++++++++++++++++++++ src/app/xml_editor/target.mk | 3 + src/app/xml_term_edit/README | 11 + src/app/xml_term_edit/component.cc | 266 +++++++++++++ src/app/xml_term_edit/target.mk | 5 + 13 files changed, 1043 insertions(+) create mode 100644 recipes/src/xml_editor/content.mk create mode 100644 recipes/src/xml_editor/hash create mode 100644 recipes/src/xml_editor/used_apis create mode 100644 recipes/src/xml_term_edit/content.mk create mode 100644 recipes/src/xml_term_edit/hash create mode 100644 recipes/src/xml_term_edit/used_apis create mode 100644 run/xml_term_edit.run create mode 100644 src/app/xml_editor/README create mode 100644 src/app/xml_editor/component.cc create mode 100644 src/app/xml_editor/target.mk create mode 100644 src/app/xml_term_edit/README create mode 100644 src/app/xml_term_edit/component.cc create mode 100644 src/app/xml_term_edit/target.mk 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)