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:
committed by
Norman Feske
parent
670aab19e3
commit
47259f7854
2
recipes/src/xml_editor/content.mk
Normal file
2
recipes/src/xml_editor/content.mk
Normal file
@@ -0,0 +1,2 @@
|
||||
SRC_DIR := src/app/xml_editor
|
||||
include $(GENODE_DIR)/repos/base/recipes/src/content.inc
|
||||
1
recipes/src/xml_editor/hash
Normal file
1
recipes/src/xml_editor/hash
Normal file
@@ -0,0 +1 @@
|
||||
2017-06-08 29beffb8c1589ab3398db5b17f432e32ade2c0f2
|
||||
8
recipes/src/xml_editor/used_apis
Normal file
8
recipes/src/xml_editor/used_apis
Normal file
@@ -0,0 +1,8 @@
|
||||
base
|
||||
block_session
|
||||
file_system_session
|
||||
os
|
||||
report_session
|
||||
rtc_session
|
||||
terminal_session
|
||||
vfs
|
||||
14
recipes/src/xml_term_edit/content.mk
Normal file
14
recipes/src/xml_term_edit/content.mk
Normal 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 $@
|
||||
1
recipes/src/xml_term_edit/hash
Normal file
1
recipes/src/xml_term_edit/hash
Normal file
@@ -0,0 +1 @@
|
||||
2017-06-08 1c40e2580a6a1abf3c72a9272cb3d9d7a1eab81d
|
||||
8
recipes/src/xml_term_edit/used_apis
Normal file
8
recipes/src/xml_term_edit/used_apis
Normal 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
163
run/xml_term_edit.run
Normal 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
25
src/app/xml_editor/README
Normal 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>
|
||||
536
src/app/xml_editor/component.cc
Normal file
536
src/app/xml_editor/component.cc
Normal 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); }
|
||||
3
src/app/xml_editor/target.mk
Normal file
3
src/app/xml_editor/target.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
TARGET = xml_editor
|
||||
SRC_CC = component.cc
|
||||
LIBS = base vfs
|
||||
11
src/app/xml_term_edit/README
Normal file
11
src/app/xml_term_edit/README
Normal 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.
|
||||
266
src/app/xml_term_edit/component.cc
Normal file
266
src/app/xml_term_edit/component.cc
Normal 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); }
|
||||
5
src/app/xml_term_edit/target.mk
Normal file
5
src/app/xml_term_edit/target.mk
Normal 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)
|
||||
Reference in New Issue
Block a user