Simple CLI for managing Genode subsystems

This commit is contained in:
Norman Feske 2013-03-19 22:09:17 +01:00
parent 22f65d1afe
commit 05027c7935
5 changed files with 1541 additions and 0 deletions

View File

@ -0,0 +1,170 @@
/*
* \brief Utility for command-line parsing
* \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 _COMMAND_LINE_H_
#define _COMMAND_LINE_H_
#include <line_editor.h>
class Command_line
{
private:
char const *_cmd_line;
Command &_command;
bool _parameter_is_known(Token token)
{
return Argument_tracker::lookup(token, _command.parameters()) != 0;
}
Token _tag_token(char const *tag)
{
for (Token token(_cmd_line); token; token = token.next())
if (strcmp(token.start(), tag, token.len()) == 0
&& strlen(tag) == token.len()
&& _parameter_is_known(token))
return token;
return Token();
}
Token _value_token(char const *tag)
{
return _tag_token(tag).next().next();
}
static bool _is_parameter(Token token)
{
return token[0] == '-' && token[1] == '-';
}
public:
/**
* Constructor
*
* \param cmd_line null-terminated command line string
* \param command meta data about the command
*/
Command_line(char const *cmd_line, Command &command)
: _cmd_line(cmd_line), _command(command) { }
/**
* Return true if tag is specified at the command line
*/
bool parameter_exists(char const *tag)
{
return _tag_token(tag);
}
/**
* Return number argument specified for the given tag
*/
template <typename T>
bool parameter(char const *tag, T &result)
{
Token value = _value_token(tag);
return value && Genode::ascii_to(value.start(), &result) != 0;
}
/**
* Return string argument specified for the given tag
*/
bool parameter(char const *tag, char *result, size_t result_len)
{
Token value = _value_token(tag);
if (!value)
return false;
value.string(result, result_len);
return true;
}
bool argument(unsigned index, char *result, size_t result_len)
{
Argument_tracker argument_tracker(_command);
/* argument counter */
unsigned cnt = 0;
for (Token token(_cmd_line); token; token = token.next()) {
argument_tracker.supply_token(token);
if (!argument_tracker.valid())
return false;
if (!argument_tracker.expect_arg())
continue;
Token arg = token.next();
if (!arg)
return false;
/*
* The 'arg' token could either the tag of a parameter or
* an argument. We only want to count the arguments. So
* we skip tokens that have the usual form a parameter tag.
*/
if (_is_parameter(arg))
continue;
if (cnt == index) {
arg.string(result, result_len);
return true;
}
cnt++;
}
return false;
}
/**
* Validate parameter tags
*
* \return tag token of first unexpected parameter, or
* invalid token if no unexpected parameter was found
*/
Token unexpected_parameter()
{
Argument_tracker argument_tracker(_command);
for (Token token(_cmd_line); token; token = token.next()) {
argument_tracker.supply_token(token);
if (!argument_tracker.valid())
return token;
if (!argument_tracker.expect_arg())
continue;
Token arg = token.next();
/* ignore non-parameter tokens (i.e., normal arguments) */
if (!_is_parameter(arg))
continue;
/* if parameter with the given tag exists, we are fine */
if (_parameter_is_known(arg))
continue;
/* we hit an unknown parameter tag */
return arg;
}
return Token();
}
};
#endif /* _COMMAND_LINE_H_ */

View File

