mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-21 02:01:38 +00:00
Simple CLI for managing Genode subsystems
This commit is contained in:
parent
22f65d1afe
commit
05027c7935
170
os/src/app/cli_monitor/command_line.h
Normal file
170
os/src/app/cli_monitor/command_line.h
Normal 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_ */
|
740
os/src/app/cli_monitor/line_editor.h
Normal file
740
os/src/app/cli_monitor/line_editor.h
Normal 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> ¶meters() { 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_ */
|
591
os/src/app/cli_monitor/main.cc
Normal file
591
os/src/app/cli_monitor/main.cc
Normal 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 ®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;
|
||||
}
|
4
os/src/app/cli_monitor/target.mk
Normal file
4
os/src/app/cli_monitor/target.mk
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET = cli_monitor
|
||||
SRC_CC = main.cc
|
||||
LIBS = base
|
||||
INC_DIR += $(PRG_DIR)
|
36
os/src/app/cli_monitor/terminal_util.h
Normal file
36
os/src/app/cli_monitor/terminal_util.h
Normal 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_ */
|
Loading…
x
Reference in New Issue
Block a user