genode/repos/gems/src/app/depot_deploy/child.h

557 lines
16 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.
*/
#ifndef _CHILD_H_
#define _CHILD_H_
/* Genode includes */
#include <util/list_model.h>
#include <base/service.h>
#include <os/reporter.h>
#include <os/buffered_xml.h>
#include <depot/archive.h>
namespace Depot_deploy {
using namespace Depot;
struct Child;
}
class Depot_deploy::Child : public List_model<Child>::Element
{
public:
typedef String<100> Name;
typedef String<80> Binary_name;
typedef String<80> Config_name;
typedef String<32> Depot_rom_server;
typedef String<100> Launcher_name;
private:
Allocator &_alloc;
Reconstructible<Buffered_xml> _start_xml; /* from config */
Constructible<Buffered_xml> _launcher_xml { };
Constructible<Buffered_xml> _pkg_xml { }; /* from blueprint */
/*
* State of the condition check for generating the start node of
* the child. I.e., if the child is complete and configured but
* a used server component is missing, we need to suppress the start
* node until the condition is satisfied.
*/
enum Condition { UNCHECKED, SATISFIED, UNSATISFIED };
Condition _condition { UNCHECKED };
Name const _name;
bool _defined_by_launcher() const
{
/*
* 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 _config_pkg_path() const
{
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());
}
Launcher_name _launcher_name() const
{
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());
}
/*
* The pkg-archive path of the current blueprint query, which may
* deviate from pkg path given in the config, once the config is
* updated.
*/
Archive::Path _blueprint_pkg_path = _config_pkg_path();
/*
* Quota definitions obtained from the blueprint
*/
Number_of_bytes _pkg_ram_quota { 0 };
unsigned long _pkg_cap_quota { 0 };
unsigned long _pkg_cpu_quota { 0 };
Binary_name _binary_name { };
Config_name _config_name { };
/*
* Set if the depot query for the child's blueprint failed.
*/
bool _pkg_incomplete = false;
bool _configured() const
{
return _pkg_xml.constructed()
&& (_config_pkg_path() == _blueprint_pkg_path);
}
inline void _gen_routes(Xml_generator &, Xml_node,
Depot_rom_server const &,
Depot_rom_server const &) const;
static void _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); });
}
static void _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); });
}
public:
Child(Allocator &alloc, Xml_node start_node)
:
_alloc(alloc),
_start_xml(_alloc, start_node),
_name(_start_xml->xml().attribute_value("name", Name()))
{ }
Name name() const { return _name; }
void apply_config(Xml_node start_node)
{
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 apply_blueprint(Xml_node pkg)
{
if (pkg.attribute_value("path", Archive::Path()) != _blueprint_pkg_path)
return;
/* package was missing but is installed now */
_pkg_incomplete = false;
Xml_node const runtime = pkg.sub_node("runtime");
_pkg_ram_quota = runtime.attribute_value("ram", Number_of_bytes());
_pkg_cap_quota = runtime.attribute_value("caps", 0UL);
_pkg_cpu_quota = runtime.attribute_value("cpu", 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);
}
void apply_launcher(Launcher_name const &name, Xml_node launcher)
{
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();
}
/*
* \return true if condition changed
*/
template <typename COND_FN>
bool apply_condition(COND_FN const &fn)
{
Condition const orig_condition = _condition;
Xml_node launcher_xml = _launcher_xml.constructed()
? _launcher_xml->xml()
: Xml_node("<empty/>");
if (_start_xml.constructed())
_condition = fn(_start_xml->xml(), launcher_xml)
? SATISFIED : UNSATISFIED;
return _condition != orig_condition;
}
template <typename FN>
void apply_if_unsatisfied(FN const &fn) const
{
Xml_node launcher_xml = _launcher_xml.constructed()
? _launcher_xml->xml()
: Xml_node("<empty/>");
if (_condition == UNSATISFIED && _start_xml.constructed())
fn(_start_xml->xml(), launcher_xml, _name);
}
void mark_as_incomplete(Xml_node missing)
{
/* 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;
}
/**
* Reconsider deployment of child after installing missing archives
*/
void reset_incomplete()
{
if (_pkg_incomplete) {
_pkg_incomplete = false;
_pkg_xml.destruct();
}
}
bool blueprint_needed() const
{
if (_configured() || _pkg_incomplete)
return false;
if (_defined_by_launcher() && !_launcher_xml.constructed())
return false;
return true;
}
void gen_query(Xml_generator &xml) const
{
if (blueprint_needed())
xml.node("blueprint", [&] () {
xml.attribute("pkg", _blueprint_pkg_path); });
}
/**
* Generate start node of init configuration
*
* \param common session routes to be added in addition to
* the ones found in the pkg blueprint
* \param cached_depot_rom name of the server that provides the depot
* content as ROM modules. If the string is
* invalid, ROM requests are routed to the
* parent.
* \param uncached_depot_rom name of the depot-ROM server used to obtain
* the content of the depot user "local", which
* is assumed to be mutable
*/
inline void gen_start_node(Xml_generator &, Xml_node common,
Depot_rom_server const &cached_depot_rom,
Depot_rom_server const &uncached_depot_rom) const;
/**
* Generate installation entry needed for the completion of the child
*/
void gen_installation_entry(Xml_generator &xml) const
{
if (!_pkg_incomplete) return;
xml.node("archive", [&] () {
xml.attribute("path", _config_pkg_path());
xml.attribute("source", "no");
});
}
bool incomplete() const { return _pkg_incomplete; }
};
void Depot_deploy::Child::gen_start_node(Xml_generator &xml, Xml_node common,
Depot_rom_server const &cached_depot_rom,
Depot_rom_server const &uncached_depot_rom) const
{
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("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));
});
unsigned long cpu_quota = _pkg_cpu_quota;
if (_defined_by_launcher())
cpu_quota = _launcher_xml->xml().attribute_value("cpu", cpu_quota);
cpu_quota = _start_xml->xml().attribute_value("cpu", cpu_quota);
xml.node("resource", [&] () {
xml.attribute("name", "CPU");
xml.attribute("quantum", cpu_quota);
});
/* location handling */
bool const affinity_from_launcher = _defined_by_launcher()
&& (_launcher_xml->xml().has_attribute("xpos") ||
_launcher_xml->xml().has_attribute("ypos"));
bool const affinity_from_start = _start_xml->xml().has_attribute("xpos")
|| _start_xml->xml().has_attribute("ypos");
if (affinity_from_start || affinity_from_launcher) {
long xpos = 0, ypos = 0;
unsigned width = 1, height = 1;
if (affinity_from_launcher) {
xpos = _launcher_xml->xml().attribute_value("xpos", xpos);
ypos = _launcher_xml->xml().attribute_value("ypos", ypos);
width = _launcher_xml->xml().attribute_value("width", width);
height = _launcher_xml->xml().attribute_value("height", height);
}
xpos = _start_xml->xml().attribute_value("xpos", xpos);
ypos = _start_xml->xml().attribute_value("ypos", ypos);
width = _start_xml->xml().attribute_value("width", width);
height = _start_xml->xml().attribute_value("height", height);
xml.node("affinity", [&] () {
xml.attribute("xpos", xpos);
xml.attribute("ypos", ypos);
xml.attribute("width", width);
xml.attribute("height", height);
});
}
/* runtime handling */
Xml_node const runtime = _pkg_xml->xml().sub_node("runtime");
/*
* 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); });
});
}
void Depot_deploy::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 (!_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); });
}
});
});
}
#endif /* _CHILD_H_ */