@ -0,0 +1,740 @@
/*
* \brief Line editor for command-line interfaces
* \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 _LINE_EDITOR_H_
#define _LINE_EDITOR_H_
/* Genode includes */
#include <base/printf.h>
#include <terminal_session/connection.h>
#include <util/string.h>
#include <util/list.h>
#include <util/token.h>
#include <util/misc_math.h>
#include <base/snprintf.h>
using Genode::List;
using Genode::max;
using Genode::strlen;
using Genode::strncpy;
using Genode::snprintf;
using Genode::strcmp;
using Genode::size_t;
using Genode::off_t;
struct Completable
{
template <size_t MAX_LEN> struct String
{
char buf[MAX_LEN];
String(char const *string) { strncpy(buf, string, sizeof(buf)); }
};
enum { NAME_MAX_LEN = 64, SHORT_HELP_MAX_LEN = 160 };
String<NAME_MAX_LEN> const _name;
String<SHORT_HELP_MAX_LEN> const _short_help;
char const *name() const { return _name.buf; }
char const *short_help() const { return _short_help.buf; }
Completable(char const *name, char const *short_help)
: _name(name), _short_help(short_help) { }
};
/**
* Representation of normal command-line argument
*/
struct Argument : List<Argument>::Element, Completable
{
Argument(char const *name, char const *short_help)
: Completable(name, short_help) { }
char const *name_suffix() const { return ""; }
};
/**
* Representation of a parameter of the form '--tag value'
*/
struct Parameter : List<Parameter>::Element, Completable
{
enum Type { IDENT, NUMBER, VOID };
Type const _type;
Parameter(char const *name, Type type, char const *short_help)
:
Completable(name, short_help), _type(type)
{ }
bool needs_value() const { return _type != VOID; }
char const *name_suffix() const
{
switch (_type) {
case IDENT: return "<identifier>";
case NUMBER: return "<number>";
case VOID: return "";
}
return "";
}
};
struct Command_line;
/**
* Representation of a command that can have arguments and parameters
*/
struct Command : List<Command>::Element, Completable
{
List<Argument> _arguments;
List<Parameter> _parameters;
Command(char const *name, char const *short_help)
: Completable(name, short_help) { }
void add_parameter(Parameter *par) { _parameters.insert(par); }
void add_argument (Argument *arg) { _arguments. insert(arg); }
void remove_argument(Argument *arg) { _arguments.remove(arg); }
char const *name_suffix() const { return ""; }
List<Parameter> &parameters() { return _parameters; }
List<Argument> &arguments() { return _arguments; }
virtual void execute(Command_line &, Terminal::Session &terminal) = 0;
};
struct Command_registry : List<Command> { };
/**
* Scanner policy that accepts '-', '.' and '_' as valid identifier characters
*/
struct Scanner_policy
{
static bool identifier_char(char c, unsigned i)
{
return Genode::is_letter(c) || (c == '_') || (c == '-') || (c == '.')
|| (i && Genode::is_digit(c));
}
};
typedef Genode::Token<Scanner_policy> Token;
/**
* State machine used for sequentially parsing command-line arguments
*/
struct Argument_tracker
{
private:
Command &_command;
enum State { EXPECT_COMMAND, EXPECT_SPACE_BEFORE_ARG, EXPECT_ARG,
EXPECT_SPACE_BEFORE_VAL, EXPECT_VAL, INVALID };
State _state;
/**
* Return true if there is exactly one complete match and no additional
* partial matches
*/
template <typename T>
static bool _one_match(char const *str, size_t str_len,
List<T> &list)
{
unsigned complete_cnt = 0, partial_cnt = 0;
Token tag(str, str_len);
for (T *curr = list.first(); curr; curr = curr->next()) {
if (strcmp(tag.start(), curr->name(), tag.len()) == 0) {
partial_cnt++;
if (strlen(curr->name()) == tag.len())
complete_cnt++;
}
}
return partial_cnt == 1 && complete_cnt == 1;;
}
public:
Argument_tracker(Command &command)
: _command(command), _state(EXPECT_COMMAND) { }
template <typename T>
static T *lookup(char const *str, size_t str_len,
List<T> &list)
{
Token tag(str, str_len);
for (T *curr = list.first(); curr; curr = curr->next())
if (strcmp(tag.start(), curr->name(), tag.len()) == 0
&& strlen(curr->name()) == tag.len())
return curr;
return 0;
}
template <typename T>
static T *lookup(Token token, List<T> &list)
{
return lookup(token.start(), token.len(), list);
}
void supply_token(Token token, bool token_may_be_incomplete = false)
{
switch (_state) {
case INVALID: break;
case EXPECT_COMMAND:
if (token.type() == Token::IDENT) {
_state = EXPECT_SPACE_BEFORE_ARG;
break;
}
_state = INVALID;
break;
case EXPECT_SPACE_BEFORE_ARG:
if (token.type() == Token::WHITESPACE)
_state = EXPECT_ARG;
break;
case EXPECT_ARG:
if (token.type() == Token::IDENT) {
Parameter *parameter =
lookup(token.start(), token.len(), _command.parameters());
if (parameter && parameter->needs_value()) {
_state = EXPECT_SPACE_BEFORE_VAL;
break;
}
if (!token_may_be_incomplete
|| _one_match(token.start(), token.len(), _command.arguments()))
_state = EXPECT_SPACE_BEFORE_ARG;
}
break;
case EXPECT_SPACE_BEFORE_VAL:
if (token.type() == Token::WHITESPACE)
_state = EXPECT_VAL;
break;
case EXPECT_VAL:
if (token.type() == Token::IDENT
|| token.type() == Token::NUMBER) {
_state = EXPECT_SPACE_BEFORE_ARG;
}
break;
}
}
bool valid() const { return _state != INVALID; }
bool expect_arg() const { return _state == EXPECT_ARG; }
bool expect_space() const { return _state == EXPECT_SPACE_BEFORE_ARG
|| _state == EXPECT_SPACE_BEFORE_VAL; }
};
/**
* Editing and completion logic
*/
class Line_editor
{
private:
char const *_prompt;
size_t const _prompt_len;
char * const _buf;
size_t const _buf_size;
unsigned _cursor_pos;
Terminal::Session &_terminal;
Command_registry &_commands;
bool _complete;
/**
* State tracker for escape sequences within user input
*
* This tracker is used to decode special keys (e.g., cursor keys).
*/
struct Seq_tracker
{
enum State { INIT, GOT_ESC, GOT_FIRST } state;
char normal, first, second;
bool sequence_complete;
Seq_tracker() : state(INIT), sequence_complete(false) { }
void input(char c)
{
switch (state) {
case INIT:
if (c == 27)
state = GOT_ESC;
else
normal = c;
sequence_complete = false;
break;
case GOT_ESC:
first = c;
state = GOT_FIRST;
break;
case GOT_FIRST:
second = c;
state = INIT;
sequence_complete = true;
break;
}
}
bool is_normal() const { return state == INIT && !sequence_complete; }
bool _fn_complete(char match_first, char match_second) const
{
return sequence_complete
&& first == match_first
&& second == match_second;
}
bool is_key_up() const { return _fn_complete(91, 65); }
bool is_key_down() const { return _fn_complete(91, 66); }
bool is_key_right() const { return _fn_complete(91, 67); }
bool is_key_left() const { return _fn_complete(91, 68); }
bool is_key_delete() const { return _fn_complete(91, 51); }
};
Seq_tracker _seq_tracker;
void _write(char c) { _terminal.write(&c, sizeof(c)); }
void _write(char const *s) { _terminal.write(s, strlen(s)); }
void _write_spaces(unsigned num)
{
for (unsigned i = 0; i < num; i++)
_write(' ');
}
void _write_newline() { _write(10); }
void _clear_until_end_of_line() { _write("\e[K "); }
void _move_cursor_to(unsigned pos)
{
char seq[10];
snprintf(seq, sizeof(seq), "\e[%dG", pos + _prompt_len);
_write(seq);
}
void _delete_character()
{
strncpy(&_buf[_cursor_pos], &_buf[_cursor_pos + 1], _buf_size);
_move_cursor_to(_cursor_pos);
_write(&_buf[_cursor_pos]);
_clear_until_end_of_line();
_move_cursor_to(_cursor_pos);
}
void _insert_character(char c)
{
/* insert regular character */
if (_cursor_pos >= _buf_size - 1)
return;
/* make room in the buffer */
for (unsigned i = _buf_size - 1; i > _cursor_pos; i--)
_buf[i] = _buf[i - 1];
_buf[_cursor_pos] = c;
/* update terminal */
_write(&_buf[_cursor_pos]);
_cursor_pos++;
_move_cursor_to(_cursor_pos);
}
void _fresh_prompt()
{
_write(_prompt);
_write(_buf);
_move_cursor_to(_cursor_pos);
}
void _handle_key()
{
enum { BACKSPACE = 8,
TAB = 9,
LINE_FEED = 10,
CARRIAGE_RETURN = 13 };
if (_seq_tracker.is_key_left()) {
if (_cursor_pos > 0) {
_cursor_pos--;
_write(BACKSPACE);
}
return;
}
if (_seq_tracker.is_key_right()) {
if (_cursor_pos < strlen(_buf)) {
_cursor_pos++;
_move_cursor_to(_cursor_pos);
}
return;
}
if (_seq_tracker.is_key_delete())
_delete_character();
if (!_seq_tracker.is_normal())
return;
char const c = _seq_tracker.normal;
if (c == TAB) {
_perform_completion();
return;
}
if (c == CARRIAGE_RETURN || c == LINE_FEED) {
if (strlen(_buf) > 0) {
_write(LINE_FEED);
_complete = true;
}
return;
}
if (c == BACKSPACE) {
if (_cursor_pos > 0) {
_cursor_pos--;
_delete_character();
}
return;
}
if (c == 126)
return;
_insert_character(c);
}
template <typename COMPLETABLE>
COMPLETABLE *_lookup_matching(char const *str, size_t str_len,
List<COMPLETABLE> &list)
{
Token tag(str, str_len);
COMPLETABLE *curr = list.first();
for (; curr; curr = curr->next()) {
if (strcmp(tag.start(), curr->name(), tag.len()) == 0
&& strlen(curr->name()) == tag.len())
return curr;
}
return 0;
}
Command *_lookup_matching_command()
{
Token cmd(_buf, _cursor_pos);
for (Command *curr = _commands.first(); curr; curr = curr->next())
if (strcmp(cmd.start(), curr->name(), cmd.len()) == 0
&& _cursor_pos > cmd.len())
return curr;
return 0;
}
template <typename T>
unsigned _num_partial_matches(char const *str, size_t str_len, List<T> &list)
{
Token token(str, str_len);
unsigned num_partial_matches = 0;
for (T *curr = list.first(); curr; curr = curr->next()) {
if (strcmp(token.start(), curr->name(), token.len()) != 0)
continue;
num_partial_matches++;
}
return num_partial_matches;
}
/**
* Determine the name-column width of list of partial matches
*/
template <typename T>
size_t _width_of_partial_matches(char const *str, size_t str_len,
List<T> &list)
{
Token token(str, str_len);
size_t max_name_len = 0;
for (T *curr = list.first(); curr; curr = curr->next()) {
if (strcmp(token.start(), curr->name(), token.len()) != 0)
continue;
size_t const name_len = strlen(curr->name())
+ strlen(curr->name_suffix());
max_name_len = max(max_name_len, name_len);
}
return max_name_len;
}
template <typename T>
char const *_any_partial_match_name(char const *str, size_t str_len,
List<T> &list)
{
Token token(str, str_len);
for (T *curr = list.first(); curr; curr = curr->next())
if (strcmp(token.start(), curr->name(), token.len()) == 0)
return curr->name();
return 0;
}
template <typename T>
void _list_partial_matches(char const *str, size_t str_len,
unsigned pad, List<T> &list)
{
Token token(str, str_len);
for (T *curr = list.first(); curr; curr = curr->next()) {
if (strcmp(token.start(), curr->name(), token.len()) != 0)
continue;
_write_newline();
_write_spaces(2);
_write(curr->name());
_write_spaces(1);
_write(curr->name_suffix());
/* pad short help with whitespaces */
size_t const name_len = strlen(curr->name())
+ strlen(curr->name_suffix());
_write_spaces(pad + 3 - name_len);
_write(curr->short_help());
}
}
template <typename T>
void _do_completion(char const *str, size_t str_len, List<T> &list)
{
Token token(str, str_len);
/* look up completable token */
T *partial_match = 0;
for (T *curr = list.first(); curr; curr = curr->next()) {
if (strcmp(token.start(), curr->name(), token.len()) == 0) {
partial_match = curr;
break;
}
}
if (!partial_match)
return;
for (unsigned i = token.len(); i < strlen(partial_match->name()); i++)
_insert_character(partial_match->name()[i]);
_insert_character(' ');
}
void _complete_argument(char const *str, size_t str_len, Command &command)
{
unsigned const matching_parameters =
_num_partial_matches(str, str_len, command.parameters());
unsigned const matching_arguments =
_num_partial_matches(str, str_len, command.arguments());
/* matches are ambiguous */
if (matching_arguments + matching_parameters > 1) {
/*
* Try to complete additional characters that are common among
* all matches.
*/
char buf[Completable::NAME_MAX_LEN];
strncpy(buf, str, Genode::min(sizeof(buf), str_len + 1));
/* pick any representative as a template to take characters from */
char const *name = _any_partial_match_name(str, str_len, command.parameters());
if (!name)
name = _any_partial_match_name(str, str_len, command.arguments());
size_t i = str_len;
for (; (i < sizeof(buf) - 1) && (i < strlen(name)); i++) {
buf[i + 0] = name[i];
buf[i + 1] = 0;
if (matching_parameters !=
_num_partial_matches(buf, i + 1, command.parameters()))
break;
if (matching_arguments !=
_num_partial_matches(buf, i + 1, command.arguments()))
break;
_insert_character(buf[i]);
}
/*
* If we managed to do a partial completion, let's yield
* control to the user.
*/
if (i > str_len)
return;
/*
* No automatic completion was possible, print list of possible
* parameters and arguments
*/
size_t const pad =
max(_width_of_partial_matches(str, str_len, command.parameters()),
_width_of_partial_matches(str, str_len, command.arguments()));
_list_partial_matches(str, str_len, pad, command.parameters());
_list_partial_matches(str, str_len, pad, command.arguments());
_write_newline();
_fresh_prompt();
return;
}
if (matching_parameters == 1)
_do_completion(str, str_len, command.parameters());
if (matching_arguments == 1)
_do_completion(str, str_len, command.arguments());
}
void _perform_completion()
{
Command *command = _lookup_matching_command();
if (!command) {
unsigned const matches = _num_partial_matches(_buf, _cursor_pos, _commands);
if (matches == 1)
_do_completion(_buf, _cursor_pos, _commands);
if (matches > 1) {
unsigned const pad =
_width_of_partial_matches(_buf, _cursor_pos, _commands);
_list_partial_matches(_buf, _cursor_pos, pad, _commands);
_write_newline();
_fresh_prompt();
}
return;
}
/*
* We hava a valid command, now try to complete the parameters...
*/
/* determine token under the cursor */
Argument_tracker argument_tracker(*command);
Token token(_buf, _buf_size);
for (; token; token = token.next()) {
argument_tracker.supply_token(token, true);
if (!argument_tracker.valid())
return;
unsigned long const token_pos = (unsigned long)(token.start() - _buf);
/* we have reached the token under the cursor */
if (token.type() == Token::IDENT
&& _cursor_pos >= token_pos
&& _cursor_pos <= token_pos + token.len()) {
if (argument_tracker.expect_arg()) {
char const *start = token.start();
size_t const len = _cursor_pos - token_pos;
_complete_argument(start, len, *command);
return;
}
}
}
/* the cursor is positioned at beginning of new argument */
if (argument_tracker.expect_arg())
_complete_argument("", 0, *command);
if (argument_tracker.expect_space())
_insert_character(' ');
}
public:
/**
* Constructor
*
* \param prompt string to be printed at the beginning of the line
* \param buf destination buffer
* \param buf_size destination buffer size
* \param terminal terminal used as output device
* \param commands meta information about commands and their arguments
*/
Line_editor(char const *prompt, char *buf, size_t buf_size,
Terminal::Session &terminal, Command_registry &commands)
:
_prompt(prompt), _prompt_len(strlen(prompt)),
_buf(buf), _buf_size(buf_size), _cursor_pos(0),
_terminal(terminal), _commands(commands),
_complete(false)
{
_buf[0] = 0;
_fresh_prompt();
}
/**
* Supply a character of user input
*/
void submit_input(char c)
{
_seq_tracker.input(c);
_handle_key();
}
/**
* Returns true if the editing is complete, i.e., the user pressed the
* return key.
*/
bool is_complete() const { return _complete; }
};
#endif /* _LINE_EDITOR_H_ */

