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
This commit is contained in:
Emery Hemingway
2017-06-01 20:41:15 -05:00
committed by Norman Feske
parent 670aab19e3
commit 47259f7854
13 changed files with 1043 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
SRC_DIR := src/app/xml_editor
include $(GENODE_DIR)/repos/base/recipes/src/content.inc

View File

@@ -0,0 +1 @@
2017-06-08 29beffb8c1589ab3398db5b17f432e32ade2c0f2

View File

@@ -0,0 +1,8 @@
base
block_session
file_system_session
os
report_session
rtc_session
terminal_session
vfs

View File

@@ -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 $@

View File

@@ -0,0 +1 @@
2017-06-08 1c40e2580a6a1abf3c72a9272cb3d9d7a1eab81d

View File

@@ -0,0 +1,8 @@
base
block_session
file_system_session
os
report_session
rtc_session
terminal_session
vfs

163
run/xml_term_edit.run Normal file
View File

@@ -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 {
<config>
<default caps="256"/>
<parent-provides>
<service name="CPU"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="IRQ"/>
<service name="LOG"/>
<service name="PD"/>
<service name="RM"/>
<service name="ROM"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="drivers" caps="4096">
<resource name="RAM" quantum="32M"/>
<binary name="init"/>
<route>
<service name="ROM" label="config"> <parent label="drivers.config"/> </service>
<service name="Timer"> <child name="timer"/> </service>
<any-service> <parent/> </any-service>
</route>
<provides>
<service name="Input"/> <service name="Framebuffer"/>
</provides>
</start>
<start name="ram_fs">
<resource name="RAM" quantum="4M"/>
<provides><service name="File_system"/></provides>
<config>
<policy label_prefix="xml_editor" root="/" writeable="yes"/>
<policy label_prefix="fs_rom" root="/" writeable="no"/>
<content>
<inline name="init.config">
<config>
<default caps="128"/>
<parent-provides>
<service name="CPU"/>
<service name="LOG"/>
<service name="PD"/>
<service name="RM"/>
<service name="ROM"/>
<service name="Timer"/>
</parent-provides>
<default-route>
<any-service> <parent/> </any-service>
</default-route>
</config>
</inline>
</content>
</config>
</start>
<start name="fs_rom">
<resource name="RAM" quantum="2M"/>
<provides><service name="ROM"/></provides>
</start>
<start name="xml_editor">
<resource name="RAM" quantum="4M"/>
<provides> <service name="Report"/> </provides>
<config output="init.config">
<vfs> <fs/> </vfs>
</config>
<route>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="init">
<resource name="RAM" quantum="4M"/>
<configfile name="init.config"/>
<route>
<service name="ROM" label="init.config"> <child name="fs_rom"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="terminal">
<resource name="RAM" quantum="2M"/>
<provides><service name="Terminal"/></provides>
<config>
<keyboard layout="us"/>
</config>
<route>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="xml_term_edit">
<resource name="RAM" quantum="4M"/>
<config>
<vfs>
<inline name="test-log.subsystem">
<start name="test-log">
<resource name="RAM" quantum="2M"/>
</start>
</inline>
<inline name="test-timer.subsystem">
<start name="test-timer">
<resource name="RAM" quantum="2M"/>
</start>
</inline>
<inline name="junk">
<start name="junk"/>
</inline>
</vfs>
</config>
<route>
<service name="Report"> <child name="xml_editor"/> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
</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

25
src/app/xml_editor/README Normal file
View File

@@ -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
----------
! <add>
! <... name="...">
! ...
! </...>
! </add>
Remove action
-------------
! <remove>
! <... name="...">
! ...
! </...>
! </remove>

View File

@@ -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 <vfs/file_system_factory.h>
#include <vfs/dir_file_system.h>
#include <report_session/report_session.h>
#include <root/component.h>
#include <base/attached_ram_dataspace.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <base/sleep.h>
#include <base/log.h>
#include <base/component.h>
#include <util/xml_generator.h>
namespace Xml_editor {
using namespace Genode;
typedef Path<Vfs::MAX_PATH_LEN/2> 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<typename FUNC>
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, "<config/>", 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<Report::Session>
{
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<char const>(), 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<Report_session_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<Report_session_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<Path::capacity()> 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 <config output=\"...\"/>");
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); }

View File

@@ -0,0 +1,3 @@
TARGET = xml_editor
SRC_CC = component.cc
LIBS = base vfs

View File

@@ -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.

View File

@@ -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 <os/reporter.h>
#include <vfs/file_system_factory.h>
#include <vfs/dir_file_system.h>
#include <terminal_session/connection.h>
#include <base/attached_rom_dataspace.h>
#include <base/heap.h>
#include <base/component.h>
/* Cli_monitor includes */
#include <command_line.h>
#include <line_editor.h>
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("<vfs><fs writeable=\"0\"/></vfs>");
}
}
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<Main> 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); }

View File

@@ -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)