diff --git a/os/src/app/cli_monitor/command_line.h b/os/src/app/cli_monitor/command_line.h new file mode 100644 index 000000000..53f3c761d --- /dev/null +++ b/os/src/app/cli_monitor/command_line.h @@ -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 + +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 + 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_ */ diff --git a/os/src/app/cli_monitor/line_editor.h b/os/src/app/cli_monitor/line_editor.h new file mode 100644 index 000000000..00bd647c8 --- /dev/null +++ b/os/src/app/cli_monitor/line_editor.h @@ -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 +#include +#include +#include +#include +#include +#include + +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 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 const _name; + String 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::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::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 ""; + case NUMBER: return ""; + case VOID: return ""; + } + return ""; + } +}; + + +struct Command_line; + + +/** + * Representation of a command that can have arguments and parameters + */ +struct Command : List::Element, Completable +{ + List _arguments; + List _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 ¶meters() { return _parameters; } + List &arguments() { return _arguments; } + + virtual void execute(Command_line &, Terminal::Session &terminal) = 0; +}; + + +struct Command_registry : List { }; + + +/** + * 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 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 + static bool _one_match(char const *str, size_t str_len, + List &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 + static T *lookup(char const *str, size_t str_len, + List &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 + static T *lookup(Token token, List &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 + COMPLETABLE *_lookup_matching(char const *str, size_t str_len, + List &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 + unsigned _num_partial_matches(char const *str, size_t str_len, List &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 + size_t _width_of_partial_matches(char const *str, size_t str_len, + List &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 + char const *_any_partial_match_name(char const *str, size_t str_len, + List &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 + void _list_partial_matches(char const *str, size_t str_len, + unsigned pad, List &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 + void _do_completion(char const *str, size_t str_len, List &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_ */ diff --git a/os/src/app/cli_monitor/main.cc b/os/src/app/cli_monitor/main.cc new file mode 100644 index 000000000..7035a494a --- /dev/null +++ b/os/src/app/cli_monitor/main.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include + +/* local includes */ +#include +#include +#include + +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::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 +{ + 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 '' 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 '' 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 '' 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 ®istry) +{ + 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; +} diff --git a/os/src/app/cli_monitor/target.mk b/os/src/app/cli_monitor/target.mk new file mode 100644 index 000000000..9b1a93096 --- /dev/null +++ b/os/src/app/cli_monitor/target.mk @@ -0,0 +1,4 @@ +TARGET = cli_monitor +SRC_CC = main.cc +LIBS = base +INC_DIR += $(PRG_DIR) diff --git a/os/src/app/cli_monitor/terminal_util.h b/os/src/app/cli_monitor/terminal_util.h new file mode 100644 index 000000000..75413a144 --- /dev/null +++ b/os/src/app/cli_monitor/terminal_util.h @@ -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 +#include + +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_ */