mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-20 14:13:09 +00:00
cli_monitor: read subsystem configs from VFS
This patch changes the way how CLI monitor obtains its subsystem configurations. Originally, this information was provided via the Genode::config mechanism. But for managing complex scenarios, the config node becomes very complex. Hence, it is preferrable to have a distinct file for each subsystem configuration. The CLI monitor scans the directory '/subsystems' for files ending with ".subsystem". Each file has the same syntax as the formerly used subsystem nodes.
This commit is contained in:
parent
f917728ecb
commit
b4ebefd616
@ -63,6 +63,18 @@ class Child_registry : public List<Child>
|
|||||||
snprintf(suffix, sizeof(suffix), ".%d", cnt + 1);
|
snprintf(suffix, sizeof(suffix), ".%d", cnt + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call functor 'fn' for each child
|
||||||
|
*
|
||||||
|
* The functor receives the child name as 'char const *'.
|
||||||
|
*/
|
||||||
|
template <typename FN>
|
||||||
|
void for_each_child_name(FN const &fn) const
|
||||||
|
{
|
||||||
|
for (Child const *child = first() ; child; child = child->next())
|
||||||
|
fn(child->name());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _CHILD_REGISTRY_H_ */
|
#endif /* _CHILD_REGISTRY_H_ */
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
#include <child_registry.h>
|
#include <child_registry.h>
|
||||||
#include <gdb_prefix.h>
|
#include <gdb_prefix.h>
|
||||||
#include <format_util.h>
|
#include <format_util.h>
|
||||||
#include <process_arg_registry.h>
|
#include <subsystem_config_registry.h>
|
||||||
|
|
||||||
class Gdb_command_child : public Child
|
class Gdb_command_child : public Child
|
||||||
{
|
{
|
||||||
@ -123,8 +123,10 @@ class Gdb_command_child : public Child
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
struct Gdb_command : Command
|
class Gdb_command : public Command
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
typedef Genode::Xml_node Xml_node;
|
typedef Genode::Xml_node Xml_node;
|
||||||
typedef Genode::Signal_context_capability Signal_context_capability;
|
typedef Genode::Signal_context_capability Signal_context_capability;
|
||||||
|
|
||||||
@ -133,70 +135,10 @@ struct Gdb_command : Command
|
|||||||
Ram &_ram;
|
Ram &_ram;
|
||||||
Child_registry &_children;
|
Child_registry &_children;
|
||||||
Genode::Cap_session &_cap;
|
Genode::Cap_session &_cap;
|
||||||
Xml_node _config;
|
Subsystem_config_registry &_subsystem_configs;
|
||||||
Process_arg_registry &_process_args;
|
|
||||||
List<Argument> _arguments;
|
|
||||||
Signal_context_capability _yield_response_sigh_cap;
|
Signal_context_capability _yield_response_sigh_cap;
|
||||||
Signal_context_capability _kill_gdb_sig_cap;
|
Signal_context_capability _kill_gdb_sig_cap;
|
||||||
|
|
||||||
Gdb_command(Ram &ram, Genode::Cap_session &cap, Child_registry &children,
|
|
||||||
Xml_node config, Process_arg_registry &process_args,
|
|
||||||
Signal_context_capability yield_response_sigh_cap,
|
|
||||||
Signal_context_capability kill_gdb_sig_cap)
|
|
||||||
:
|
|
||||||
Command("gdb", "create new subsystem with GDB"),
|
|
||||||
_ram(ram), _children(children), _cap(cap), _config(config),
|
|
||||||
_process_args(process_args),
|
|
||||||
_yield_response_sigh_cap(yield_response_sigh_cap),
|
|
||||||
_kill_gdb_sig_cap(kill_gdb_sig_cap)
|
|
||||||
{
|
|
||||||
/* scan config for possible subsystem arguments */
|
|
||||||
try {
|
|
||||||
Xml_node node = _config.sub_node("subsystem");
|
|
||||||
for (;; node = node.next("subsystem")) {
|
|
||||||
|
|
||||||
char name[Parameter::NAME_MAX_LEN];
|
|
||||||
try { node.attribute("name").value(name, sizeof(name)); }
|
|
||||||
catch (Xml_node::Nonexistent_attribute) {
|
|
||||||
PWRN("Missing name in '<subsystem>' configuration");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
char const *prefix = "config: ";
|
|
||||||
size_t const prefix_len = strlen(prefix);
|
|
||||||
|
|
||||||
char help[Parameter::SHORT_HELP_MAX_LEN + prefix_len];
|
|
||||||
strncpy(help, prefix, ~0);
|
|
||||||
try { node.attribute("help").value(help + prefix_len,
|
|
||||||
sizeof(help) - prefix_len); }
|
|
||||||
catch (Xml_node::Nonexistent_attribute) {
|
|
||||||
PWRN("Missing help in '<subsystem>' configuration");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_arguments.insert(new Argument(name, help));
|
|
||||||
}
|
|
||||||
} catch (Xml_node::Nonexistent_sub_node) { /* end of list */ }
|
|
||||||
|
|
||||||
add_parameter(new Parameter("--ram", Parameter::NUMBER, "initial RAM quota"));
|
|
||||||
add_parameter(new Parameter("--ram-limit", Parameter::NUMBER, "limit for expanding RAM quota"));
|
|
||||||
add_parameter(new Parameter("--gdb-ram-preserve", Parameter::NUMBER,
|
|
||||||
"RAM quota which GDB monitor should preserve for itself (default: 5M)"));
|
|
||||||
add_parameter(new Parameter("--verbose", Parameter::VOID, "show diagnostics"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup subsystem in config
|
|
||||||
*/
|
|
||||||
Xml_node _subsystem_node(char const *name)
|
|
||||||
{
|
|
||||||
Xml_node node = _config.sub_node("subsystem");
|
|
||||||
for (;; node = node.next("subsystem")) {
|
|
||||||
if (node.attribute("name").has_value(name))
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the config node for the GDB subsystem
|
* Generate the config node for the GDB subsystem
|
||||||
*/
|
*/
|
||||||
@ -381,52 +323,19 @@ struct Gdb_command : Command
|
|||||||
"GDB target binary.\n");
|
"GDB target binary.\n");
|
||||||
throw Child_configuration_failed();
|
throw Child_configuration_failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void execute(Command_line &cmd, Terminal::Session &terminal)
|
void _execute_subsystem(char const *name, Command_line &cmd,
|
||||||
|
Terminal::Session &terminal,
|
||||||
|
Xml_node subsystem_node)
|
||||||
{
|
{
|
||||||
/* check if the GDB-related ROM modules are available */
|
|
||||||
try {
|
|
||||||
Genode::Rom_connection gdb_command_config("gdb_command_config");
|
|
||||||
Genode::Rom_connection terminal_crosslink("terminal_crosslink");
|
|
||||||
Genode::Rom_connection noux("noux");
|
|
||||||
Genode::Rom_connection gdb_monitor("gdb_monitor");
|
|
||||||
} catch (Genode::Rom_connection::Rom_connection_failed) {
|
|
||||||
tprintf(terminal, "Error: The 'gdb' command needs the following ROM "
|
|
||||||
"modules (of which some are currently missing): "
|
|
||||||
"gdb_command_config, terminal_crosslink, noux, ",
|
|
||||||
"gdb_monitor\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Genode::Number_of_bytes ram = 0;
|
Genode::Number_of_bytes ram = 0;
|
||||||
Genode::Number_of_bytes ram_limit = 0;
|
Genode::Number_of_bytes ram_limit = 0;
|
||||||
Genode::Number_of_bytes gdb_ram_preserve = 10*1024*1024;
|
Genode::Number_of_bytes gdb_ram_preserve = 10*1024*1024;
|
||||||
|
|
||||||
char name[128];
|
|
||||||
name[0] = 0;
|
|
||||||
if (cmd.argument(0, name, sizeof(name)) == false) {
|
|
||||||
tprintf(terminal, "Error: no configuration name specified\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char buf[128];
|
|
||||||
if (cmd.argument(1, buf, sizeof(buf))) {
|
|
||||||
tprintf(terminal, "Error: unexpected argument \"%s\"\n", buf);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check if a configuration for the subsystem exists */
|
|
||||||
try { _subsystem_node(name); }
|
|
||||||
catch (Xml_node::Nonexistent_sub_node) {
|
|
||||||
tprintf(terminal, "Error: no configuration for \"%s\"\n", name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* read default RAM quota from config */
|
/* read default RAM quota from config */
|
||||||
try {
|
try {
|
||||||
Xml_node rsc = _subsystem_node(name).sub_node("resource");
|
Xml_node rsc = subsystem_node.sub_node("resource");
|
||||||
for (;; rsc = rsc.next("resource")) {
|
for (;; rsc = rsc.next("resource")) {
|
||||||
if (rsc.attribute("name").has_value("RAM")) {
|
if (rsc.attribute("name").has_value("RAM")) {
|
||||||
rsc.attribute("quantum").value(&ram);
|
rsc.attribute("quantum").value(&ram);
|
||||||
@ -459,7 +368,7 @@ struct Gdb_command : Command
|
|||||||
char binary_name[128];
|
char binary_name[128];
|
||||||
strncpy(binary_name, name, sizeof(binary_name));
|
strncpy(binary_name, name, sizeof(binary_name));
|
||||||
try {
|
try {
|
||||||
Xml_node bin = _subsystem_node(name).sub_node("binary");
|
Xml_node bin = subsystem_node.sub_node("binary");
|
||||||
bin.attribute("name").value(binary_name, sizeof(binary_name));
|
bin.attribute("name").value(binary_name, sizeof(binary_name));
|
||||||
} catch (...) { }
|
} catch (...) { }
|
||||||
|
|
||||||
@ -488,7 +397,7 @@ struct Gdb_command : Command
|
|||||||
const char *target_config_addr = 0;
|
const char *target_config_addr = 0;
|
||||||
size_t target_config_size = 0;
|
size_t target_config_size = 0;
|
||||||
try {
|
try {
|
||||||
Xml_node target_config_node = _subsystem_node(name).sub_node("config");
|
Xml_node target_config_node = subsystem_node.sub_node("config");
|
||||||
target_config_addr = target_config_node.addr();
|
target_config_addr = target_config_node.addr();
|
||||||
target_config_size = target_config_node.size();
|
target_config_size = target_config_node.size();
|
||||||
} catch (...) { }
|
} catch (...) { }
|
||||||
@ -537,12 +446,101 @@ struct Gdb_command : Command
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_process_args.list.insert(&child->argument);
|
|
||||||
_children.insert(child);
|
_children.insert(child);
|
||||||
child->start();
|
child->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Argument> &arguments() { return _arguments; }
|
public:
|
||||||
|
|
||||||
|
Gdb_command(Ram &ram, Genode::Cap_session &cap, Child_registry &children,
|
||||||
|
Subsystem_config_registry &subsustem_configs,
|
||||||
|
Signal_context_capability yield_response_sigh_cap,
|
||||||
|
Signal_context_capability kill_gdb_sig_cap)
|
||||||
|
:
|
||||||
|
Command("gdb", "create new subsystem with GDB"),
|
||||||
|
_ram(ram), _children(children), _cap(cap),
|
||||||
|
_subsystem_configs(subsustem_configs),
|
||||||
|
_yield_response_sigh_cap(yield_response_sigh_cap),
|
||||||
|
_kill_gdb_sig_cap(kill_gdb_sig_cap)
|
||||||
|
{
|
||||||
|
add_parameter(new Parameter("--ram", Parameter::NUMBER, "initial RAM quota"));
|
||||||
|
add_parameter(new Parameter("--ram-limit", Parameter::NUMBER, "limit for expanding RAM quota"));
|
||||||
|
add_parameter(new Parameter("--gdb-ram-preserve", Parameter::NUMBER,
|
||||||
|
"RAM quota which GDB monitor should preserve for itself (default: 5M)"));
|
||||||
|
add_parameter(new Parameter("--verbose", Parameter::VOID, "show diagnostics"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _for_each_argument(Argument_fn const &fn) const override
|
||||||
|
{
|
||||||
|
/* functor for processing a subsystem configuration */
|
||||||
|
auto process_subsystem_config_fn = [&] (Genode::Xml_node node) {
|
||||||
|
|
||||||
|
char name[Parameter::Name::size()];
|
||||||
|
try { node.attribute("name").value(name, sizeof(name)); }
|
||||||
|
catch (Xml_node::Nonexistent_attribute) {
|
||||||
|
PWRN("Missing name in '<subsystem>' configuration");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *prefix = "config: ";
|
||||||
|
size_t const prefix_len = strlen(prefix);
|
||||||
|
|
||||||
|
char help[Parameter::Short_help::size() + prefix_len];
|
||||||
|
strncpy(help, prefix, ~0);
|
||||||
|
try { node.attribute("help").value(help + prefix_len,
|
||||||
|
sizeof(help) - prefix_len); }
|
||||||
|
catch (Xml_node::Nonexistent_attribute) {
|
||||||
|
PWRN("Missing help in '<subsystem>' configuration");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Argument arg(name, help);
|
||||||
|
fn(arg);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* scan subsystem config registry for possible subsystem arguments */
|
||||||
|
_subsystem_configs.for_each_config(process_subsystem_config_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute(Command_line &cmd, Terminal::Session &terminal)
|
||||||
|
{
|
||||||
|
/* check if the GDB-related ROM modules are available */
|
||||||
|
try {
|
||||||
|
Genode::Rom_connection gdb_command_config("gdb_command_config");
|
||||||
|
Genode::Rom_connection terminal_crosslink("terminal_crosslink");
|
||||||
|
Genode::Rom_connection noux("noux");
|
||||||
|
Genode::Rom_connection gdb_monitor("gdb_monitor");
|
||||||
|
} catch (Genode::Rom_connection::Rom_connection_failed) {
|
||||||
|
tprintf(terminal, "Error: The 'gdb' command needs the following ROM "
|
||||||
|
"modules (of which some are currently missing): "
|
||||||
|
"gdb_command_config, terminal_crosslink, noux, ",
|
||||||
|
"gdb_monitor\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char name[128];
|
||||||
|
name[0] = 0;
|
||||||
|
if (cmd.argument(0, name, sizeof(name)) == false) {
|
||||||
|
tprintf(terminal, "Error: no configuration name specified\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[128];
|
||||||
|
if (cmd.argument(1, buf, sizeof(buf))) {
|
||||||
|
tprintf(terminal, "Error: unexpected argument \"%s\"\n", buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_subsystem_configs.for_config(name, [&] (Genode::Xml_node node)
|
||||||
|
{
|
||||||
|
_execute_subsystem(name, cmd, terminal, node);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Subsystem_config_registry::Nonexistent_subsystem_config) {
|
||||||
|
tprintf(terminal, "Error: no configuration for \"%s\"\n", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _GDB_COMMAND_H_ */
|
#endif /* _GDB_COMMAND_H_ */
|
||||||
|
@ -16,31 +16,36 @@
|
|||||||
|
|
||||||
/* local includes */
|
/* local includes */
|
||||||
#include <child_registry.h>
|
#include <child_registry.h>
|
||||||
#include <process_arg_registry.h>
|
|
||||||
|
|
||||||
struct Kill_command : Command
|
struct Kill_command : Command
|
||||||
{
|
{
|
||||||
Child_registry &_children;
|
Child_registry &_children;
|
||||||
|
|
||||||
Process_arg_registry &_process_args;
|
|
||||||
|
|
||||||
void _destroy_child(Child *child, Terminal::Session &terminal)
|
void _destroy_child(Child *child, Terminal::Session &terminal)
|
||||||
{
|
{
|
||||||
tprintf(terminal, "destroying subsystem '%s'\n", child->name());
|
tprintf(terminal, "destroying subsystem '%s'\n", child->name());
|
||||||
_process_args.list.remove(&child->argument);
|
|
||||||
_children.remove(child);
|
_children.remove(child);
|
||||||
Genode::destroy(Genode::env()->heap(), child);
|
Genode::destroy(Genode::env()->heap(), child);
|
||||||
}
|
}
|
||||||
|
|
||||||
Kill_command(Child_registry &children, Process_arg_registry &process_args)
|
Kill_command(Child_registry &children)
|
||||||
:
|
:
|
||||||
Command("kill", "destroy subsystem"),
|
Command("kill", "destroy subsystem"),
|
||||||
_children(children),
|
_children(children)
|
||||||
_process_args(process_args)
|
|
||||||
{
|
{
|
||||||
add_parameter(new Parameter("--all", Parameter::VOID, "kill all subsystems"));
|
add_parameter(new Parameter("--all", Parameter::VOID, "kill all subsystems"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _for_each_argument(Argument_fn const &fn) const override
|
||||||
|
{
|
||||||
|
auto child_name_fn = [&] (char const *child_name) {
|
||||||
|
Argument arg(child_name, "");
|
||||||
|
fn(arg);
|
||||||
|
};
|
||||||
|
|
||||||
|
_children.for_each_child_name(child_name_fn);
|
||||||
|
}
|
||||||
|
|
||||||
void execute(Command_line &cmd, Terminal::Session &terminal)
|
void execute(Command_line &cmd, Terminal::Session &terminal)
|
||||||
{
|
{
|
||||||
bool const kill_all = cmd.parameter_exists("--all");
|
bool const kill_all = cmd.parameter_exists("--all");
|
||||||
@ -68,8 +73,6 @@ struct Kill_command : Command
|
|||||||
|
|
||||||
tprintf(terminal, "Error: subsystem '%s' does not exist\n", label);
|
tprintf(terminal, "Error: subsystem '%s' does not exist\n", label);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Argument> &arguments() { return _process_args.list; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _KILL_COMMAND_H_ */
|
#endif /* _KILL_COMMAND_H_ */
|
||||||
|
@ -35,18 +35,14 @@ using Genode::off_t;
|
|||||||
|
|
||||||
struct Completable
|
struct Completable
|
||||||
{
|
{
|
||||||
template <size_t MAX_LEN> struct String
|
typedef Genode::String<64> Name;
|
||||||
{
|
typedef Genode::String<160> Short_help;
|
||||||
char buf[MAX_LEN];
|
|
||||||
String(char const *string) { strncpy(buf, string, sizeof(buf)); }
|
|
||||||
};
|
|
||||||
|
|
||||||
enum { NAME_MAX_LEN = 64, SHORT_HELP_MAX_LEN = 160 };
|
Name const _name;
|
||||||
String<NAME_MAX_LEN> const _name;
|
Short_help const _short_help;
|
||||||
String<SHORT_HELP_MAX_LEN> const _short_help;
|
|
||||||
|
|
||||||
char const *name() const { return _name.buf; }
|
Name name() const { return _name; }
|
||||||
char const *short_help() const { return _short_help.buf; }
|
Short_help short_help() const { return _short_help; }
|
||||||
|
|
||||||
Completable(char const *name, char const *short_help)
|
Completable(char const *name, char const *short_help)
|
||||||
: _name(name), _short_help(short_help) { }
|
: _name(name), _short_help(short_help) { }
|
||||||
@ -56,7 +52,7 @@ struct Completable
|
|||||||
/**
|
/**
|
||||||
* Representation of normal command-line argument
|
* Representation of normal command-line argument
|
||||||
*/
|
*/
|
||||||
struct Argument : List<Argument>::Element, Completable
|
struct Argument : Completable
|
||||||
{
|
{
|
||||||
Argument(char const *name, char const *short_help)
|
Argument(char const *name, char const *short_help)
|
||||||
: Completable(name, short_help) { }
|
: Completable(name, short_help) { }
|
||||||
@ -103,6 +99,14 @@ struct Command : List<Command>::Element, Completable
|
|||||||
{
|
{
|
||||||
List<Parameter> _parameters;
|
List<Parameter> _parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functor that takes a command 'Argument' object as argument
|
||||||
|
*/
|
||||||
|
struct Argument_fn
|
||||||
|
{
|
||||||
|
virtual void operator () (Argument const &) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
Command(char const *name, char const *short_help)
|
Command(char const *name, char const *short_help)
|
||||||
: Completable(name, short_help) { }
|
: Completable(name, short_help) { }
|
||||||
|
|
||||||
@ -112,16 +116,28 @@ struct Command : List<Command>::Element, Completable
|
|||||||
|
|
||||||
List<Parameter> ¶meters() { return _parameters; }
|
List<Parameter> ¶meters() { return _parameters; }
|
||||||
|
|
||||||
/**
|
|
||||||
* To be overridden by commands that accept auto-completion of arguments
|
|
||||||
*/
|
|
||||||
virtual List<Argument> &arguments()
|
|
||||||
{
|
|
||||||
static List<Argument> empty;
|
|
||||||
return empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void execute(Command_line &, Terminal::Session &terminal) = 0;
|
virtual void execute(Command_line &, Terminal::Session &terminal) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Command-specific support for 'for_each_argument'
|
||||||
|
*/
|
||||||
|
virtual void _for_each_argument(Argument_fn const &fn) const { };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute functor 'fn' for each command argument
|
||||||
|
*/
|
||||||
|
template <typename FN>
|
||||||
|
void for_each_argument(FN const &fn) const
|
||||||
|
{
|
||||||
|
struct _Fn : Argument_fn
|
||||||
|
{
|
||||||
|
FN const &fn;
|
||||||
|
void operator () (Argument const &arg) const override { fn(arg); }
|
||||||
|
_Fn(FN const &fn) : fn(fn) { }
|
||||||
|
} _fn(fn);
|
||||||
|
|
||||||
|
_for_each_argument(_fn);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -162,22 +178,24 @@ struct Argument_tracker
|
|||||||
* Return true if there is exactly one complete match and no additional
|
* Return true if there is exactly one complete match and no additional
|
||||||
* partial matches
|
* partial matches
|
||||||
*/
|
*/
|
||||||
template <typename T>
|
static bool _one_matching_argument(char const *str, size_t str_len,
|
||||||
static bool _one_match(char const *str, size_t str_len,
|
Command const &command)
|
||||||
List<T> &list)
|
|
||||||
{
|
{
|
||||||
unsigned complete_cnt = 0, partial_cnt = 0;
|
unsigned complete_cnt = 0, partial_cnt = 0;
|
||||||
|
|
||||||
Token tag(str, str_len);
|
auto argument_fn = [&] (Argument const &arg) {
|
||||||
for (T *curr = list.first(); curr; curr = curr->next()) {
|
|
||||||
if (strcmp(tag.start(), curr->name(), tag.len()) == 0) {
|
if (strcmp(arg.name().string(), str, str_len) == 0) {
|
||||||
partial_cnt++;
|
partial_cnt++;
|
||||||
if (strlen(curr->name()) == tag.len())
|
|
||||||
|
if (strlen(arg.name().string()) == str_len)
|
||||||
complete_cnt++;
|
complete_cnt++;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return partial_cnt == 1 && complete_cnt == 1;;
|
command.for_each_argument(argument_fn);
|
||||||
|
|
||||||
|
return partial_cnt == 1 && complete_cnt == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -191,8 +209,8 @@ struct Argument_tracker
|
|||||||
{
|
{
|
||||||
Token tag(str, str_len);
|
Token tag(str, str_len);
|
||||||
for (T *curr = list.first(); curr; curr = curr->next())
|
for (T *curr = list.first(); curr; curr = curr->next())
|
||||||
if (strcmp(tag.start(), curr->name(), tag.len()) == 0
|
if (strcmp(tag.start(), curr->name().string(), tag.len()) == 0
|
||||||
&& strlen(curr->name()) == tag.len())
|
&& strlen(curr->name().string()) == tag.len())
|
||||||
return curr;
|
return curr;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -238,7 +256,7 @@ struct Argument_tracker
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!token_may_be_incomplete
|
if (!token_may_be_incomplete
|
||||||
|| _one_match(token.start(), token.len(), _command.arguments()))
|
|| _one_matching_argument(token.start(), token.len(), _command))
|
||||||
_state = EXPECT_SPACE_BEFORE_ARG;
|
_state = EXPECT_SPACE_BEFORE_ARG;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -469,7 +487,7 @@ class Line_editor
|
|||||||
{
|
{
|
||||||
Token cmd(_buf, _cursor_pos);
|
Token cmd(_buf, _cursor_pos);
|
||||||
for (Command *curr = _commands.first(); curr; curr = curr->next())
|
for (Command *curr = _commands.first(); curr; curr = curr->next())
|
||||||
if (strcmp(cmd.start(), curr->name(), cmd.len()) == 0
|
if (strcmp(cmd.start(), curr->name().string(), cmd.len()) == 0
|
||||||
&& _cursor_pos > cmd.len())
|
&& _cursor_pos > cmd.len())
|
||||||
return curr;
|
return curr;
|
||||||
return 0;
|
return 0;
|
||||||
@ -482,7 +500,7 @@ class Line_editor
|
|||||||
|
|
||||||
unsigned num_partial_matches = 0;
|
unsigned num_partial_matches = 0;
|
||||||
for (T *curr = list.first(); curr; curr = curr->next()) {
|
for (T *curr = list.first(); curr; curr = curr->next()) {
|
||||||
if (strcmp(token.start(), curr->name(), token.len()) != 0)
|
if (strcmp(token.start(), curr->name().string(), token.len()) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
num_partial_matches++;
|
num_partial_matches++;
|
||||||
@ -490,6 +508,22 @@ class Line_editor
|
|||||||
return num_partial_matches;
|
return num_partial_matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned _num_matching_arguments(char const *str, size_t str_len,
|
||||||
|
Command const &command) const
|
||||||
|
{
|
||||||
|
unsigned num_matches = 0;
|
||||||
|
|
||||||
|
auto argument_fn = [&] (Argument const &arg) {
|
||||||
|
|
||||||
|
if (strcmp(arg.name().string(), str, str_len) == 0)
|
||||||
|
num_matches++;
|
||||||
|
};
|
||||||
|
|
||||||
|
command.for_each_argument(argument_fn);
|
||||||
|
|
||||||
|
return num_matches;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine the name-column width of list of partial matches
|
* Determine the name-column width of list of partial matches
|
||||||
*/
|
*/
|
||||||
@ -501,16 +535,35 @@ class Line_editor
|
|||||||
|
|
||||||
size_t max_name_len = 0;
|
size_t max_name_len = 0;
|
||||||
for (T *curr = list.first(); curr; curr = curr->next()) {
|
for (T *curr = list.first(); curr; curr = curr->next()) {
|
||||||
if (strcmp(token.start(), curr->name(), token.len()) != 0)
|
if (strcmp(token.start(), curr->name().string(), token.len()) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
size_t const name_len = strlen(curr->name())
|
size_t const name_len = strlen(curr->name().string())
|
||||||
+ strlen(curr->name_suffix());
|
+ strlen(curr->name_suffix());
|
||||||
max_name_len = max(max_name_len, name_len);
|
max_name_len = max(max_name_len, name_len);
|
||||||
}
|
}
|
||||||
return max_name_len;
|
return max_name_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned _width_of_matching_arguments(char const *str, size_t str_len,
|
||||||
|
Command const &command) const
|
||||||
|
{
|
||||||
|
size_t max_name_len = 0;
|
||||||
|
|
||||||
|
auto argument_fn = [&] (Argument const &arg) {
|
||||||
|
|
||||||
|
if (strcmp(arg.name().string(), str, str_len) == 0) {
|
||||||
|
size_t const name_len = strlen(arg.name().string());
|
||||||
|
if (name_len > max_name_len)
|
||||||
|
max_name_len = name_len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
command.for_each_argument(argument_fn);
|
||||||
|
|
||||||
|
return max_name_len;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
char const *_any_partial_match_name(char const *str, size_t str_len,
|
char const *_any_partial_match_name(char const *str, size_t str_len,
|
||||||
List<T> &list)
|
List<T> &list)
|
||||||
@ -518,12 +571,28 @@ class Line_editor
|
|||||||
Token token(str, str_len);
|
Token token(str, str_len);
|
||||||
|
|
||||||
for (T *curr = list.first(); curr; curr = curr->next())
|
for (T *curr = list.first(); curr; curr = curr->next())
|
||||||
if (strcmp(token.start(), curr->name(), token.len()) == 0)
|
if (strcmp(token.start(), curr->name().string(), token.len()) == 0)
|
||||||
return curr->name();
|
return curr->name().string();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Argument::Name _any_matching_argument(char const *str, size_t str_len,
|
||||||
|
Command const &command) const
|
||||||
|
{
|
||||||
|
Argument::Name name;
|
||||||
|
|
||||||
|
auto argument_fn = [&] (Argument const &arg) {
|
||||||
|
|
||||||
|
if (strcmp(arg.name().string(), str, str_len) == 0)
|
||||||
|
name = arg.name();
|
||||||
|
};
|
||||||
|
|
||||||
|
command.for_each_argument(argument_fn);
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void _list_partial_matches(char const *str, size_t str_len,
|
void _list_partial_matches(char const *str, size_t str_len,
|
||||||
unsigned pad, List<T> &list)
|
unsigned pad, List<T> &list)
|
||||||
@ -531,23 +600,47 @@ class Line_editor
|
|||||||
Token token(str, str_len);
|
Token token(str, str_len);
|
||||||
|
|
||||||
for (T *curr = list.first(); curr; curr = curr->next()) {
|
for (T *curr = list.first(); curr; curr = curr->next()) {
|
||||||
if (strcmp(token.start(), curr->name(), token.len()) != 0)
|
if (strcmp(token.start(), curr->name().string(), token.len()) != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
_write_newline();
|
_write_newline();
|
||||||
_write_spaces(2);
|
_write_spaces(2);
|
||||||
_write(curr->name());
|
_write(curr->name().string());
|
||||||
_write_spaces(1);
|
_write_spaces(1);
|
||||||
_write(curr->name_suffix());
|
_write(curr->name_suffix());
|
||||||
|
|
||||||
/* pad short help with whitespaces */
|
/* pad short help with whitespaces */
|
||||||
size_t const name_len = strlen(curr->name())
|
size_t const name_len = strlen(curr->name().string())
|
||||||
+ strlen(curr->name_suffix());
|
+ strlen(curr->name_suffix());
|
||||||
_write_spaces(pad + 3 - name_len);
|
_write_spaces(pad + 3 - name_len);
|
||||||
_write(curr->short_help());
|
_write(curr->short_help().string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _list_matching_arguments(char const *str, size_t str_len,
|
||||||
|
unsigned pad, Command const &command)
|
||||||
|
{
|
||||||
|
auto argument_fn = [&] (Argument const &arg) {
|
||||||
|
|
||||||
|
if (strcmp(arg.name().string(), str, str_len) == 0) {
|
||||||
|
|
||||||
|
_write_newline();
|
||||||
|
_write_spaces(2);
|
||||||
|
_write(arg.name().string());
|
||||||
|
_write_spaces(1);
|
||||||
|
_write(arg.name_suffix());
|
||||||
|
|
||||||
|
/* pad short help with whitespaces */
|
||||||
|
size_t const name_len = strlen(arg.name().string())
|
||||||
|
+ strlen(arg.name_suffix());
|
||||||
|
_write_spaces(pad + 3 - name_len);
|
||||||
|
_write(arg.short_help().string());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
command.for_each_argument(argument_fn);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void _do_completion(char const *str, size_t str_len, List<T> &list)
|
void _do_completion(char const *str, size_t str_len, List<T> &list)
|
||||||
{
|
{
|
||||||
@ -556,7 +649,7 @@ class Line_editor
|
|||||||
/* look up completable token */
|
/* look up completable token */
|
||||||
T *partial_match = 0;
|
T *partial_match = 0;
|
||||||
for (T *curr = list.first(); curr; curr = curr->next()) {
|
for (T *curr = list.first(); curr; curr = curr->next()) {
|
||||||
if (strcmp(token.start(), curr->name(), token.len()) == 0) {
|
if (strcmp(token.start(), curr->name().string(), token.len()) == 0) {
|
||||||
partial_match = curr;
|
partial_match = curr;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -565,8 +658,27 @@ class Line_editor
|
|||||||
if (!partial_match)
|
if (!partial_match)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (unsigned i = token.len(); i < strlen(partial_match->name()); i++)
|
for (unsigned i = token.len(); i < strlen(partial_match->name().string()); i++)
|
||||||
_insert_character(partial_match->name()[i]);
|
_insert_character(partial_match->name().string()[i]);
|
||||||
|
|
||||||
|
_insert_character(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _do_argument_completion(char const *str, size_t str_len,
|
||||||
|
Command const &command)
|
||||||
|
{
|
||||||
|
Argument::Name partial_match;
|
||||||
|
|
||||||
|
auto argument_fn = [&] (Argument const &arg) {
|
||||||
|
|
||||||
|
if (strcmp(arg.name().string(), str, str_len) == 0)
|
||||||
|
partial_match = arg.name();
|
||||||
|
};
|
||||||
|
|
||||||
|
command.for_each_argument(argument_fn);
|
||||||
|
|
||||||
|
for (unsigned i = str_len; i < strlen(partial_match.string()); i++)
|
||||||
|
_insert_character(partial_match.string()[i]);
|
||||||
|
|
||||||
_insert_character(' ');
|
_insert_character(' ');
|
||||||
}
|
}
|
||||||
@ -577,7 +689,7 @@ class Line_editor
|
|||||||
_num_partial_matches(str, str_len, command.parameters());
|
_num_partial_matches(str, str_len, command.parameters());
|
||||||
|
|
||||||
unsigned const matching_arguments =
|
unsigned const matching_arguments =
|
||||||
_num_partial_matches(str, str_len, command.arguments());
|
_num_matching_arguments(str, str_len, command);
|
||||||
|
|
||||||
/* matches are ambiguous */
|
/* matches are ambiguous */
|
||||||
if (matching_arguments + matching_parameters > 1) {
|
if (matching_arguments + matching_parameters > 1) {
|
||||||
@ -586,13 +698,17 @@ class Line_editor
|
|||||||
* Try to complete additional characters that are common among
|
* Try to complete additional characters that are common among
|
||||||
* all matches.
|
* all matches.
|
||||||
*/
|
*/
|
||||||
char buf[Completable::NAME_MAX_LEN];
|
char buf[Completable::Name::size()];
|
||||||
strncpy(buf, str, Genode::min(sizeof(buf), str_len + 1));
|
strncpy(buf, str, Genode::min(sizeof(buf), str_len + 1));
|
||||||
|
|
||||||
/* pick any representative as a template to take characters from */
|
/* pick any representative as a template to take characters from */
|
||||||
char const *name = _any_partial_match_name(str, str_len, command.parameters());
|
char const *name = _any_partial_match_name(str, str_len, command.parameters());
|
||||||
if (!name)
|
Argument::Name arg_name;
|
||||||
name = _any_partial_match_name(str, str_len, command.arguments());
|
if (!name) {
|
||||||
|
arg_name = _any_matching_argument(str, str_len, command);
|
||||||
|
if (strlen(arg_name.string()))
|
||||||
|
name = arg_name.string();
|
||||||
|
}
|
||||||
|
|
||||||
size_t i = str_len;
|
size_t i = str_len;
|
||||||
for (; (i < sizeof(buf) - 1) && (i < strlen(name)); i++) {
|
for (; (i < sizeof(buf) - 1) && (i < strlen(name)); i++) {
|
||||||
@ -605,7 +721,7 @@ class Line_editor
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
if (matching_arguments !=
|
if (matching_arguments !=
|
||||||
_num_partial_matches(buf, i + 1, command.arguments()))
|
_num_matching_arguments(buf, i + 1, command))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
_insert_character(buf[i]);
|
_insert_character(buf[i]);
|
||||||
@ -624,10 +740,10 @@ class Line_editor
|
|||||||
*/
|
*/
|
||||||
size_t const pad =
|
size_t const pad =
|
||||||
max(_width_of_partial_matches(str, str_len, command.parameters()),
|
max(_width_of_partial_matches(str, str_len, command.parameters()),
|
||||||
_width_of_partial_matches(str, str_len, command.arguments()));
|
_width_of_matching_arguments(str, str_len, command));
|
||||||
|
|
||||||
_list_partial_matches(str, str_len, pad, command.parameters());
|
_list_partial_matches(str, str_len, pad, command.parameters());
|
||||||
_list_partial_matches(str, str_len, pad, command.arguments());
|
_list_matching_arguments(str, str_len, pad, command);
|
||||||
|
|
||||||
_write_newline();
|
_write_newline();
|
||||||
_fresh_prompt();
|
_fresh_prompt();
|
||||||
@ -639,7 +755,7 @@ class Line_editor
|
|||||||
_do_completion(str, str_len, command.parameters());
|
_do_completion(str, str_len, command.parameters());
|
||||||
|
|
||||||
if (matching_arguments == 1)
|
if (matching_arguments == 1)
|
||||||
_do_completion(str, str_len, command.arguments());
|
_do_argument_completion(str, str_len, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _perform_completion()
|
void _perform_completion()
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
/* Genode includes */
|
/* Genode includes */
|
||||||
#include <os/config.h>
|
#include <os/config.h>
|
||||||
#include <cap_session/connection.h>
|
#include <cap_session/connection.h>
|
||||||
|
#include <vfs/file_system_factory.h>
|
||||||
|
#include <vfs/dir_file_system.h>
|
||||||
|
|
||||||
/* public CLI-monitor includes */
|
/* public CLI-monitor includes */
|
||||||
#include <cli_monitor/ram.h>
|
#include <cli_monitor/ram.h>
|
||||||
@ -48,8 +50,8 @@ static inline Command *lookup_command(char const *buf, Command_registry ®istr
|
|||||||
{
|
{
|
||||||
Token token(buf);
|
Token token(buf);
|
||||||
for (Command *curr = registry.first(); curr; curr = curr->next())
|
for (Command *curr = registry.first(); curr; curr = curr->next())
|
||||||
if (strcmp(token.start(), curr->name(), token.len()) == 0
|
if (strcmp(token.start(), curr->name().string(), token.len()) == 0
|
||||||
&& strlen(curr->name()) == token.len())
|
&& strlen(curr->name().string()) == token.len())
|
||||||
return curr;
|
return curr;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -70,6 +72,30 @@ static size_t ram_preservation_from_config()
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return singleton instance of the subsystem config registry
|
||||||
|
*/
|
||||||
|
static Subsystem_config_registry &subsystem_config_registry()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
|
||||||
|
/* initialize virtual file system */
|
||||||
|
static Vfs::Dir_file_system
|
||||||
|
root_dir(Genode::config()->xml_node().sub_node("vfs"),
|
||||||
|
Vfs::global_file_system_factory());
|
||||||
|
|
||||||
|
static Subsystem_config_registry inst(root_dir);
|
||||||
|
|
||||||
|
return inst;
|
||||||
|
|
||||||
|
} catch (Genode::Xml_node::Nonexistent_sub_node) {
|
||||||
|
|
||||||
|
PERR("missing '<vfs>' configuration");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
/* look for dynamic linker */
|
/* look for dynamic linker */
|
||||||
@ -86,7 +112,6 @@ int main(int argc, char **argv)
|
|||||||
static Terminal::Connection terminal;
|
static Terminal::Connection terminal;
|
||||||
static Command_registry commands;
|
static Command_registry commands;
|
||||||
static Child_registry children;
|
static Child_registry children;
|
||||||
static Process_arg_registry process_args;
|
|
||||||
|
|
||||||
/* initialize platform-specific commands */
|
/* initialize platform-specific commands */
|
||||||
init_extension(commands);
|
init_extension(commands);
|
||||||
@ -112,20 +137,18 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
/* initialize generic commands */
|
/* initialize generic commands */
|
||||||
commands.insert(new Help_command);
|
commands.insert(new Help_command);
|
||||||
Kill_command kill_command(children, process_args);
|
Kill_command kill_command(children);
|
||||||
commands.insert(&kill_command);
|
commands.insert(&kill_command);
|
||||||
commands.insert(new Gdb_command(ram, cap, children,
|
commands.insert(new Gdb_command(ram, cap, children,
|
||||||
Genode::config()->xml_node(),
|
subsystem_config_registry(),
|
||||||
process_args,
|
|
||||||
yield_response_sig_cap,
|
yield_response_sig_cap,
|
||||||
kill_gdb_sig_cap));
|
kill_gdb_sig_cap));
|
||||||
commands.insert(new Start_command(ram, cap, children,
|
commands.insert(new Start_command(ram, cap, children,
|
||||||
Genode::config()->xml_node(),
|
subsystem_config_registry(),
|
||||||
process_args,
|
|
||||||
yield_response_sig_cap));
|
yield_response_sig_cap));
|
||||||
commands.insert(new Status_command(ram, children));
|
commands.insert(new Status_command(ram, children));
|
||||||
commands.insert(new Yield_command(children, process_args));
|
commands.insert(new Yield_command(children));
|
||||||
commands.insert(new Ram_command(children, process_args));
|
commands.insert(new Ram_command(children));
|
||||||
|
|
||||||
enum { COMMAND_MAX_LEN = 1000 };
|
enum { COMMAND_MAX_LEN = 1000 };
|
||||||
static char buf[COMMAND_MAX_LEN];
|
static char buf[COMMAND_MAX_LEN];
|
||||||
@ -180,7 +203,6 @@ int main(int argc, char **argv)
|
|||||||
dynamic_cast<Gdb_command_child*>(child);
|
dynamic_cast<Gdb_command_child*>(child);
|
||||||
if (gdb_command_child && gdb_command_child->kill_requested()) {
|
if (gdb_command_child && gdb_command_child->kill_requested()) {
|
||||||
tprintf(terminal, "Destroying GDB subsystem after an error occured.\n");
|
tprintf(terminal, "Destroying GDB subsystem after an error occured.\n");
|
||||||
process_args.list.remove(&gdb_command_child->argument);
|
|
||||||
children.remove(gdb_command_child);
|
children.remove(gdb_command_child);
|
||||||
Genode::destroy(Genode::env()->heap(), gdb_command_child);
|
Genode::destroy(Genode::env()->heap(), gdb_command_child);
|
||||||
line_editor.reset();
|
line_editor.reset();
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
* \brief Registry of process names used as arguments
|
|
||||||
* \author Norman Feske
|
|
||||||
* \date 2013-03-18
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (C) 2013 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef _PROCESS_ARG_REGISTRY_H_
|
|
||||||
#define _PROCESS_ARG_REGISTRY_H_
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registry of arguments referring to the currently running processes
|
|
||||||
*/
|
|
||||||
struct Process_arg_registry
|
|
||||||
{
|
|
||||||
Genode::List<Argument> list;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* _PROCESS_ARG_REGISTRY_H_ */
|
|
@ -16,18 +16,15 @@
|
|||||||
|
|
||||||
/* local includes */
|
/* local includes */
|
||||||
#include <child_registry.h>
|
#include <child_registry.h>
|
||||||
#include <process_arg_registry.h>
|
|
||||||
|
|
||||||
struct Ram_command : Command
|
struct Ram_command : Command
|
||||||
{
|
{
|
||||||
Child_registry &_children;
|
Child_registry &_children;
|
||||||
Process_arg_registry &_process_args;
|
|
||||||
|
|
||||||
Ram_command(Child_registry &children, Process_arg_registry &process_args)
|
Ram_command(Child_registry &children)
|
||||||
:
|
:
|
||||||
Command("ram", "set RAM quota of subsystem"),
|
Command("ram", "set RAM quota of subsystem"),
|
||||||
_children(children),
|
_children(children)
|
||||||
_process_args(process_args)
|
|
||||||
{
|
{
|
||||||
add_parameter(new Parameter("--quota", Parameter::NUMBER, "new RAM quota"));
|
add_parameter(new Parameter("--quota", Parameter::NUMBER, "new RAM quota"));
|
||||||
add_parameter(new Parameter("--limit", Parameter::NUMBER, "on-demand quota limit"));
|
add_parameter(new Parameter("--limit", Parameter::NUMBER, "on-demand quota limit"));
|
||||||
@ -83,6 +80,16 @@ struct Ram_command : Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _for_each_argument(Argument_fn const &fn) const override
|
||||||
|
{
|
||||||
|
auto child_name_fn = [&] (char const *child_name) {
|
||||||
|
Argument arg(child_name, "");
|
||||||
|
fn(arg);
|
||||||
|
};
|
||||||
|
|
||||||
|
_children.for_each_child_name(child_name_fn);
|
||||||
|
}
|
||||||
|
|
||||||
void execute(Command_line &cmd, Terminal::Session &terminal)
|
void execute(Command_line &cmd, Terminal::Session &terminal)
|
||||||
{
|
{
|
||||||
char label[128];
|
char label[128];
|
||||||
@ -116,8 +123,6 @@ struct Ram_command : Command
|
|||||||
_set_quota(terminal, *child, quota);
|
_set_quota(terminal, *child, quota);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Argument> &arguments() { return _process_args.list; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _RAM_COMMAND_H_ */
|
#endif /* _RAM_COMMAND_H_ */
|
||||||
|
@ -17,103 +17,34 @@
|
|||||||
/* Genode includes */
|
/* Genode includes */
|
||||||
#include <util/xml_node.h>
|
#include <util/xml_node.h>
|
||||||
|
|
||||||
struct Start_command : Command
|
/* local includes */
|
||||||
|
#include <subsystem_config_registry.h>
|
||||||
|
|
||||||
|
class Start_command : public Command
|
||||||
{
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
typedef Genode::Xml_node Xml_node;
|
typedef Genode::Xml_node Xml_node;
|
||||||
typedef Genode::Signal_context_capability Signal_context_capability;
|
typedef Genode::Signal_context_capability Signal_context_capability;
|
||||||
|
|
||||||
Ram &_ram;
|
Ram &_ram;
|
||||||
Child_registry &_children;
|
Child_registry &_children;
|
||||||
Genode::Cap_session &_cap;
|
Genode::Cap_session &_cap;
|
||||||
Xml_node _config;
|
Subsystem_config_registry &_subsystem_configs;
|
||||||
Process_arg_registry &_process_args;
|
|
||||||
List<Argument> _arguments;
|
List<Argument> _arguments;
|
||||||
Signal_context_capability _yield_response_sigh_cap;
|
Signal_context_capability _yield_response_sigh_cap;
|
||||||
|
|
||||||
Start_command(Ram &ram, Genode::Cap_session &cap, Child_registry &children,
|
void _execute_subsystem(char const *name, Command_line &cmd,
|
||||||
Xml_node config, Process_arg_registry &process_args,
|
Terminal::Session &terminal,
|
||||||
Signal_context_capability yield_response_sigh_cap)
|
Genode::Xml_node subsystem_node)
|
||||||
:
|
|
||||||
Command("start", "create new subsystem"),
|
|
||||||
_ram(ram), _children(children), _cap(cap), _config(config),
|
|
||||||
_process_args(process_args),
|
|
||||||
_yield_response_sigh_cap(yield_response_sigh_cap)
|
|
||||||
{
|
|
||||||
/* scan config for possible subsystem arguments */
|
|
||||||
try {
|
|
||||||
Xml_node node = _config.sub_node("subsystem");
|
|
||||||
for (;; node = node.next("subsystem")) {
|
|
||||||
|
|
||||||
char name[Parameter::NAME_MAX_LEN];
|
|
||||||
try { node.attribute("name").value(name, sizeof(name)); }
|
|
||||||
catch (Xml_node::Nonexistent_attribute) {
|
|
||||||
PWRN("Missing name in '<subsystem>' configuration");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
char const *prefix = "config: ";
|
|
||||||
size_t const prefix_len = strlen(prefix);
|
|
||||||
|
|
||||||
char help[Parameter::SHORT_HELP_MAX_LEN + prefix_len];
|
|
||||||
strncpy(help, prefix, ~0);
|
|
||||||
try { node.attribute("help").value(help + prefix_len,
|
|
||||||
sizeof(help) - prefix_len); }
|
|
||||||
catch (Xml_node::Nonexistent_attribute) {
|
|
||||||
PWRN("Missing help in '<subsystem>' configuration");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_arguments.insert(new Argument(name, help));
|
|
||||||
}
|
|
||||||
} catch (Xml_node::Nonexistent_sub_node) { /* end of list */ }
|
|
||||||
|
|
||||||
add_parameter(new Parameter("--count", Parameter::NUMBER, "number of instances"));
|
|
||||||
add_parameter(new Parameter("--ram", Parameter::NUMBER, "initial RAM quota"));
|
|
||||||
add_parameter(new Parameter("--ram-limit", Parameter::NUMBER, "limit for expanding RAM quota"));
|
|
||||||
add_parameter(new Parameter("--verbose", Parameter::VOID, "show diagnostics"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup subsystem in config
|
|
||||||
*/
|
|
||||||
Xml_node _subsystem_node(char const *name)
|
|
||||||
{
|
|
||||||
Xml_node node = _config.sub_node("subsystem");
|
|
||||||
for (;; node = node.next("subsystem")) {
|
|
||||||
if (node.attribute("name").has_value(name))
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void execute(Command_line &cmd, Terminal::Session &terminal)
|
|
||||||
{
|
{
|
||||||
size_t count = 1;
|
size_t count = 1;
|
||||||
Genode::Number_of_bytes ram = 0;
|
Genode::Number_of_bytes ram = 0;
|
||||||
Genode::Number_of_bytes ram_limit = 0;
|
Genode::Number_of_bytes ram_limit = 0;
|
||||||
|
|
||||||
char name[128];
|
|
||||||
name[0] = 0;
|
|
||||||
if (cmd.argument(0, name, sizeof(name)) == false) {
|
|
||||||
tprintf(terminal, "Error: no configuration name specified\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char buf[128];
|
|
||||||
if (cmd.argument(1, buf, sizeof(buf))) {
|
|
||||||
tprintf(terminal, "Error: unexpected argument \"%s\"\n", buf);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check if a configuration for the subsystem exists */
|
|
||||||
try { _subsystem_node(name); }
|
|
||||||
catch (Xml_node::Nonexistent_sub_node) {
|
|
||||||
tprintf(terminal, "Error: no configuration for \"%s\"\n", name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* read default RAM quota from config */
|
/* read default RAM quota from config */
|
||||||
try {
|
try {
|
||||||
Xml_node rsc = _subsystem_node(name).sub_node("resource");
|
Xml_node rsc = subsystem_node.sub_node("resource");
|
||||||
for (;; rsc = rsc.next("resource")) {
|
for (;; rsc = rsc.next("resource")) {
|
||||||
if (rsc.attribute("name").has_value("RAM")) {
|
if (rsc.attribute("name").has_value("RAM")) {
|
||||||
rsc.attribute("quantum").value(&ram);
|
rsc.attribute("quantum").value(&ram);
|
||||||
@ -146,7 +77,7 @@ struct Start_command : Command
|
|||||||
char binary_name[128];
|
char binary_name[128];
|
||||||
strncpy(binary_name, name, sizeof(binary_name));
|
strncpy(binary_name, name, sizeof(binary_name));
|
||||||
try {
|
try {
|
||||||
Xml_node bin = _subsystem_node(name).sub_node("binary");
|
Xml_node bin = subsystem_node.sub_node("binary");
|
||||||
bin.attribute("name").value(binary_name, sizeof(binary_name));
|
bin.attribute("name").value(binary_name, sizeof(binary_name));
|
||||||
} catch (...) { }
|
} catch (...) { }
|
||||||
|
|
||||||
@ -196,7 +127,7 @@ struct Start_command : Command
|
|||||||
|
|
||||||
/* configure child */
|
/* configure child */
|
||||||
try {
|
try {
|
||||||
Xml_node config_node = _subsystem_node(name).sub_node("config");
|
Xml_node config_node = subsystem_node.sub_node("config");
|
||||||
child->configure(config_node.addr(), config_node.size());
|
child->configure(config_node.addr(), config_node.size());
|
||||||
if (verbose)
|
if (verbose)
|
||||||
tprintf(terminal, " config: inline\n");
|
tprintf(terminal, " config: inline\n");
|
||||||
@ -205,12 +136,86 @@ struct Start_command : Command
|
|||||||
tprintf(terminal, " config: none\n");
|
tprintf(terminal, " config: none\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
_process_args.list.insert(&child->argument);
|
|
||||||
_children.insert(child);
|
_children.insert(child);
|
||||||
child->start();
|
child->start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
Start_command(Ram &ram, Genode::Cap_session &cap, Child_registry &children,
|
||||||
|
Subsystem_config_registry &subsustem_configs,
|
||||||
|
Signal_context_capability yield_response_sigh_cap)
|
||||||
|
:
|
||||||
|
Command("start", "create new subsystem"),
|
||||||
|
_ram(ram), _children(children), _cap(cap),
|
||||||
|
_subsystem_configs(subsustem_configs),
|
||||||
|
_yield_response_sigh_cap(yield_response_sigh_cap)
|
||||||
|
{
|
||||||
|
add_parameter(new Parameter("--count", Parameter::NUMBER, "number of instances"));
|
||||||
|
add_parameter(new Parameter("--ram", Parameter::NUMBER, "initial RAM quota"));
|
||||||
|
add_parameter(new Parameter("--ram-limit", Parameter::NUMBER, "limit for expanding RAM quota"));
|
||||||
|
add_parameter(new Parameter("--verbose", Parameter::VOID, "show diagnostics"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _for_each_argument(Argument_fn const &fn) const override
|
||||||
|
{
|
||||||
|
/* functor for processing a subsystem configuration */
|
||||||
|
auto process_subsystem_config_fn = [&] (Genode::Xml_node node) {
|
||||||
|
|
||||||
|
char name[Parameter::Name::size()];
|
||||||
|
try { node.attribute("name").value(name, sizeof(name)); }
|
||||||
|
catch (Xml_node::Nonexistent_attribute) {
|
||||||
|
PWRN("Missing name in '<subsystem>' configuration");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *prefix = "config: ";
|
||||||
|
size_t const prefix_len = strlen(prefix);
|
||||||
|
|
||||||
|
char help[Parameter::Short_help::size() + prefix_len];
|
||||||
|
strncpy(help, prefix, ~0);
|
||||||
|
try { node.attribute("help").value(help + prefix_len,
|
||||||
|
sizeof(help) - prefix_len); }
|
||||||
|
catch (Xml_node::Nonexistent_attribute) {
|
||||||
|
PWRN("Missing help in '<subsystem>' configuration");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Argument arg(name, help);
|
||||||
|
fn(arg);
|
||||||
|
};
|
||||||
|
|
||||||
|
/* scan subsystem config registry for possible subsystem arguments */
|
||||||
|
_subsystem_configs.for_each_config(process_subsystem_config_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void execute(Command_line &cmd, Terminal::Session &terminal)
|
||||||
|
{
|
||||||
|
char name[128];
|
||||||
|
name[0] = 0;
|
||||||
|
if (cmd.argument(0, name, sizeof(name)) == false) {
|
||||||
|
tprintf(terminal, "Error: no configuration name specified\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[128];
|
||||||
|
if (cmd.argument(1, buf, sizeof(buf))) {
|
||||||
|
tprintf(terminal, "Error: unexpected argument \"%s\"\n", buf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_subsystem_configs.for_config(name, [&] (Genode::Xml_node node)
|
||||||
|
{
|
||||||
|
_execute_subsystem(name, cmd, terminal, node);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (Subsystem_config_registry::Nonexistent_subsystem_config) {
|
||||||
|
tprintf(terminal, "Error: no configuration for \"%s\"\n", name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
List<Argument> &arguments() { return _arguments; }
|
List<Argument> &arguments() { return _arguments; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
161
repos/os/src/app/cli_monitor/subsystem_config_registry.h
Normal file
161
repos/os/src/app/cli_monitor/subsystem_config_registry.h
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* \brief Registry of subsystem configuration
|
||||||
|
* \author Norman Feske
|
||||||
|
* \date 2015-01-27
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2015 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SUBSYSTEM_CONFIG_REGISTRY_H_
|
||||||
|
#define _SUBSYSTEM_CONFIG_REGISTRY_H_
|
||||||
|
|
||||||
|
/* Genode includes */
|
||||||
|
#include <vfs/file_system.h>
|
||||||
|
#include <vfs/vfs_handle.h>
|
||||||
|
|
||||||
|
class Subsystem_config_registry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception type
|
||||||
|
*/
|
||||||
|
class Nonexistent_subsystem_config { };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Vfs::File_system &_fs;
|
||||||
|
|
||||||
|
enum { CONFIG_BUF_SIZE = 32*1024 };
|
||||||
|
char _config_buf[CONFIG_BUF_SIZE];
|
||||||
|
|
||||||
|
char const *_subsystems_path() { return "/subsystems"; }
|
||||||
|
char const *_subsystem_suffix() { return ".subsystem"; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return index of ".subsystem" suffix in dirent name
|
||||||
|
*
|
||||||
|
* \return index, or 0 if no matching suffix could be found
|
||||||
|
*/
|
||||||
|
unsigned _subsystem_suffix(Vfs::Directory_service::Dirent const &dirent)
|
||||||
|
{
|
||||||
|
unsigned found = 0;
|
||||||
|
for (unsigned i = 0; i < sizeof(dirent.name) && dirent.name[i]; i++)
|
||||||
|
if (Genode::strcmp(_subsystem_suffix(), &dirent.name[i]) == 0)
|
||||||
|
found = i;
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
Subsystem_config_registry(Vfs::File_system &fs)
|
||||||
|
:
|
||||||
|
_fs(fs)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute functor 'fn' for specified subsystem name
|
||||||
|
*
|
||||||
|
* The functor is called with the subsystem XML node as argument
|
||||||
|
*
|
||||||
|
* \throw Nonexistent_subsystem_config
|
||||||
|
*/
|
||||||
|
template <typename FN>
|
||||||
|
void for_config(char const *name, FN const &fn)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Load subsystem configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
Genode::Path<256> path(_subsystems_path());
|
||||||
|
path.append("/");
|
||||||
|
path.append(name);
|
||||||
|
path.append(_subsystem_suffix());
|
||||||
|
|
||||||
|
Vfs::Vfs_handle *handle = nullptr;
|
||||||
|
|
||||||
|
Vfs::Directory_service::Open_result const open_result =
|
||||||
|
_fs.open(path.base(),
|
||||||
|
Vfs::Directory_service::OPEN_MODE_RDONLY,
|
||||||
|
&handle);
|
||||||
|
|
||||||
|
Vfs::Vfs_handle::Guard handle_guard(handle);
|
||||||
|
|
||||||
|
if (open_result != Vfs::Directory_service::OPEN_OK) {
|
||||||
|
PERR("could not open '%s', err=%d", path.base(), open_result);
|
||||||
|
throw Nonexistent_subsystem_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
Vfs::file_size out_count = 0;
|
||||||
|
Vfs::File_io_service::Read_result read_result =
|
||||||
|
handle->fs().read(handle, _config_buf, sizeof(_config_buf), out_count);
|
||||||
|
|
||||||
|
if (read_result != Vfs::File_io_service::READ_OK) {
|
||||||
|
PERR("could not read '%s', err=%d", path.base(), read_result);
|
||||||
|
throw Nonexistent_subsystem_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Genode::Xml_node subsystem_node(_config_buf, out_count);
|
||||||
|
fn(subsystem_node);
|
||||||
|
|
||||||
|
} catch (Genode::Xml_node::Invalid_syntax) {
|
||||||
|
PERR("subsystem configuration has invalid syntax");
|
||||||
|
throw Nonexistent_subsystem_config();
|
||||||
|
|
||||||
|
} catch (Genode::Xml_node::Nonexistent_sub_node) {
|
||||||
|
PERR("invalid subsystem configuration");
|
||||||
|
throw Nonexistent_subsystem_config();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call specified functor for each subsystem config
|
||||||
|
*/
|
||||||
|
template <typename FN>
|
||||||
|
void for_each_config(FN const &fn)
|
||||||
|
{
|
||||||
|
/* iterate over the directory entries */
|
||||||
|
for (unsigned i = 0;; i++) {
|
||||||
|
|
||||||
|
Vfs::Directory_service::Dirent dirent;
|
||||||
|
|
||||||
|
Vfs::Directory_service::Dirent_result dirent_result =
|
||||||
|
_fs.dirent(_subsystems_path(), i, dirent);
|
||||||
|
|
||||||
|
if (dirent_result != Vfs::Directory_service::DIRENT_OK) {
|
||||||
|
PERR("could not access directory '%s'", _subsystems_path());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dirent.type == Vfs::Directory_service::DIRENT_TYPE_END)
|
||||||
|
return;
|
||||||
|
|
||||||
|
unsigned const subsystem_suffix = _subsystem_suffix(dirent);
|
||||||
|
|
||||||
|
/* if file has a matching suffix, apply 'fn' */
|
||||||
|
if (subsystem_suffix) {
|
||||||
|
|
||||||
|
/* subsystem name is file name without the suffix */
|
||||||
|
char name[sizeof(dirent.name)];
|
||||||
|
Genode::strncpy(name, dirent.name, subsystem_suffix + 1);
|
||||||
|
|
||||||
|
try {
|
||||||
|
for_config(name, fn);
|
||||||
|
} catch (Nonexistent_subsystem_config) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _PROCESS_ARG_REGISTRY_H_ */
|
@ -1,6 +1,6 @@
|
|||||||
TARGET = cli_monitor
|
TARGET = cli_monitor
|
||||||
SRC_CC = main.cc
|
SRC_CC = main.cc
|
||||||
LIBS = base cli_monitor config
|
LIBS = base cli_monitor config vfs ld
|
||||||
INC_DIR += $(PRG_DIR)
|
INC_DIR += $(PRG_DIR)
|
||||||
|
|
||||||
ifeq ($(findstring arm, $(SPECS)), arm)
|
ifeq ($(findstring arm, $(SPECS)), arm)
|
||||||
|
@ -16,23 +16,30 @@
|
|||||||
|
|
||||||
/* local includes */
|
/* local includes */
|
||||||
#include <child_registry.h>
|
#include <child_registry.h>
|
||||||
#include <process_arg_registry.h>
|
|
||||||
|
|
||||||
struct Yield_command : Command
|
struct Yield_command : Command
|
||||||
{
|
{
|
||||||
Child_registry &_children;
|
Child_registry &_children;
|
||||||
Process_arg_registry &_process_args;
|
|
||||||
|
|
||||||
Yield_command(Child_registry &children, Process_arg_registry &process_args)
|
Yield_command(Child_registry &children)
|
||||||
:
|
:
|
||||||
Command("yield", "instruct subsystem to yield resources"),
|
Command("yield", "instruct subsystem to yield resources"),
|
||||||
_children(children),
|
_children(children)
|
||||||
_process_args(process_args)
|
|
||||||
{
|
{
|
||||||
add_parameter(new Parameter("--ram", Parameter::NUMBER, "RAM quota to free"));
|
add_parameter(new Parameter("--ram", Parameter::NUMBER, "RAM quota to free"));
|
||||||
add_parameter(new Parameter("--greedy", Parameter::VOID, "withdraw yielded RAM quota"));
|
add_parameter(new Parameter("--greedy", Parameter::VOID, "withdraw yielded RAM quota"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _for_each_argument(Argument_fn const &fn) const override
|
||||||
|
{
|
||||||
|
auto child_name_fn = [&] (char const *child_name) {
|
||||||
|
Argument arg(child_name, "");
|
||||||
|
fn(arg);
|
||||||
|
};
|
||||||
|
|
||||||
|
_children.for_each_child_name(child_name_fn);
|
||||||
|
}
|
||||||
|
|
||||||
void execute(Command_line &cmd, Terminal::Session &terminal)
|
void execute(Command_line &cmd, Terminal::Session &terminal)
|
||||||
{
|
{
|
||||||
char label[128];
|
char label[128];
|
||||||
@ -64,8 +71,6 @@ struct Yield_command : Command
|
|||||||
tprint_bytes(terminal, ram);
|
tprint_bytes(terminal, ram);
|
||||||
tprintf(terminal, "\n");
|
tprintf(terminal, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Argument> &arguments() { return _process_args.list; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /* _YIELD_COMMAND_H_ */
|
#endif /* _YIELD_COMMAND_H_ */
|
||||||
|
Loading…
Reference in New Issue
Block a user