View File

@ -0,0 +1,591 @@
/*
* \brief Simple command-line interface for managing Genode subsystems
* \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.
*/
/* Genode includes */
#include <base/child.h>
#include <init/child_policy.h>
#include <os/config.h>
#include <os/child_policy_dynamic_rom.h>
#include <ram_session/connection.h>
#include <cpu_session/connection.h>
#include <rm_session/connection.h>
#include <cap_session/connection.h>
/* local includes */
#include <line_editor.h>
#include <command_line.h>
#include <terminal_util.h>
using Terminal::tprintf;
using Genode::Xml_node;
/***************
** Utilities **
***************/
static inline void tprint_bytes(Terminal::Session &terminal, size_t bytes)
{
enum { KB = 1024, MB = 1024*KB };
if (bytes > MB) {
size_t const mb = bytes / MB;
tprintf(terminal, "%zd.", mb);
size_t const remainder = bytes - (mb * MB);
tprintf(terminal, "%zd MiB", (remainder*100)/(MB));
return;
}
if (bytes > KB) {
size_t const kb = bytes / KB;
tprintf(terminal, "%zd.", kb);
size_t const remainder = bytes - (kb * KB);
tprintf(terminal, "%zd KiB", (remainder*100)/(KB));
return;
}
tprintf(terminal, "%zd bytes", bytes);
}
static inline void tprint_status_bytes(Terminal::Session &terminal,
char const *label, size_t bytes)
{
tprintf(terminal, label);
tprint_bytes(terminal, bytes);
tprintf(terminal, "\n");
}
inline void *operator new (size_t size)
{
return Genode::env()->heap()->alloc(size);
}
/********************
** Child handling **
********************/
class Child : public List<Child>::Element, Genode::Child_policy
{
public:
/*
* XXX derive donated quota from information to be provided by
* the used 'Connection' interfaces
*/
enum { DONATED_RAM_QUOTA = 128*1024 };
class Quota_exceeded : public Genode::Exception { };
private:
struct Label
{
enum { LABEL_MAX_LEN = 128 };
char buf[LABEL_MAX_LEN];
Label(char const *label) { strncpy(buf, label, sizeof(buf)); }
};
Label const _label;
Argument _kill_argument;
struct Resources
{
Genode::Ram_connection ram;
Genode::Cpu_connection cpu;
Genode::Rm_connection rm;
Resources(const char *label, Genode::size_t ram_quota)
: ram(label), cpu(label)
{
if (ram_quota > DONATED_RAM_QUOTA)
ram_quota -= DONATED_RAM_QUOTA;
else
throw Quota_exceeded();
ram.ref_account(Genode::env()->ram_session_cap());
if (Genode::env()->ram_session()->transfer_quota(ram.cap(), ram_quota) != 0)
throw Quota_exceeded();
}
};
Resources _resources;
Genode::Service_registry _parent_services;
Genode::Rom_connection _binary_rom;
enum { ENTRYPOINT_STACK_SIZE = 12*1024 };
Genode::Rpc_entrypoint _entrypoint;
Init::Child_policy_enforce_labeling _labeling_policy;
Init::Child_policy_provide_rom_file _binary_policy;
Genode::Child_policy_dynamic_rom_file _config_policy;
Genode::Child _child;
public:
Child(char const *label,
char const *binary,
Genode::Cap_session &cap_session,
Genode::size_t ram_quota)
:
_label(label),
_kill_argument(label, "subsystem"),
_resources(_label.buf, ram_quota),
_binary_rom(binary, _label.buf),
_entrypoint(&cap_session, ENTRYPOINT_STACK_SIZE, _label.buf, false),
_labeling_policy(_label.buf),
_binary_policy("binary", _binary_rom.dataspace(), &_entrypoint),
_config_policy("config", _entrypoint, &_resources.ram),
_child(_binary_rom.dataspace(),
_resources.ram.cap(), _resources.cpu.cap(),
_resources.rm.cap(), &_entrypoint, this)
{ }
void configure(char const *config, size_t config_len)
{
_config_policy.load(config, config_len);
}
void start()
{
_entrypoint.activate();
}
Argument *kill_argument() { return &_kill_argument; }
/****************************
** Child_policy interface **
****************************/
const char *name() const { return _label.buf; }
Genode::Service *resolve_session_request(const char *service_name,
const char *args)
{
Genode::Service *service = 0;
/* check for binary file request */
if ((service = _binary_policy.resolve_session_request(service_name, args)))
return service;
/* check for config file request */
if ((service = _config_policy.resolve_session_request(service_name, args)))
return service;
/* fill parent service registry on demand */
if (!(service = _parent_services.find(service_name))) {
service = new (Genode::env()->heap())
Genode::Parent_service(service_name);
_parent_services.insert(service);
}
/* return parent service */
return service;
}
void filter_session_args(const char *service,
char *args, Genode::size_t args_len)
{
_labeling_policy.filter_session_args(service, args, args_len);
}
};
class Child_registry : public List<Child>
{
private:
/**
* Return true if a child with the specified name already exists
*/
bool _child_name_exists(const char *label)
{
for (Child *child = first() ; child; child = child->next())
if (strcmp(child->name(), label) == 0)
return true;
return false;
}
public:
enum { CHILD_NAME_MAX_LEN = 64 };
/**
* Produce new unique child name
*/
void unique_child_name(const char *prefix, char *dst, int dst_len)
{
char buf[CHILD_NAME_MAX_LEN];
char suffix[8];
suffix[0] = 0;
for (int cnt = 1; true; cnt++) {
/* build program name composed of prefix and numeric suffix */
snprintf(buf, sizeof(buf), "%s%s", prefix, suffix);
/* if such a program name does not exist yet, we are happy */
if (!_child_name_exists(buf)) {
strncpy(dst, buf, dst_len);
return;
}
/* increase number of suffix */
snprintf(suffix, sizeof(suffix), ".%d", cnt + 1);
}
}
};
/**************
** Commands **
**************/
struct Help_command : Command
{
Help_command() : Command("help", "brief help information") { }
void execute(Command_line &, Terminal::Session &terminal)
{
tprintf(terminal, " Press [tab] for a list of commands.\n");
tprintf(terminal, " When given a command, press [tab] for a list of arguments.\n");
}
};
struct Kill_command : Command
{
Child_registry &_children;
void _destroy_child(Child *child, Terminal::Session &terminal)
{
tprintf(terminal, "destroying subsystem '%s'\n", child->name());
remove_argument(child->kill_argument());
_children.remove(child);
Genode::destroy(Genode::env()->heap(), child);
}
Kill_command(Child_registry &children)
:
Command("kill", "destroy subsystem"),
_children(children)
{
add_parameter(new Parameter("--all", Parameter::VOID, "kill all subsystems"));
}
void execute(Command_line &cmd, Terminal::Session &terminal)
{
bool const kill_all = cmd.parameter_exists("--all");
if (kill_all) {
for (Child *child = _children.first(); child; child = _children.first())
_destroy_child(child, terminal);
return;
}
char label[128];
label[0] = 0;
if (cmd.argument(0, label, sizeof(label)) == false) {
tprintf(terminal, "Error: no configuration name specified\n");
return;
}
/* lookup child by its unique name */
for (Child *child = _children.first(); child; child = child->next()) {
if (strcmp(child->name(), label) == 0) {
_destroy_child(child, terminal);
return;
}
}
tprintf(terminal, "Error: subsystem '%s' does not exist\n", label);
}
};
struct Start_command : Command
{
Child_registry &_children;
Genode::Cap_session &_cap;
Xml_node _config;
Kill_command &_kill_command;
Start_command(Genode::Cap_session &cap, Child_registry &children,
Xml_node config, Kill_command &kill_command)
:
Command("start", "create new subsystem"),
_children(children), _cap(cap), _config(config),
_kill_command(kill_command)
{
/* 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;
}
add_argument(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, "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;
Genode::Number_of_bytes ram = 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 */
try {
Xml_node rsc = _subsystem_node(name).sub_node("resource");
for (;; rsc = rsc.next("resource")) {
if (rsc.attribute("name").has_value("RAM")) {
rsc.attribute("quantum").value(&ram);
break;
}
}
} catch (...) { }
cmd.parameter("--count", count);
cmd.parameter("--ram", ram);
bool const verbose = cmd.parameter_exists("--verbose");
/*
* Determine binary name
*
* Use subsystem name by default, override with '<binary>' declaration.
*/
char binary_name[128];
strncpy(binary_name, name, sizeof(binary_name));
try {
Xml_node bin = _subsystem_node(name).sub_node("binary");
bin.attribute("name").value(binary_name, sizeof(binary_name));
} catch (...) { }
for (unsigned i = 0; i < count; i++) {
/* generate unique child name */
char label[Child_registry::CHILD_NAME_MAX_LEN];
_children.unique_child_name(name, label, sizeof(label));
tprintf(terminal, "starting new subsystem '%s'\n", label);
if (verbose) {
tprintf(terminal, " RAM quota: ");
tprint_bytes(terminal, ram);
tprintf(terminal,"\n");
tprintf(terminal, " binary: %s\n", binary_name);
}
Child *child = 0;
try {
child = new (Genode::env()->heap())
Child(label, binary_name, _cap, ram);
}
catch (Genode::Rom_connection::Rom_connection_failed) {
tprintf(terminal, "Error: could not obtain ROM module \"%s\"\n",
binary_name);
return;
}
catch (Child::Quota_exceeded) {
tprintf(terminal, "Error: insufficient memory, need ");
tprint_bytes(terminal, ram + Child::DONATED_RAM_QUOTA);
tprintf(terminal, ", have ");
tprint_bytes(terminal, Genode::env()->ram_session()->avail());
tprintf(terminal, "\n");
return;
}
catch (Genode::Allocator::Out_of_memory) {
tprintf(terminal, "Error: could not allocate meta data, out of memory\n");
return;
}
/* configure child */
try {
Xml_node config_node = _subsystem_node(name).sub_node("config");
child->configure(config_node.addr(), config_node.size());
if (verbose)
tprintf(terminal, " config: inline\n");
} catch (...) {
if (verbose)
tprintf(terminal, " config: none\n");
}
_kill_command.add_argument(child->kill_argument());
_children.insert(child);
child->start();
}
}
};
struct Status_command : Command
{
Status_command() : Command("status", "show runtime status") { }
void execute(Command_line &, Terminal::Session &terminal)
{
Genode::Ram_session *ram = Genode::env()->ram_session();
tprint_status_bytes(terminal, " RAM quota: ", ram->quota());
tprint_status_bytes(terminal, " used: ", ram->used());
tprint_status_bytes(terminal, " avail: ", ram->avail());
}
};
/******************
** Main program **
******************/
static inline Command *lookup_command(char const *buf, Command_registry &registry)
{
Token token(buf);
for (Command *curr = registry.first(); curr; curr = curr->next())
if (strcmp(token.start(), curr->name(), token.len()) == 0
&& strlen(curr->name()) == token.len())
return curr;
return 0;
}
int main(int argc, char **argv)
{
using Genode::Signal_context;
using Genode::Signal_receiver;
try { Genode::config()->xml_node(); }
catch (...) {
PERR("Error: could not obtain configuration");
return -1;
}
static Genode::Cap_connection cap;
static Terminal::Connection terminal;
static Command_registry commands;
static Child_registry children;
commands.insert(new Help_command);
Kill_command kill_command(children);
commands.insert(&kill_command);
commands.insert(new Start_command(cap, children,
Genode::config()->xml_node(),
kill_command));
commands.insert(new Status_command);
static Signal_receiver sig_rec;
static Signal_context read_avail_sig_ctx;
terminal.read_avail_sigh(sig_rec.manage(&read_avail_sig_ctx));
for (;;) {
enum { COMMAND_MAX_LEN = 1000 };
static char buf[COMMAND_MAX_LEN];
Line_editor line_editor("genode> ", buf, sizeof(buf), terminal, commands);
while (!line_editor.is_complete()) {
/* block for event, e.g., the arrival of new user input */
sig_rec.wait_for_signal();
/* supply pending terminal input to line editor */
while (terminal.avail() && !line_editor.is_complete()) {
char c;
terminal.read(&c, 1);
line_editor.submit_input(c);
}
}
Command *command = lookup_command(buf, commands);
if (!command) {
Token cmd_name(buf);
tprintf(terminal, "Error: unknown command \"");
terminal.write(cmd_name.start(), cmd_name.len());
tprintf(terminal, "\"\n");
continue;
}
/* validate parameters against command meta data */
Command_line cmd_line(buf, *command);
Token unexpected = cmd_line.unexpected_parameter();
if (unexpected) {
tprintf(terminal, "Error: unexpected parameter \"");
terminal.write(unexpected.start(), unexpected.len());
tprintf(terminal, "\"\n");
continue;
}
command->execute(cmd_line, terminal);
}
return 0;
}

View File

@ -0,0 +1,4 @@
TARGET = cli_monitor
SRC_CC = main.cc
LIBS = base
INC_DIR += $(PRG_DIR)

View File

@ -0,0 +1,36 @@
/*
* \brief Convenience functions for operating on a terminal session
* \author Norman Feske
* \date 2013-03-19
*/
#ifndef _TERMINAL_UTIL_H_
#define _TERMINAL_UTIL_H_
/* Genode includes */
#include <terminal_session/terminal_session.h>
#include <base/snprintf.h>
namespace Terminal {
static inline void tprintf(Session &terminal, const char *format_args, ...)
{
using namespace Genode;
enum { MAX_LEN = 256 };
char buf[MAX_LEN];
/* process format string */
va_list list;
va_start(list, format_args);
String_console sc(buf, MAX_LEN);
sc.vprintf(format_args, list);
va_end(list);
terminal.write(buf, strlen(buf));
}
}
#endif /* _TERMINAL_UTIL_H_ */