932 lines
24 KiB
C++
932 lines
24 KiB
C++
|
|
/*
|
|
* \brief Child representation
|
|
* \author Norman Feske
|
|
* \date 2018-01-23
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2018 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU Affero General Public License version 3.
|
|
*/
|
|
|
|
/* local includes */
|
|
#include <child.h>
|
|
|
|
using namespace Depot_deploy;
|
|
|
|
static void forward_to_log(Genode::uint64_t const sec,
|
|
Genode::uint64_t const ms,
|
|
char const *const base,
|
|
char const *const end)
|
|
{
|
|
log(sec, ".", ms < 10 ? "00" : ms < 100 ? "0" : "", ms, " ",
|
|
Cstring(base, end - base));
|
|
}
|
|
|
|
|
|
void Child::gen_start_node(Xml_generator &xml,
|
|
Xml_node common,
|
|
Depot_rom_server const &cached_depot_rom,
|
|
Depot_rom_server const &uncached_depot_rom)
|
|
{
|
|
if (_state != UNFINISHED) {
|
|
while (Timeout_event *event = _timeout_events.first()) {
|
|
_timeout_events.remove(event);
|
|
destroy(_alloc, event);
|
|
}
|
|
while (Log_event *event = _log_events.first()) {
|
|
_log_events.remove(event);
|
|
destroy(_alloc, event);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (_skip) {
|
|
|
|
log("");
|
|
log("--- Run \"", _name, "\" (max 1 sec) ---");
|
|
log("");
|
|
|
|
_state = State::SKIPPED;
|
|
|
|
char name_padded[32];
|
|
for (size_t i = 0; i < sizeof(name_padded) - 1; name_padded[i++] = ' ');
|
|
name_padded[sizeof(name_padded) - 1] = '\0';
|
|
memcpy(name_padded, _name.string(), min(_name.length() - 1, sizeof(name_padded) - 1));
|
|
|
|
_conclusion = Conclusion {
|
|
Cstring(name_padded), " skipped" };
|
|
|
|
log(" ", _conclusion);
|
|
_config_handler.submit();
|
|
|
|
return;
|
|
}
|
|
|
|
if (!_configured() || _condition == UNSATISFIED)
|
|
return;
|
|
|
|
if (_defined_by_launcher() && !_launcher_xml.constructed())
|
|
return;
|
|
|
|
if (!_pkg_xml->xml().has_sub_node("runtime")) {
|
|
warning("blueprint for '", _name, "' lacks runtime information");
|
|
return;
|
|
}
|
|
|
|
Xml_node const runtime = _pkg_xml->xml().sub_node("runtime");
|
|
|
|
xml.node("start", [&] () {
|
|
|
|
xml.attribute("name", _name);
|
|
|
|
unsigned long caps = _pkg_cap_quota;
|
|
if (_defined_by_launcher())
|
|
caps = _launcher_xml->xml().attribute_value("caps", caps);
|
|
caps = _start_xml->xml().attribute_value("caps", caps);
|
|
|
|
xml.attribute("caps", caps);
|
|
|
|
typedef String<64> Version;
|
|
Version const version = _start_xml->xml().attribute_value("version", Version());
|
|
if (version.valid())
|
|
xml.attribute("version", version);
|
|
|
|
xml.node("binary", [&] () { xml.attribute("name", _binary_name); });
|
|
|
|
Number_of_bytes ram = _pkg_ram_quota;
|
|
if (_defined_by_launcher())
|
|
ram = _launcher_xml->xml().attribute_value("ram", ram);
|
|
ram = _start_xml->xml().attribute_value("ram", ram);
|
|
|
|
xml.node("resource", [&] () {
|
|
xml.attribute("name", "RAM");
|
|
xml.attribute("quantum", String<32>(ram));
|
|
});
|
|
|
|
/*
|
|
* Insert inline '<config>' node if provided by the start node,
|
|
* the launcher definition (if a launcher is user), or the
|
|
* blueprint. The former is preferred over the latter.
|
|
*/
|
|
if (_start_xml->xml().has_sub_node("config")) {
|
|
_gen_copy_of_sub_node(xml, _start_xml->xml(), "config");
|
|
} else {
|
|
if (_defined_by_launcher() && _launcher_xml->xml().has_sub_node("config")) {
|
|
_gen_copy_of_sub_node(xml, _launcher_xml->xml(), "config");
|
|
} else {
|
|
if (runtime.has_sub_node("config"))
|
|
_gen_copy_of_sub_node(xml, runtime, "config");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Declare services provided by the subsystem.
|
|
*/
|
|
if (runtime.has_sub_node("provides")) {
|
|
xml.node("provides", [&] () {
|
|
runtime.sub_node("provides").for_each_sub_node([&] (Xml_node service) {
|
|
_gen_provides_sub_node(xml, service, "audio_in", "Audio_in");
|
|
_gen_provides_sub_node(xml, service, "audio_out", "Audio_out");
|
|
_gen_provides_sub_node(xml, service, "block", "Block");
|
|
_gen_provides_sub_node(xml, service, "file_system", "File_system");
|
|
_gen_provides_sub_node(xml, service, "framebuffer", "Framebuffer");
|
|
_gen_provides_sub_node(xml, service, "input", "Input");
|
|
_gen_provides_sub_node(xml, service, "log", "LOG");
|
|
_gen_provides_sub_node(xml, service, "nic", "Nic");
|
|
_gen_provides_sub_node(xml, service, "nitpicker", "Nitpicker");
|
|
_gen_provides_sub_node(xml, service, "report", "Report");
|
|
_gen_provides_sub_node(xml, service, "rom", "ROM");
|
|
_gen_provides_sub_node(xml, service, "terminal", "Terminal");
|
|
_gen_provides_sub_node(xml, service, "timer", "Timer");
|
|
});
|
|
});
|
|
}
|
|
|
|
xml.node("route", [&] () {
|
|
_gen_routes(xml, common, cached_depot_rom, uncached_depot_rom); });
|
|
});
|
|
if (_running) {
|
|
return; }
|
|
|
|
Genode::uint64_t max_timeout_sec = 0;
|
|
try {
|
|
Xml_node const events = _pkg_xml->xml().sub_node("runtime").sub_node("events");
|
|
events.for_each_sub_node("timeout", [&] (Xml_node const &event) {
|
|
try {
|
|
Timeout_event &timeout = *new (_alloc) Timeout_event(_timer, *this, event);
|
|
if (timeout.sec() > max_timeout_sec) {
|
|
max_timeout_sec = timeout.sec();
|
|
}
|
|
_timeout_events.insert(&timeout);
|
|
}
|
|
catch (Timeout_event::Invalid) { warning("Invalid timeout event"); }
|
|
});
|
|
events.for_each_sub_node("log", [&] (Xml_node const &event) {
|
|
_log_events.insert(new (_alloc) Log_event(event));
|
|
});
|
|
}
|
|
catch (...) { }
|
|
log("");
|
|
log("--- Run \"", _name, "\" (max ", max_timeout_sec, " sec) ---");
|
|
log("");
|
|
_running = true;
|
|
init_time_us = _timer.curr_time().trunc_to_plain_us().value;
|
|
}
|
|
|
|
|
|
void Child::_gen_routes(Xml_generator &xml,
|
|
Xml_node common,
|
|
Depot_rom_server const &cached_depot_rom,
|
|
Depot_rom_server const &uncached_depot_rom) const
|
|
{
|
|
if (_skip) {
|
|
return; }
|
|
|
|
if (!_pkg_xml.constructed())
|
|
return;
|
|
|
|
typedef String<160> Path;
|
|
|
|
/*
|
|
* Add routes given in the start node.
|
|
*/
|
|
if (_start_xml->xml().has_sub_node("route")) {
|
|
Xml_node const route = _start_xml->xml().sub_node("route");
|
|
route.with_raw_content([&] (char const *start, size_t length) {
|
|
xml.append(start, length); });
|
|
}
|
|
|
|
/*
|
|
* Add routes given in the launcher definition.
|
|
*/
|
|
if (_launcher_xml.constructed() && _launcher_xml->xml().has_sub_node("route")) {
|
|
Xml_node const route = _launcher_xml->xml().sub_node("route");
|
|
route.with_raw_content([&] (char const *start, size_t length) {
|
|
xml.append(start, length); });
|
|
}
|
|
|
|
/**
|
|
* Return name of depot-ROM server used for obtaining the 'path'
|
|
*
|
|
* If the depot path refers to the depot-user "local", route the
|
|
* session request to the non-cached ROM service.
|
|
*/
|
|
auto rom_server = [&] (Path const &path) {
|
|
|
|
return (String<7>(path) == "local/") ? uncached_depot_rom
|
|
: cached_depot_rom;
|
|
};
|
|
|
|
/*
|
|
* Redirect config ROM request to label as given in the 'config' attribute,
|
|
* if present. We need to search the blueprint's <rom> nodes for the
|
|
* matching ROM module to rewrite the label with the configuration's path
|
|
* within the depot.
|
|
*/
|
|
if (_config_name.valid()) {
|
|
_pkg_xml->xml().for_each_sub_node("rom", [&] (Xml_node rom) {
|
|
|
|
if (!rom.has_attribute("path"))
|
|
return;
|
|
|
|
if (rom.attribute_value("label", Name()) != _config_name)
|
|
return;
|
|
|
|
/* we found the <rom> node for the config ROM */
|
|
xml.node("service", [&] () {
|
|
xml.attribute("name", "ROM");
|
|
xml.attribute("label", "config");
|
|
typedef String<160> Path;
|
|
Path const path = rom.attribute_value("path", Path());
|
|
|
|
if (cached_depot_rom.valid())
|
|
xml.node("child", [&] () {
|
|
xml.attribute("name", rom_server(path));
|
|
xml.attribute("label", path); });
|
|
else
|
|
xml.node("parent", [&] () {
|
|
xml.attribute("label", path); });
|
|
});
|
|
});
|
|
}
|
|
|
|
/*
|
|
* Add common routes as defined in our config.
|
|
*/
|
|
common.with_raw_content([&] (char const *start, size_t length) {
|
|
xml.append(start, length); });
|
|
|
|
/*
|
|
* Add ROM routing rule with the label rewritten to the path within the
|
|
* depot.
|
|
*/
|
|
_pkg_xml->xml().for_each_sub_node("rom", [&] (Xml_node rom) {
|
|
|
|
if (!rom.has_attribute("path"))
|
|
return;
|
|
|
|
typedef Name Label;
|
|
Path const path = rom.attribute_value("path", Path());
|
|
Label const label = rom.attribute_value("label", Label());
|
|
|
|
xml.node("service", [&] () {
|
|
xml.attribute("name", "ROM");
|
|
xml.attribute("label_last", label);
|
|
|
|
if (cached_depot_rom.valid()) {
|
|
xml.node("child", [&] () {
|
|
xml.attribute("name", rom_server(path));
|
|
xml.attribute("label", path);
|
|
});
|
|
} else {
|
|
xml.node("parent", [&] () {
|
|
xml.attribute("label", path); });
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
|
|
bool Child::_defined_by_launcher() const
|
|
{
|
|
if (_skip) {
|
|
return false; }
|
|
|
|
/*
|
|
* If the <start> node lacks a 'pkg' attribute, we expect the
|
|
* policy to be defined by a launcher XML snippet.
|
|
*/
|
|
return _start_xml.constructed() && !_start_xml->xml().has_attribute("pkg");
|
|
}
|
|
|
|
|
|
Archive::Path Child::_config_pkg_path() const
|
|
{
|
|
if (_skip) {
|
|
return Archive::Path(); }
|
|
|
|
if (_defined_by_launcher() && _launcher_xml.constructed())
|
|
return _launcher_xml->xml().attribute_value("pkg", Archive::Path());
|
|
|
|
return _start_xml->xml().attribute_value("pkg", Archive::Path());
|
|
}
|
|
|
|
|
|
Child::Launcher_name Child::_launcher_name() const
|
|
{
|
|
if (_skip) {
|
|
return Launcher_name(); }
|
|
|
|
if (!_defined_by_launcher())
|
|
return Launcher_name();
|
|
|
|
if (_start_xml->xml().has_attribute("launcher"))
|
|
return _start_xml->xml().attribute_value("launcher", Launcher_name());
|
|
|
|
return _start_xml->xml().attribute_value("name", Launcher_name());
|
|
}
|
|
|
|
|
|
bool Child::_configured() const
|
|
{
|
|
if (_skip) {
|
|
return false; }
|
|
|
|
return _pkg_xml.constructed()
|
|
&& (_config_pkg_path() == _blueprint_pkg_path);
|
|
}
|
|
|
|
|
|
void Child::_gen_provides_sub_node(Xml_generator &xml,
|
|
Xml_node service,
|
|
Xml_node::Type const &node_type,
|
|
Service::Name const &service_name)
|
|
{
|
|
if (service.type() == node_type)
|
|
xml.node("service", [&] () {
|
|
xml.attribute("name", service_name); });
|
|
}
|
|
|
|
|
|
void Child::_gen_copy_of_sub_node(Xml_generator &xml,
|
|
Xml_node from_node,
|
|
Xml_node::Type const &sub_node_type)
|
|
{
|
|
if (!from_node.has_sub_node(sub_node_type.string()))
|
|
return;
|
|
|
|
Xml_node const sub_node = from_node.sub_node(sub_node_type.string());
|
|
sub_node.with_raw_node([&] (char const *start, size_t length) {
|
|
xml.append(start, length); });
|
|
}
|
|
|
|
|
|
Child::Child(Allocator &alloc,
|
|
Xml_node start_node,
|
|
Timer::Connection &timer,
|
|
Signal_context_capability const &config_handler)
|
|
:
|
|
_skip { start_node.attribute_value("skip", false) },
|
|
_alloc { alloc },
|
|
_start_xml { _alloc, start_node },
|
|
_name { _start_xml->xml().attribute_value("name", Name()) },
|
|
_timer { timer },
|
|
_config_handler { config_handler }
|
|
{ }
|
|
|
|
|
|
Child::~Child()
|
|
{
|
|
while (Timeout_event *event = _timeout_events.first()) {
|
|
_timeout_events.remove(event);
|
|
destroy(_alloc, event);
|
|
}
|
|
while (Log_event *event = _log_events.first()) {
|
|
_log_events.remove(event);
|
|
destroy(_alloc, event);
|
|
}
|
|
}
|
|
|
|
|
|
void Child::log_session_write(Log_event::Line const &log_line)
|
|
{
|
|
if (_skip) {
|
|
return; }
|
|
|
|
enum { ASCII_ESC = 27 };
|
|
enum { ASCII_LF = 10 };
|
|
enum { ASCII_TAB = 9 };
|
|
|
|
struct Break : Exception { };
|
|
|
|
|
|
struct Skip_escape_sequence
|
|
{
|
|
char const * const base;
|
|
size_t const size;
|
|
};
|
|
struct Replace_ampersend_sequence
|
|
{
|
|
char const * const base;
|
|
size_t const size;
|
|
char const by;
|
|
};
|
|
static Skip_escape_sequence skip_esc_seq[5]
|
|
{
|
|
{ "[0m", 3 },
|
|
{ "[31m", 4 },
|
|
{ "[32m", 4 },
|
|
{ "[33m", 4 },
|
|
{ "[34m", 4 },
|
|
};
|
|
static Replace_ampersend_sequence replace_amp_seq[3]
|
|
{
|
|
{ "lt;", 3, '<' },
|
|
{ "amp;", 4, '&' },
|
|
{ "#42;", 4, '*' }
|
|
};
|
|
|
|
/* calculate timestamp that prefixes*/
|
|
Genode::uint64_t const time_us { _timer.curr_time().trunc_to_plain_us().value - init_time_us };
|
|
Genode::uint64_t time_ms { time_us / 1000UL };
|
|
Genode::uint64_t const time_sec { time_ms / 1000UL };
|
|
time_ms = time_ms - time_sec * 1000UL;
|
|
|
|
char const *const log_base { log_line.string() };
|
|
char const *const log_end { log_base + strlen(log_base) };
|
|
try {
|
|
char const *log_print { log_base };
|
|
_log_events.for_each([&] (Log_event &log_event) {
|
|
|
|
bool match { false };
|
|
char const *pattern_end { log_event.remaining_end() };
|
|
char const *pattern_curr { log_event.remaining_base() };
|
|
char const *log_curr { log_base };
|
|
|
|
for (;;) {
|
|
|
|
/* handle end of pattern */
|
|
if (pattern_curr == pattern_end) {
|
|
match = true;
|
|
log_event.remaining_base() = log_event.base();
|
|
log_event.reset_to() = log_event.base();
|
|
log_event.reset_retry() = false;
|
|
break;
|
|
}
|
|
/* skip irrelevant characters in the pattern */
|
|
if (*pattern_curr == ASCII_LF || *pattern_curr == ASCII_TAB) {
|
|
pattern_curr++;
|
|
continue;
|
|
}
|
|
if (*pattern_curr == '*') {
|
|
pattern_curr++;
|
|
log_event.reset_to() = pattern_curr;
|
|
log_event.reset_retry() = false;
|
|
continue;
|
|
}
|
|
/* handle end of log line */
|
|
if (log_curr == log_end) {
|
|
log_event.remaining_base() = pattern_curr;
|
|
break;
|
|
}
|
|
/* skip irrelevant characters in the log line */
|
|
if (*log_curr == ASCII_LF) {
|
|
|
|
/* forward to our log session a complete line */
|
|
if (log_print < log_curr) {
|
|
forward_to_log(time_sec, time_ms, log_print, log_curr);
|
|
log_print = log_curr + 1;
|
|
}
|
|
log_curr++;
|
|
continue;
|
|
}
|
|
if (*log_curr == ASCII_TAB) {
|
|
log_curr++;
|
|
continue;
|
|
}
|
|
/* skip irrelevant escape sequences in the log line */
|
|
if (*log_curr == ASCII_ESC) {
|
|
|
|
bool seq_match { false };
|
|
|
|
for (unsigned i = 0; i < sizeof(skip_esc_seq)/sizeof(skip_esc_seq[0]); i++) {
|
|
|
|
char const *seq_curr { skip_esc_seq[i].base };
|
|
char const *seq_end { seq_curr + skip_esc_seq[i].size };
|
|
|
|
for (char const *log_seq_curr { log_curr + 1 } ; ; log_seq_curr++, seq_curr++) {
|
|
|
|
if (seq_curr == seq_end) {
|
|
seq_match = true;
|
|
log_curr = log_seq_curr;
|
|
break;
|
|
}
|
|
if (log_seq_curr == log_end) {
|
|
break; }
|
|
|
|
if (*log_seq_curr != *seq_curr) {
|
|
break; }
|
|
}
|
|
if (seq_match) {
|
|
break; }
|
|
}
|
|
if (seq_match) {
|
|
continue; }
|
|
}
|
|
char pattern_curr_san = *pattern_curr;
|
|
size_t pattern_curr_san_sz = 1;
|
|
|
|
/* replace ampersend sequences in the pattern */
|
|
if (*pattern_curr == '&') {
|
|
|
|
bool seq_match { false };
|
|
|
|
for (unsigned i = 0; i < sizeof(replace_amp_seq)/sizeof(replace_amp_seq[0]); i++) {
|
|
|
|
char const *seq_curr { replace_amp_seq[i].base };
|
|
char const *seq_end { seq_curr + replace_amp_seq[i].size };
|
|
|
|
for (char const *pattern_seq_curr { pattern_curr + 1 } ; ; pattern_seq_curr++, seq_curr++) {
|
|
|
|
if (seq_curr == seq_end) {
|
|
seq_match = true;
|
|
pattern_curr_san = replace_amp_seq[i].by;
|
|
pattern_curr_san_sz = replace_amp_seq[i].size + 1;
|
|
break;
|
|
}
|
|
if (pattern_seq_curr == pattern_end) {
|
|
break; }
|
|
|
|
if (*pattern_seq_curr != *seq_curr) {
|
|
break; }
|
|
}
|
|
if (seq_match) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* check if log keeps matching pattern */
|
|
if (*log_curr != pattern_curr_san) {
|
|
pattern_curr = log_event.reset_to();
|
|
if (!log_event.reset_retry()) {
|
|
|
|
log_curr++; }
|
|
else {
|
|
log_event.reset_retry() = false; }
|
|
} else {
|
|
pattern_curr += pattern_curr_san_sz;
|
|
log_curr++;
|
|
log_event.reset_retry() = true;
|
|
}
|
|
}
|
|
/* forward to our log session what is left */
|
|
if (log_print < log_curr) {
|
|
for (;; log_curr++) {
|
|
|
|
if (log_curr == log_end) {
|
|
|
|
forward_to_log(time_sec, time_ms, log_print, log_curr);
|
|
log_print = log_curr;
|
|
break;
|
|
}
|
|
if (*log_curr == ASCII_LF) {
|
|
|
|
forward_to_log(time_sec, time_ms, log_print, log_curr);
|
|
log_print = log_curr + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/* check if log line finished a match with the pattern */
|
|
if (!match) {
|
|
return; }
|
|
|
|
/* execute event handler and stop trying further events */
|
|
event_occured(log_event, time_us);
|
|
throw Break();
|
|
});
|
|
}
|
|
catch (...) { }
|
|
}
|
|
|
|
|
|
void Child::apply_config(Xml_node start_node)
|
|
{
|
|
if (_skip)
|
|
return;
|
|
|
|
if (!start_node.differs_from(_start_xml->xml()))
|
|
return;
|
|
|
|
Archive::Path const old_pkg_path = _config_pkg_path();
|
|
|
|
/* import new start node */
|
|
_start_xml.construct(_alloc, start_node);
|
|
|
|
Archive::Path const new_pkg_path = _config_pkg_path();
|
|
|
|
/* invalidate blueprint if 'pkg' path changed */
|
|
if (old_pkg_path != new_pkg_path) {
|
|
_blueprint_pkg_path = new_pkg_path;
|
|
_pkg_xml.destruct();
|
|
|
|
/* reset error state, attempt to obtain the blueprint again */
|
|
_pkg_incomplete = false;
|
|
}
|
|
}
|
|
|
|
|
|
void Child::apply_blueprint(Xml_node pkg)
|
|
{
|
|
if (_skip) {
|
|
return; }
|
|
|
|
if (pkg.attribute_value("path", Archive::Path()) != _blueprint_pkg_path)
|
|
return;
|
|
|
|
try {
|
|
Xml_node const runtime = pkg.sub_node("runtime");
|
|
|
|
/* package was missing but is installed now */
|
|
_pkg_incomplete = false;
|
|
|
|
_pkg_ram_quota = runtime.attribute_value("ram", Number_of_bytes());
|
|
_pkg_cap_quota = runtime.attribute_value("caps", 0UL);
|
|
|
|
_binary_name = runtime.attribute_value("binary", Binary_name());
|
|
_config_name = runtime.attribute_value("config", Config_name());
|
|
|
|
/* keep copy of the blueprint info */
|
|
_pkg_xml.construct(_alloc, pkg);
|
|
}
|
|
catch (Xml_node::Nonexistent_sub_node) {
|
|
error("missing runtime subnode in packege blueprint");
|
|
}
|
|
}
|
|
|
|
|
|
void Child::apply_launcher(Launcher_name const &name,
|
|
Xml_node launcher)
|
|
{
|
|
if (_skip)
|
|
return;
|
|
|
|
if (!_defined_by_launcher())
|
|
return;
|
|
|
|
if (_launcher_name() != name)
|
|
return;
|
|
|
|
if (_launcher_xml.constructed() && !launcher.differs_from(_launcher_xml->xml()))
|
|
return;
|
|
|
|
_launcher_xml.construct(_alloc, launcher);
|
|
|
|
_blueprint_pkg_path = _config_pkg_path();
|
|
}
|
|
|
|
|
|
Child::State_name Child::_padded_state_name() const
|
|
{
|
|
if (_skip) {
|
|
return "?"; }
|
|
|
|
switch (_state) {
|
|
case SUCCEEDED: return "ok ";
|
|
case FAILED: return "failed ";
|
|
case SKIPPED:;
|
|
case UNFINISHED: ;
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
|
|
void Child::print_conclusion()
|
|
{
|
|
log(" ", _conclusion);
|
|
}
|
|
|
|
void Child::conclusion(Result &result)
|
|
{
|
|
struct Bad_state : Exception { };
|
|
switch (_state) {
|
|
case SUCCEEDED: result.succeeded++; break;
|
|
case FAILED: result.failed++; break;
|
|
case SKIPPED: result.skipped++; break;
|
|
default: throw Bad_state();
|
|
}
|
|
}
|
|
|
|
|
|
void Child::mark_as_incomplete(Xml_node missing)
|
|
{
|
|
if (_skip) {
|
|
return; }
|
|
|
|
/* print error message only once */
|
|
if(_pkg_incomplete)
|
|
return;
|
|
|
|
Archive::Path const path = missing.attribute_value("path", Archive::Path());
|
|
if (path != _blueprint_pkg_path)
|
|
return;
|
|
|
|
log(path, " incomplete or missing");
|
|
|
|
_pkg_incomplete = true;
|
|
}
|
|
|
|
|
|
void Child::reset_incomplete()
|
|
{
|
|
if (_skip) {
|
|
return; }
|
|
|
|
if (_pkg_incomplete) {
|
|
_pkg_incomplete = false;
|
|
_pkg_xml.destruct();
|
|
}
|
|
}
|
|
|
|
|
|
bool Child::gen_query(Xml_generator &xml) const
|
|
{
|
|
if (_skip) {
|
|
return false; }
|
|
|
|
if (_configured() || _pkg_incomplete)
|
|
return false;
|
|
|
|
if (_defined_by_launcher() && !_launcher_xml.constructed())
|
|
return false;
|
|
|
|
xml.node("blueprint", [&] () {
|
|
xml.attribute("pkg", _blueprint_pkg_path); });
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void Child::gen_installation_entry(Xml_generator &xml) const
|
|
{
|
|
if (_skip) {
|
|
return; }
|
|
|
|
if (!_pkg_incomplete) return;
|
|
|
|
xml.node("archive", [&] () {
|
|
xml.attribute("path", _config_pkg_path());
|
|
xml.attribute("source", "no");
|
|
});
|
|
}
|
|
|
|
|
|
void Child::event_occured(Event const &event,
|
|
Genode::uint64_t const time_us)
|
|
{
|
|
if (_skip) {
|
|
return; }
|
|
|
|
if (_state != UNFINISHED) {
|
|
return; }
|
|
|
|
if (event.meaning() == "succeeded") { _finished(SUCCEEDED, event, time_us); }
|
|
else if (event.meaning() == "failed" ) { _finished(FAILED, event, time_us); }
|
|
}
|
|
|
|
|
|
void Child::_finished(State state,
|
|
Event const &event,
|
|
Genode::uint64_t const time_us)
|
|
{
|
|
if (_skip) {
|
|
return; }
|
|
|
|
_running = false;
|
|
_state = state;
|
|
|
|
Genode::uint64_t time_ms { time_us / 1000UL };
|
|
Genode::uint64_t const time_sec { time_ms / 1000UL };
|
|
time_ms = time_ms - time_sec * 1000UL;
|
|
|
|
char name_padded[32];
|
|
for (size_t i = 0; i < sizeof(name_padded) - 1; name_padded[i++] = ' ');
|
|
name_padded[sizeof(name_padded) - 1] = '\0';
|
|
memcpy(name_padded, _name.string(), min(_name.length() - 1, sizeof(name_padded) - 1));
|
|
|
|
if (event.has_type(Event::Type::LOG)) {
|
|
|
|
enum { MAX_EXPL_SZ = 32 };
|
|
|
|
Log_event const &log_event = *static_cast<Log_event const *>(&event);
|
|
|
|
char const *const pattern_base = log_event.base();
|
|
size_t const pattern_sz = log_event.size();
|
|
char const *const pattern_end = pattern_base + pattern_sz;
|
|
|
|
char const *expl_base = pattern_base;
|
|
for (; expl_base < pattern_end && *expl_base < 33; expl_base++) ;
|
|
|
|
char const *expl_end = expl_base;
|
|
for (; expl_end < pattern_end && *expl_end > 31; expl_end++) ;
|
|
|
|
size_t const expl_sz = min((size_t)(expl_end - expl_base), (size_t)MAX_EXPL_SZ);
|
|
|
|
_conclusion = Conclusion {
|
|
Cstring(name_padded), " ", _padded_state_name(), " ",
|
|
time_sec < 10 ? " " : time_sec < 100 ? " " : "", time_sec, ".",
|
|
time_ms < 10 ? "00" : time_ms < 100 ? "0" : "", time_ms,
|
|
" log \"", Cstring(expl_base, expl_sz),
|
|
expl_sz < MAX_EXPL_SZ ? "" : " ...", "\"" };
|
|
|
|
} else if (event.has_type(Event::Type::TIMEOUT)) {
|
|
|
|
Timeout_event const &timeout_event = *static_cast<Timeout_event const *>(&event);
|
|
|
|
_conclusion = Conclusion {
|
|
Cstring(name_padded), " ", _padded_state_name(), " ",
|
|
time_sec < 10 ? " " : time_sec < 100 ? " " : "", time_sec, ".",
|
|
time_ms < 10 ? "00" : time_ms < 100 ? "0" : "", time_ms,
|
|
" timeout ", timeout_event.sec(), " sec" };
|
|
|
|
} else {
|
|
|
|
_conclusion = Conclusion { _padded_state_name(), " ", _name };
|
|
|
|
}
|
|
log("");
|
|
log(" ", _conclusion);
|
|
_config_handler.submit();
|
|
}
|
|
|
|
|
|
/*******************
|
|
** Timeout_event **
|
|
*******************/
|
|
|
|
Timeout_event::Timeout_event(Timer::Connection &timer,
|
|
Child &child,
|
|
Xml_node const &event)
|
|
:
|
|
Event { event, Type::TIMEOUT },
|
|
_child { child },
|
|
_timer { timer },
|
|
_sec { event.attribute_value("sec", (Genode::uint64_t)0) },
|
|
_timeout { timer, *this, &Timeout_event::_handle_timeout }
|
|
{
|
|
if (!_sec) {
|
|
throw Invalid(); }
|
|
|
|
_timeout.schedule(Microseconds(_sec * 1000 * 1000));
|
|
}
|
|
|
|
|
|
void Timeout_event::_handle_timeout(Duration)
|
|
{
|
|
_child.event_occured(*this, _timer.curr_time().trunc_to_plain_us().value - _child.init_time_us);
|
|
}
|
|
|
|
|
|
/***************
|
|
** Log_event **
|
|
***************/
|
|
|
|
static char const *xml_content_base(Xml_node node)
|
|
{
|
|
char const *result = nullptr;
|
|
node.with_raw_content([&] (char const *start, size_t) { result = start; });
|
|
return result;
|
|
}
|
|
|
|
|
|
static size_t xml_content_size(Xml_node node)
|
|
{
|
|
size_t result = 0;
|
|
node.with_raw_content([&] (char const *, size_t length) { result = length; });
|
|
return result;
|
|
}
|
|
|
|
|
|
Log_event::Log_event(Xml_node const &xml)
|
|
:
|
|
Event { xml, Type::LOG },
|
|
_base { xml_content_base(xml) },
|
|
_size { xml_content_size(xml) },
|
|
_remaining_base { xml_content_base(xml) },
|
|
_remaining_end { _remaining_base + xml_content_size(xml) },
|
|
_reset_to { xml_content_base(xml) }
|
|
{ }
|
|
|
|
|
|
/***********
|
|
** Event **
|
|
***********/
|
|
|
|
Event::Event(Xml_node const &node,
|
|
Type type)
|
|
:
|
|
_meaning { node.attribute_value("meaning", Meaning_string()) },
|
|
_type { type }
|
|
{
|
|
if (_meaning != Meaning_string("failed") &&
|
|
_meaning != Meaning_string("succeeded"))
|
|
{
|
|
throw Invalid();
|
|
}
|
|
}
|
|
|
|
|
|
/************
|
|
** Result **
|
|
************/
|
|
|
|
void Result::print(Output &output) const
|
|
{
|
|
Genode::print(output, "succeeded: ", succeeded, " failed: ", failed,
|
|
" skipped: ", skipped);
|
|
}
|