diff --git a/repos/os/lib/mk/sandbox.mk b/repos/os/lib/mk/sandbox.mk index 9fe8dfab35..9e30fd1828 100644 --- a/repos/os/lib/mk/sandbox.mk +++ b/repos/os/lib/mk/sandbox.mk @@ -1,4 +1,4 @@ -SRC_CC = library.cc child.cc server.cc +SRC_CC = library.cc child.cc server.cc config_model.cc INC_DIR += $(REP_DIR)/src/lib/sandbox LIBS += base diff --git a/repos/os/recipes/raw/test-init/test-init.config b/repos/os/recipes/raw/test-init/test-init.config index 98e6ebf156..aa2ac2fe23 100644 --- a/repos/os/recipes/raw/test-init/test-init.config +++ b/repos/os/recipes/raw/test-init/test-init.config @@ -64,7 +64,7 @@ - + @@ -261,7 +261,7 @@ - + @@ -306,7 +306,7 @@ - + @@ -524,6 +524,7 @@ + @@ -551,7 +552,7 @@ - + @@ -759,7 +760,7 @@ - + @@ -838,7 +839,7 @@ - + @@ -1078,7 +1079,7 @@ - + @@ -1155,7 +1156,7 @@ - + @@ -1246,6 +1247,7 @@ + @@ -1266,6 +1268,7 @@ good-case tests. --> + @@ -1478,11 +1481,11 @@ - + - + @@ -1513,7 +1516,7 @@ - + @@ -1543,7 +1546,7 @@ - + @@ -1657,8 +1660,8 @@ - - + + @@ -1682,7 +1685,7 @@ - + @@ -1701,7 +1704,7 @@ - + @@ -1730,7 +1733,7 @@ - + diff --git a/repos/os/src/init/target.mk b/repos/os/src/init/target.mk index 279736b2d6..bc74bb5f0f 100644 --- a/repos/os/src/init/target.mk +++ b/repos/os/src/init/target.mk @@ -6,6 +6,6 @@ INC_DIR += $(PRG_DIR) CONFIG_XSD = config.xsd # statically link sandbox library to avoid dependency from sandbox.lib.so -SRC_CC += library.cc child.cc server.cc +SRC_CC += library.cc child.cc server.cc config_model.cc INC_DIR += $(REP_DIR)/src/lib/sandbox vpath %.cc $(REP_DIR)/src/lib/sandbox diff --git a/repos/os/src/lib/sandbox/alias.h b/repos/os/src/lib/sandbox/alias.h index d723365c69..7f05a5052d 100644 --- a/repos/os/src/lib/sandbox/alias.h +++ b/repos/os/src/lib/sandbox/alias.h @@ -19,34 +19,28 @@ namespace Sandbox { struct Alias; } -struct Sandbox::Alias : List::Element +struct Sandbox::Alias : List::Element, Noncopyable { - typedef String<128> Name; - typedef String<128> Child; + typedef Child_policy::Name Name; + typedef Child_policy::Name Child; - Name name; - Child child; + Name const name; - /** - * Exception types + Child child { }; /* defined by 'update' */ + + Alias(Name const &name) : name(name) { } + + class Child_attribute_missing : Exception { }; + + /* + * \throw Child_attribute_missing */ - class Name_is_missing : Exception { }; - class Child_is_missing : Exception { }; - - /** - * Constructor - * - * \throw Name_is_missing - * \throw Child_is_missing - */ - Alias(Genode::Xml_node alias) - : - name (alias.attribute_value("name", Name())), - child(alias.attribute_value("child", Child())) + void update(Xml_node const &alias) { + if (!alias.has_attribute("child")) + warning("alias node \"", name, "\" lacks child attribute"); - if (!name.valid()) throw Name_is_missing(); - if (!child.valid()) throw Child_is_missing(); + child = alias.attribute_value("child", Child()); } }; diff --git a/repos/os/src/lib/sandbox/child.cc b/repos/os/src/lib/sandbox/child.cc index 0a39c8cda9..6fc765dd5f 100644 --- a/repos/os/src/lib/sandbox/child.cc +++ b/repos/os/src/lib/sandbox/child.cc @@ -11,6 +11,7 @@ * under the terms of the GNU Affero General Public License version 3. */ +/* Genode includes */ #include /* local includes */ @@ -28,14 +29,14 @@ void Sandbox::Child::destroy_services() Sandbox::Child::Apply_config_result Sandbox::Child::apply_config(Xml_node start_node) { - if (_state == STATE_ABANDONED || _exited) + if (abandoned() || stuck() || restart_scheduled() || _exited) return NO_SIDE_EFFECTS; /* - * If the child's environment is incomplete, restart it to attempt - * the re-routing of its environment sessions. + * If the child was started but its environment is incomplete, mark it as + * being stuck in order to restart it once the environment changes. */ - { + if (_state != State::INITIAL) { bool env_log_exists = false, env_binary_exists = false; _child.for_each_session([&] (Session_state const &session) { Parent::Client::Id const id = session.id_at_client(); @@ -44,8 +45,8 @@ Sandbox::Child::apply_config(Xml_node start_node) }); if (!env_binary_exists || !env_log_exists) { - abandon(); - return MAY_HAVE_SIDE_EFFECTS; + _state = State::STUCK; + return NO_SIDE_EFFECTS; } } @@ -62,14 +63,24 @@ Sandbox::Child::apply_config(Xml_node start_node) if (start_node.differs_from(_start_node->xml())) { /* - * Start node changed - * + * The node may affect the availability or unavailability + * of dependencies. + */ + start_node.with_sub_node("route", [&] (Xml_node const &route) { + _start_node->xml().with_sub_node("route", [&] (Xml_node const &orig) { + if (route.differs_from(orig)) + _uncertain_dependencies = true; }); }); + + /* * Determine how the inline config is affected. */ char const * const tag = "config"; bool const config_was_present = _start_node->xml().has_sub_node(tag); bool const config_is_present = start_node.has_sub_node(tag); + if (config_was_present != config_is_present) + _uncertain_dependencies = true; + if (config_was_present && !config_is_present) config_update = CONFIG_VANISHED; @@ -125,7 +136,10 @@ Sandbox::Child::apply_config(Xml_node start_node) * the binary's ROM session, triggering the restart of the * child. */ + Binary_name const orig_binary_name = _binary_name; _binary_name = _binary_from_xml(start_node, _unique_name); + if (orig_binary_name != _binary_name) + _uncertain_dependencies = true; _heartbeat_enabled = start_node.has_sub_node("heartbeat"); @@ -136,8 +150,8 @@ Sandbox::Child::apply_config(Xml_node start_node) /* * Apply change to '_config_rom_service'. This will * potentially result in a change of the "config" ROM route, which - * may in turn prompt the routing-check below to abandon (restart) - * the child. + * may in turn prompt the routing-check by 'evaluate_dependencies' + * to restart the child. */ switch (config_update) { case CONFIG_UNCHANGED: break; @@ -146,26 +160,39 @@ Sandbox::Child::apply_config(Xml_node start_node) case CONFIG_VANISHED: _config_rom_service->abandon(); break; } - /* validate that the routes of all existing sessions remain intact */ - { - bool routing_changed = false; - _child.for_each_session([&] (Session_state const &session) { - if (!_route_valid(session)) - routing_changed = true; }); - - if (routing_changed) { - abandon(); - return MAY_HAVE_SIDE_EFFECTS; - } - } - if (provided_services_changed) - return MAY_HAVE_SIDE_EFFECTS; + return PROVIDED_SERVICES_CHANGED; return NO_SIDE_EFFECTS; } +void Sandbox::Child::evaluate_dependencies() +{ + bool any_route_changed = false; + bool any_route_unavailable = false; + + _child.for_each_session([&] (Session_state const &session) { + + switch (_route_valid(session)) { + case Route_state::VALID: break; + case Route_state::UNAVAILABLE: any_route_unavailable = true; break; + case Route_state::MISMATCH: any_route_changed = true; break; + } + }); + + _uncertain_dependencies = false; + + if (any_route_unavailable) { + _state = State::STUCK; + return; + } + + if (any_route_changed || stuck()) + _schedule_restart(); +} + + Sandbox::Ram_quota Sandbox::Child::_configured_ram_quota() const { size_t assigned = 0; @@ -338,7 +365,7 @@ void Sandbox::Child::report_state(Xml_generator &xml, Report_detail const &detai if (detail.ids()) xml.attribute("id", _id.value); - if (!_child.active()) + if (stuck() || _state == State::RAM_INITIALIZED) xml.attribute("state", "incomplete"); if (_exited) @@ -420,11 +447,29 @@ void Sandbox::Child::init(Pd_session &session, Pd_session_capability cap) size_t const initial_session_costs = session_alloc_batch_size()*_child.session_factory().session_costs(); - Ram_quota const ram_quota { _resources.effective_ram_quota().value > initial_session_costs - ? _resources.effective_ram_quota().value - initial_session_costs - : 0 }; + Ram_quota ram_quota { _resources.effective_ram_quota().value > initial_session_costs + ? _resources.effective_ram_quota().value - initial_session_costs + : 0 }; - Cap_quota const cap_quota { _resources.effective_cap_quota().value }; + Ram_quota avail_ram = _ram_limit_accessor.resource_limit(Ram_quota()); + + avail_ram = Genode::Child::effective_quota(avail_ram); + + if (ram_quota.value > avail_ram.value) { + warning(name(), ": configured RAM exceeds available RAM, proceed with ", avail_ram); + ram_quota = avail_ram; + } + + Cap_quota cap_quota { _resources.effective_cap_quota().value }; + + Cap_quota avail_caps = _cap_limit_accessor.resource_limit(avail_caps); + + avail_caps = Genode::Child::effective_quota(avail_caps); + + if (cap_quota.value > avail_caps.value) { + warning(name(), ": configured caps exceed available caps, proceed with ", avail_caps); + cap_quota = avail_caps; + } try { _env.pd().transfer_quota(cap, cap_quota); } catch (Out_of_caps) { @@ -718,8 +763,6 @@ Sandbox::Child::Child(Env &env, Default_route_accessor &default_route_accessor, Default_caps_accessor &default_caps_accessor, Name_registry &name_registry, - Ram_quota ram_limit, - Cap_quota cap_limit, Ram_limit_accessor &ram_limit_accessor, Cap_limit_accessor &cap_limit_accessor, Prio_levels prio_levels, @@ -739,8 +782,7 @@ Sandbox::Child::Child(Env &env, _name_registry(name_registry), _heartbeat_enabled(start_node.has_sub_node("heartbeat")), _resources(_resources_from_start_node(start_node, prio_levels, affinity_space, - default_caps_accessor.default_caps(), cap_limit)), - _resources_clamped_to_limit((_clamp_resources(ram_limit, cap_limit), true)), + default_caps_accessor.default_caps())), _parent_services(parent_services), _child_services(child_services), _local_services(local_services), diff --git a/repos/os/src/lib/sandbox/child.h b/repos/os/src/lib/sandbox/child.h index e0385616f6..11fca7ca63 100644 --- a/repos/os/src/lib/sandbox/child.h +++ b/repos/os/src/lib/sandbox/child.h @@ -78,10 +78,34 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Id const _id; - enum State { STATE_INITIAL, STATE_RAM_INITIALIZED, STATE_ALIVE, - STATE_ABANDONED }; + enum class State { - State _state = STATE_INITIAL; + /* + * States modelling the child's boostrap phase + */ + INITIAL, RAM_INITIALIZED, ALIVE, + + /* + * The child is present in the config model but its bootstrapping + * permanently failed. + */ + STUCK, + + /* + * The child must be restarted because a fundamental dependency + * changed. While the child is in this state, it is still + * referenced by the config model. + */ + RESTART_SCHEDULED, + + /* + * The child is no longer referenced by config model and can + * safely be destructed. + */ + ABANDONED + }; + + State _state = State::INITIAL; Report_update_trigger &_report_update_trigger; @@ -94,6 +118,8 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup */ Version _version { _start_node->xml().attribute_value("version", Version()) }; + bool _uncertain_dependencies = false; + /* * True if the binary is loaded with ld.lib.so */ @@ -117,7 +143,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup if (name.valid()) return name; - warning("missint 'name' attribute in '' entry"); + warning("missing 'name' attribute in '' entry"); throw Missing_name_attribute(); } @@ -151,7 +177,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup bool _heartbeat_expected() const { /* don't expect heartbeats from a child that is not yet complete */ - return _heartbeat_enabled && (_state == STATE_ALIVE); + return _heartbeat_enabled && (_state == State::ALIVE); } /** @@ -193,9 +219,10 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup } }; + static Resources _resources_from_start_node(Xml_node start_node, Prio_levels prio_levels, Affinity::Space const &affinity_space, - Cap_quota default_cap_quota, Cap_quota) + Cap_quota default_cap_quota) { size_t cpu_quota_pc = 0; Number_of_bytes ram_bytes = 0; @@ -208,7 +235,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Name const name = rsc.attribute_value("name", Name()); if (name == "RAM") { - ram_bytes = rsc.attribute_value("quantum", ram_bytes); + ram_bytes = rsc.attribute_value("quantum", ram_bytes); } if (name == "CPU") { @@ -231,29 +258,9 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Resources _resources; - /** - * Print diagnostic information on misconfiguration - */ - void _clamp_resources(Ram_quota ram_limit, Cap_quota cap_limit) - { - if (_resources.assigned_ram_quota.value > ram_limit.value) { - warning(name(), " assigned RAM (", _resources.assigned_ram_quota, ") " - "exceeds available RAM (", ram_limit, ")"); - _resources.assigned_ram_quota = ram_limit; - } - - if (_resources.assigned_cap_quota.value > cap_limit.value) { - warning(name(), " assigned caps (", _resources.assigned_cap_quota, ") " - "exceed available caps (", cap_limit, ")"); - _resources.assigned_cap_quota = cap_limit; - } - } - Ram_quota _configured_ram_quota() const; Cap_quota _configured_cap_quota() const; - bool const _resources_clamped_to_limit; - using Local_service = Genode::Sandbox::Local_service_base; Registry &_parent_services; @@ -375,6 +382,8 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup _session_requester.trigger_update(); } + enum class Route_state { VALID, MISMATCH, UNAVAILABLE }; + /** * Return true if the policy results in the current route of the session * @@ -382,7 +391,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup * client session of a child, i.e., to determine whether the child must * be restarted. */ - bool _route_valid(Session_state const &session) + Route_state _route_valid(Session_state const &session) { try { Route const route = @@ -390,10 +399,12 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup session.client_label(), session.diag()); - return (session.service() == route.service) - && (route.label == session.label()); + bool const valid = (session.service() == route.service) + && (route.label == session.label()); + + return valid ? Route_state::VALID : Route_state::MISMATCH; } - catch (Service_denied) { return false; } + catch (Service_denied) { return Route_state::UNAVAILABLE; } } static Xml_node _provides_sub_node(Xml_node start_node) @@ -421,7 +432,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup service.name() == node.attribute_value("name", Service::Name())) exists = true; }); - return exists && !abandoned(); + return exists && !abandoned() && !restart_scheduled(); } void _add_service(Xml_node service) @@ -454,7 +465,7 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup */ bool _pd_alive() const { - return !abandoned() && !_exited; + return !abandoned() && !restart_scheduled() && !_exited; } void _destroy_services(); @@ -477,6 +488,19 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup } _sampled_state { }; + void _abandon_services() + { + _child_services.for_each([&] (Routed_service &service) { + if (service.has_id_space(_session_requester.id_space())) + service.abandon(); }); + } + + void _schedule_restart() + { + _state = State::RESTART_SCHEDULED; + _abandon_services(); + } + public: /** @@ -505,8 +529,6 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Default_route_accessor &default_route_accessor, Default_caps_accessor &default_caps_accessor, Name_registry &name_registry, - Ram_quota ram_limit, - Cap_quota cap_limit, Ram_limit_accessor &ram_limit_accessor, Cap_limit_accessor &cap_limit_accessor, Prio_levels prio_levels, @@ -527,50 +549,55 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Ram_quota ram_quota() const { return _resources.assigned_ram_quota; } Cap_quota cap_quota() const { return _resources.assigned_cap_quota; } - void initiate_env_pd_session() + void try_start() { - if (_state == STATE_INITIAL) { + if (_state == State::INITIAL) { _child.initiate_env_pd_session(); - _state = STATE_RAM_INITIALIZED; + _state = State::RAM_INITIALIZED; } - } - void initiate_env_sessions() - { - if (_state == STATE_RAM_INITIALIZED) { + /* + * Update the state if async env sessions have brought the child to + * life. Otherwise, we would wrongly call 'initiate_env_sessions()' + * another time. + */ + if (_state == State::RAM_INITIALIZED && _child.active()) + _state = State::ALIVE; + if (_state == State::RAM_INITIALIZED) { _child.initiate_env_sessions(); - /* check for completeness of the child's environment */ - if (_verbose.enabled()) - _child.for_each_session([&] (Session_state const &session) { - if (!session.alive()) - warning(name(), ": incomplete environment ", - session.service().name(), " session " - "(", session.label(), ")"); }); - - _state = STATE_ALIVE; + if (_child.active()) + _state = State::ALIVE; + else + _uncertain_dependencies = true; } } + /* + * Mark child as to be removed because its was dropped from the + * config model. Either node disappeared or 'restart_scheduled' + * was handled. + */ void abandon() { - _state = STATE_ABANDONED; - - _child_services.for_each([&] (Routed_service &service) { - if (service.has_id_space(_session_requester.id_space())) - service.abandon(); }); + _state = State::ABANDONED; + _abandon_services(); } void destroy_services(); void close_all_sessions() { _child.close_all_sessions(); } - bool abandoned() const { return _state == STATE_ABANDONED; } + bool abandoned() const { return _state == State::ABANDONED; } + + bool restart_scheduled() const { return _state == State::RESTART_SCHEDULED; } + + bool stuck() const { return _state == State::STUCK; } bool env_sessions_closed() const { return _child.env_sessions_closed(); } - enum Apply_config_result { MAY_HAVE_SIDE_EFFECTS, NO_SIDE_EFFECTS }; + enum Apply_config_result { PROVIDED_SERVICES_CHANGED, NO_SIDE_EFFECTS }; /** * Apply new configuration to child @@ -580,6 +607,15 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup */ Apply_config_result apply_config(Xml_node start_node); + bool uncertain_dependencies() const { return _uncertain_dependencies; } + + /** + * Validate that the routes of all existing sessions remain intact + * + * The child may become scheduled for restart or get stuck. + */ + void evaluate_dependencies(); + /* common code for upgrading RAM and caps */ template void _apply_resource_upgrade(QUOTA &, QUOTA, LIMIT_ACCESSOR const &); diff --git a/repos/os/src/lib/sandbox/child_registry.h b/repos/os/src/lib/sandbox/child_registry.h index 9b0cbfc03c..b13e7527fa 100644 --- a/repos/os/src/lib/sandbox/child_registry.h +++ b/repos/os/src/lib/sandbox/child_registry.h @@ -29,30 +29,8 @@ class Sandbox::Child_registry : public Name_registry, Child_list List _aliases { }; - bool _unique(const char *name) const - { - /* check for name clash with an existing child */ - List_element const *curr = first(); - for (; curr; curr = curr->next()) - if (curr->object()->has_name(name)) - return false; - - /* check for name clash with an existing alias */ - for (Alias const *a = _aliases.first(); a; a = a->next()) { - if (Alias::Name(name) == a->name) - return false; - } - - return true; - } - public: - /** - * Exception type - */ - class Alias_name_is_not_unique { }; - /** * Register child */ @@ -74,10 +52,6 @@ class Sandbox::Child_registry : public Name_registry, Child_list */ void insert_alias(Alias *alias) { - if (!_unique(alias->name.string())) { - error("alias name ", alias->name, " is not unique"); - throw Alias_name_is_not_unique(); - } _aliases.insert(alias); } @@ -89,22 +63,6 @@ class Sandbox::Child_registry : public Name_registry, Child_list _aliases.remove(alias); } - /** - * Return any of the registered children, or 0 if no child exists - */ - Child *any() - { - return first() ? first()->object() : 0; - } - - /** - * Return any of the registered aliases, or 0 if no alias exists - */ - Alias *any_alias() - { - return _aliases.first() ? _aliases.first() : 0; - } - template void for_each_child(FN const &fn) const { @@ -127,7 +85,6 @@ class Sandbox::Child_registry : public Name_registry, Child_list { for_each_child([&] (Child &child) { child.report_state(xml, detail); }); - /* check for name clash with an existing alias */ for (Alias const *a = _aliases.first(); a; a = a->next()) { xml.node("alias", [&] () { xml.attribute("name", a->name); diff --git a/repos/os/src/lib/sandbox/config_model.cc b/repos/os/src/lib/sandbox/config_model.cc new file mode 100644 index 0000000000..0c48d2e977 --- /dev/null +++ b/repos/os/src/lib/sandbox/config_model.cc @@ -0,0 +1,402 @@ +/* + * \brief Internal model of the XML configuration + * \author Norman Feske + * \date 2021-04-02 + */ + +/* + * Copyright (C) 2021 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. + */ + +#include + +using namespace Sandbox; + + +struct Config_model::Node : Noncopyable, Interface, private List_model::Element +{ + friend class List_model; + friend class List; + + virtual bool matches(Xml_node const &) const = 0; + + virtual void update(Xml_node const &) = 0; + + virtual void apply_child_restart(Xml_node const &) { /* only implemented by 'Start_node' */ } + + virtual void trigger_start_child() { /* only implemented by 'Start_node' */ } +}; + + +struct Config_model::Parent_provides_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("parent-provides"); + } + + Parent_provides_model _model; + + Parent_provides_node(Allocator &alloc, Verbose const &verbose, + Parent_provides_model::Factory &factory) + : + _model(alloc, verbose, factory) + { } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override { _model.update_from_xml(xml); } +}; + + +struct Config_model::Default_route_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("default-route"); + } + + Allocator &_alloc; + Constructible &_default_route; + + Default_route_node(Allocator &alloc, Constructible &default_route) + : _alloc(alloc), _default_route(default_route) { } + + ~Default_route_node() { _default_route.destruct(); } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override + { + if (!_default_route.constructed() || _default_route->xml().differs_from(xml)) + _default_route.construct(_alloc, xml); + } +}; + + +struct Config_model::Default_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("default"); + } + + Cap_quota &_default_caps; + + Default_node(Cap_quota &default_caps) : _default_caps(default_caps) { } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override + { + _default_caps = Cap_quota { xml.attribute_value("caps", 0UL) }; + } +}; + + +struct Config_model::Affinity_space_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("affinity-space"); + } + + Constructible &_affinity_space; + + Affinity_space_node(Constructible &affinity_space) + : _affinity_space(affinity_space) { } + + ~Affinity_space_node() { _affinity_space.destruct(); } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override + { + _affinity_space.construct(xml.attribute_value("width", 1u), + xml.attribute_value("height", 1u)); + } +}; + + +struct Config_model::Start_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("start") || xml.has_type("alias"); + } + + Start_model _model; + + /* + * \throw Start_model::Factory::Creation_failed + */ + Start_node(Start_model::Factory &factory, Xml_node const &xml) + : _model(factory, xml) { } + + bool matches(Xml_node const &xml) const override + { + return type_matches(xml) && _model.matches(xml); + } + + void update(Xml_node const &xml) override + { + _model.update_from_xml(xml); + } + + void apply_child_restart(Xml_node const &xml) override + { + _model.apply_child_restart(xml); + } + + void trigger_start_child() override + { + _model.trigger_start_child(); + } +}; + + +struct Config_model::Report_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("report"); + } + + Version const &_version; + State_reporter &_state_reporter; + + Report_node(Version const &version, State_reporter &state_reporter) + : _version(version), _state_reporter(state_reporter) { } + + ~Report_node() + { + _state_reporter.apply_config(_version, Xml_node("")); + } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override + { + _state_reporter.apply_config(_version, xml); + } +}; + + +struct Config_model::Resource_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("resource"); + } + + enum class Category { RAM, CAP } const _category; + + class Unknown_resource_name : Exception { }; + + static Category _category_from_xml(Xml_node const &xml) + { + typedef String<16> Name; + Name const name = xml.attribute_value("name", Name()); + + if (name == "RAM") return Category::RAM; + if (name == "CAP") return Category::CAP; + + throw Unknown_resource_name(); + } + + Preservation &_keep; + + /* + * \throw Unknown_resource_name + */ + Resource_node(Preservation &keep, Xml_node xml) + : + _category(_category_from_xml(xml)), _keep(keep) + { } + + ~Resource_node() + { + switch (_category) { + case Category::RAM: _keep.ram = Preservation::default_ram(); break; + case Category::CAP: _keep.caps = Preservation::default_caps(); break; + } + } + + bool matches(Xml_node const &xml) const override + { + return type_matches(xml) && _category == _category_from_xml(xml); + } + + void update(Xml_node const &xml) override + { + switch (_category) { + + case Category::RAM: + { + Number_of_bytes keep { Preservation::default_ram().value }; + _keep.ram = { xml.attribute_value("preserve", keep) }; + break; + } + + case Category::CAP: + { + size_t keep = Preservation::default_caps().value; + _keep.caps = { xml.attribute_value("preserve", keep) }; + break; + } + } + } +}; + + +struct Config_model::Heartbeat_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("heartbeat"); + } + + Heartbeat &_heartbeat; + + Heartbeat_node(Heartbeat &heartbeat) : _heartbeat(heartbeat) { } + + ~Heartbeat_node() + { + _heartbeat.disable(); + } + + bool matches(Xml_node const &xml) const override { return type_matches(xml); } + + void update(Xml_node const &xml) override + { + _heartbeat.apply_config(xml); + } +}; + + +struct Config_model::Service_node : Node +{ + static bool type_matches(Xml_node const &xml) + { + return xml.has_type("service"); + } + + Service_model::Factory &_factory; + + Service_model &_model; + + Service_node(Service_model::Factory &factory, Xml_node const &xml) + : _factory(factory), _model(factory.create_service(xml)) { } + + ~Service_node() { _factory.destroy_service(_model); } + + bool matches(Xml_node const &xml) const override + { + return type_matches(xml) && _model.matches(xml); + } + + void update(Xml_node const &xml) override { _model.update_from_xml(xml); } +}; + + +void Config_model::update_from_xml(Xml_node const &xml, + Allocator &alloc, + Reconstructible &verbose, + Version &version, + Preservation &preservation, + Constructible &default_route, + Cap_quota &default_caps, + Prio_levels &prio_levels, + Constructible &affinity_space, + Start_model::Factory &child_factory, + Parent_provides_model::Factory &parent_service_factory, + Service_model::Factory &service_factory, + State_reporter &state_reporter, + Heartbeat &heartbeat) +{ + /* config version to be reflected in state reports */ + version = xml.attribute_value("version", Version()); + + preservation.reset(); + + prio_levels = ::Sandbox::prio_levels_from_xml(xml); + + affinity_space.destruct(); + + verbose.construct(xml); + + class Unknown_element_type : Exception { }; + + auto destroy = [&] (Node &node) { Genode::destroy(alloc, &node); }; + + auto create = [&] (Xml_node const &xml) -> Node & + { + if (Parent_provides_node::type_matches(xml)) + return *new (alloc) + Parent_provides_node(alloc, *verbose, parent_service_factory); + + if (Default_route_node::type_matches(xml)) + return *new (alloc) Default_route_node(alloc, default_route); + + if (Default_node::type_matches(xml)) + return *new (alloc) Default_node(default_caps); + + if (Start_node::type_matches(xml)) + return *new (alloc) Start_node(child_factory, xml); + + if (Affinity_space_node::type_matches(xml)) + return *new (alloc) Affinity_space_node(affinity_space); + + if (Report_node::type_matches(xml)) + return *new (alloc) Report_node(version, state_reporter); + + if (Resource_node::type_matches(xml)) + return *new (alloc) Resource_node(preservation, xml); + + if (Heartbeat_node::type_matches(xml)) + return *new (alloc) Heartbeat_node(heartbeat); + + if (Service_node::type_matches(xml)) + return *new (alloc) Service_node(service_factory, xml); + + error("unknown config element type <", xml.type(), ">"); + throw Unknown_element_type(); + }; + + auto update = [&] (Node &node, Xml_node const &xml) { node.update(xml); }; + + try { + update_list_model_from_xml(_model, xml, create, destroy, update); + } + catch (Unknown_element_type) { + error("unable to apply complete configuration"); } + catch (Start_model::Factory::Creation_failed) { + error("child creation failed"); } +} + + +void Config_model::apply_children_restart(Xml_node const &xml) +{ + class Unexpected : Exception { }; + auto destroy = [&] (Node &) { }; + auto create = [&] (Xml_node const &) -> Node & { throw Unexpected(); }; + auto update = [&] (Node &node, Xml_node const &xml) + { + node.apply_child_restart(xml); + }; + + try { + update_list_model_from_xml(_model, xml, create, destroy, update); + } + catch (...) { }; +} + + +void Config_model::trigger_start_children() +{ + _model.for_each([&] (Node &node) { + node.trigger_start_child(); }); +} diff --git a/repos/os/src/lib/sandbox/config_model.h b/repos/os/src/lib/sandbox/config_model.h new file mode 100644 index 0000000000..ea19a5c165 --- /dev/null +++ b/repos/os/src/lib/sandbox/config_model.h @@ -0,0 +1,283 @@ +/* + * \brief Internal model of the XML configuration + * \author Norman Feske + * \date 2021-04-01 + */ + +/* + * Copyright (C) 2021 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 _CONFIG_MODEL_H_ +#define _CONFIG_MODEL_H_ + +/* local includes */ +#include +#include + +namespace Sandbox { + + struct Parent_provides_model; + struct Start_model; + struct Service_model; + struct Config_model; +} + + +struct Sandbox::Parent_provides_model : Noncopyable +{ + struct Factory : Interface, Noncopyable + { + virtual Parent_service &create_parent_service(Service::Name const &) = 0; + }; + + Allocator &_alloc; + Verbose const &_verbose; + Factory &_factory; + + struct Node : Noncopyable, List_model::Element + { + Parent_service &service; + + Node(Factory &factory, Service::Name const &name) + : + service(factory.create_parent_service(name)) + { } + + ~Node() + { + /* + * The destruction of the 'Parent_service' is deferred to the + * handling of abandoned entries of the 'Registry'. + */ + service.abandon(); + } + + bool matches(Xml_node const &xml) const + { + return xml.attribute_value("name", Service::Name()) == service.name(); + }; + }; + + List_model _model { }; + + Parent_provides_model(Allocator &alloc, Verbose const &verbose, Factory &factory) + : + _alloc(alloc), _verbose(verbose), _factory(factory) + { } + + void update_from_xml(Xml_node const &xml) + { + bool first_log = true; + + auto create = [&] (Xml_node const &xml) -> Node & + { + Service::Name const name = xml.attribute_value("name", Service::Name()); + + if (_verbose.enabled()) { + if (first_log) + log("parent provides"); + + log(" service \"", name, "\""); + first_log = false; + } + + return *new (_alloc) Node(_factory, name); + }; + + auto destroy = [&] (Node &node) { Genode::destroy(_alloc, &node); }; + + auto update = [&] (Node &, Xml_node const &) { }; + + try { + update_list_model_from_xml(_model, xml, create, destroy, update); + } catch (...) { + error("unable to apply complete configuration"); + } + } +}; + + +struct Sandbox::Start_model : Noncopyable +{ + /* + * The 'Start_model' represents both '' nodes and '' nodes + * because both node types share the same name space. + */ + + typedef Child_policy::Name Name; + typedef Child::Version Version; + + static char const *start_type() { return "start"; } + static char const *alias_type() { return "alias"; } + + struct Factory : Interface + { + class Creation_failed : Exception { }; + + virtual bool ready_to_create_child(Name const &, Version const &) const = 0; + + /* + * \throw Creation_failed + */ + virtual Child &create_child(Xml_node const &start) = 0; + + virtual void update_child(Child &child, Xml_node const &start) = 0; + + /* + * \throw Creation_failed + */ + virtual Alias &create_alias(Name const &) = 0; + + virtual void destroy_alias(Alias &) = 0; + }; + + Name const _name; + Version const _version; + + Factory &_factory; + + bool _alias = false; + + struct { Child *_child_ptr = nullptr; }; + struct { Alias *_alias_ptr = nullptr; }; + + void _reset() + { + if (_child_ptr) _child_ptr->abandon(); + if (_alias_ptr) _factory.destroy_alias(*_alias_ptr); + + _child_ptr = nullptr; + _alias_ptr = nullptr; + } + + bool matches(Xml_node const &xml) const + { + return _name == xml.attribute_value("name", Name()) + && _version == xml.attribute_value("version", Version()); + } + + Start_model(Factory &factory, Xml_node const &xml) + : + _name(xml.attribute_value("name", Name())), + _version(xml.attribute_value("version", Version())), + _factory(factory) + { } + + ~Start_model() { _reset(); } + + void update_from_xml(Xml_node const &xml) + { + /* handle case where the node keeps the name but changes the type */ + + bool const orig_alias = _alias; + _alias = xml.has_type("alias"); + if (orig_alias != _alias) + _reset(); + + /* create alias or child depending of the node type */ + + if (_alias) { + + if (!_alias_ptr) + _alias_ptr = &_factory.create_alias(_name); + + } else { + + if (!_child_ptr && _factory.ready_to_create_child(_name, _version)) + _child_ptr = &_factory.create_child(xml); + } + + /* update */ + + if (_alias_ptr) + _alias_ptr->update(xml); + + if (_child_ptr) + _factory.update_child(*_child_ptr, xml); + } + + void apply_child_restart(Xml_node const &xml) + { + if (_child_ptr && _child_ptr->restart_scheduled()) { + + /* tear down */ + _child_ptr->abandon(); + _child_ptr = nullptr; + + /* respawn */ + update_from_xml(xml); + } + } + + void trigger_start_child() + { + if (_child_ptr) + _child_ptr->try_start(); + } +}; + + +struct Sandbox::Service_model : Interface, Noncopyable +{ + struct Factory : Interface, Noncopyable + { + virtual Service_model &create_service(Xml_node const &) = 0; + virtual void destroy_service(Service_model &) = 0; + }; + + virtual void update_from_xml(Xml_node const &) = 0; + + virtual bool matches(Xml_node const &) = 0; +}; + + +class Sandbox::Config_model : Noncopyable +{ + private: + + struct Node; + + List_model _model { }; + + struct Parent_provides_node; + struct Default_route_node; + struct Default_node; + struct Affinity_space_node; + struct Start_node; + struct Report_node; + struct Resource_node; + struct Heartbeat_node; + struct Service_node; + + public: + + typedef State_reporter::Version Version; + + void update_from_xml(Xml_node const &, + Allocator &, + Reconstructible &, + Version &, + Preservation &, + Constructible &, + Cap_quota &, + Prio_levels &, + Constructible &, + Start_model::Factory &, + Parent_provides_model::Factory &, + Service_model::Factory &, + State_reporter &, + Heartbeat &); + + void apply_children_restart(Xml_node const &); + + /* + * Call 'Child::try_start' for each child in start-node order + */ + void trigger_start_children(); +}; + +#endif /* _CONFIG_MODEL_H_ */ diff --git a/repos/os/src/lib/sandbox/heartbeat.h b/repos/os/src/lib/sandbox/heartbeat.h index 64ab915ca6..4f445508d0 100644 --- a/repos/os/src/lib/sandbox/heartbeat.h +++ b/repos/os/src/lib/sandbox/heartbeat.h @@ -64,23 +64,23 @@ class Sandbox::Heartbeat : Noncopyable _timer_handler(_env.ep(), *this, &Heartbeat::_handle_timer) { } - void apply_config(Xml_node config) + void disable() { - bool const enabled = config.has_sub_node("heartbeat"); + _timer.destruct(); + _rate_ms = 0; + } - _timer.conditional(enabled, _env); - - if (!enabled) { - _rate_ms = 0; - return; + void apply_config(Xml_node heartbeat) + { + if (!_timer.constructed()) { + _timer.construct(_env); + _timer->sigh(_timer_handler); } - unsigned const rate_ms = - config.sub_node("heartbeat").attribute_value("rate_ms", 1000UL); + unsigned const rate_ms = heartbeat.attribute_value("rate_ms", 1000UL); if (rate_ms != _rate_ms) { _rate_ms = rate_ms; - _timer->sigh(_timer_handler); _timer->trigger_periodic(_rate_ms*1000); } } diff --git a/repos/os/src/lib/sandbox/library.cc b/repos/os/src/lib/sandbox/library.cc index 5023c06ece..21e1245f08 100644 --- a/repos/os/src/lib/sandbox/library.cc +++ b/repos/os/src/lib/sandbox/library.cc @@ -5,7 +5,7 @@ */ /* - * Copyright (C) 2010-2020 Genode Labs GmbH + * Copyright (C) 2010-2021 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. @@ -20,12 +20,15 @@ #include #include #include +#include struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, ::Sandbox::Child::Default_route_accessor, ::Sandbox::Child::Default_caps_accessor, ::Sandbox::Child::Ram_limit_accessor, - ::Sandbox::Child::Cap_limit_accessor + ::Sandbox::Child::Cap_limit_accessor, + ::Sandbox::Start_model::Factory, + ::Sandbox::Parent_provides_model::Factory { using Routed_service = ::Sandbox::Routed_service; using Parent_service = ::Sandbox::Parent_service; @@ -41,6 +44,9 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, using Prio_levels = ::Sandbox::Prio_levels; using Ram_info = ::Sandbox::Ram_info; using Cap_info = ::Sandbox::Cap_info; + using Config_model = ::Sandbox::Config_model; + using Start_model = ::Sandbox::Start_model; + using Preservation = ::Sandbox::Preservation; Env &_env; Heap &_heap; @@ -50,77 +56,82 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, Registry &_local_services; Child_registry _children { }; - Reconstructible _verbose { }; + /* + * Global parameters obtained from config + */ + Reconstructible _verbose { }; + Config_model::Version _version { }; + Constructible _default_route { }; + Cap_quota _default_caps { 0 }; + Prio_levels _prio_levels { }; + Constructible _affinity_space { }; + Preservation _preservation { }; - Constructible _default_route { }; - - Cap_quota _default_caps { 0 }; - - unsigned _child_cnt = 0; - - static Ram_quota _preserved_ram_from_config(Xml_node config) + Affinity::Space _effective_affinity_space() const { - Number_of_bytes preserve { 40*sizeof(long)*1024 }; - - config.for_each_sub_node("resource", [&] (Xml_node node) { - if (node.attribute_value("name", String<16>()) == "RAM") - preserve = node.attribute_value("preserve", preserve); }); - - return Ram_quota { preserve }; + return _affinity_space.constructed() ? *_affinity_space + : Affinity::Space { 1, 1 }; } - Ram_quota _preserved_ram { 0 }; - Cap_quota _preserved_caps { 0 }; + State_reporter _state_reporter; + + Heartbeat _heartbeat { _env, _children, _state_reporter }; + + /* + * Internal representation of the XML configuration + */ + Config_model _config_model { }; + + /* + * Variables for tracking the side effects of updating the config model + */ + bool _server_appeared_or_disappeared = false; + bool _state_report_outdated = false; + + unsigned _child_cnt = 0; Ram_quota _avail_ram() const { Ram_quota avail_ram = _env.pd().avail_ram(); - if (_preserved_ram.value > avail_ram.value) { + if (_preservation.ram.value > avail_ram.value) { error("RAM preservation exceeds available memory"); return Ram_quota { 0 }; } /* deduce preserved quota from available quota */ - return Ram_quota { avail_ram.value - _preserved_ram.value }; - } - - static Cap_quota _preserved_caps_from_config(Xml_node config) - { - size_t preserve = 20; - - config.for_each_sub_node("resource", [&] (Xml_node node) { - if (node.attribute_value("name", String<16>()) == "CAP") - preserve = node.attribute_value("preserve", preserve); }); - - return Cap_quota { preserve }; + return Ram_quota { avail_ram.value - _preservation.ram.value }; } Cap_quota _avail_caps() const { Cap_quota avail_caps { _env.pd().avail_caps().value }; - if (_preserved_caps.value > avail_caps.value) { + if (_preservation.caps.value > avail_caps.value) { error("Capability preservation exceeds available capabilities"); return Cap_quota { 0 }; } /* deduce preserved quota from available quota */ - return Cap_quota { avail_caps.value - _preserved_caps.value }; + return Cap_quota { avail_caps.value - _preservation.caps.value }; } /** * Child::Ram_limit_accessor interface */ - Ram_quota resource_limit(Ram_quota const &) const override { return _avail_ram(); } + Ram_quota resource_limit(Ram_quota const &) const override + { + return _avail_ram(); + } /** * Child::Cap_limit_accessor interface */ Cap_quota resource_limit(Cap_quota const &) const override { return _avail_caps(); } - void _handle_resource_avail() { } - + /** + * State_reporter::Producer interface + */ void produce_state_report(Xml_generator &xml, Report_detail const &detail) const override { if (detail.init_ram()) @@ -133,6 +144,9 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, _children.report_state(xml, detail); } + /** + * State_reporter::Producer interface + */ Child::Sample_state_result sample_children_state() override { return _children.sample_state(); @@ -152,18 +166,57 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, */ Cap_quota default_caps() override { return _default_caps; } - State_reporter _state_reporter; - - Heartbeat _heartbeat { _env, _children, _state_reporter }; - void _update_aliases_from_config(Xml_node const &); void _update_parent_services_from_config(Xml_node const &); - void _abandon_obsolete_children(Xml_node const &); void _update_children_config(Xml_node const &); void _destroy_abandoned_parent_services(); + void _destroy_abandoned_children(); Server _server { _env, _heap, _child_services, _state_reporter }; + /** + * Sandbox::Start_model::Factory + */ + Child &create_child(Xml_node const &) override; + + /** + * Sandbox::Start_model::Factory + */ + void update_child(Child &, Xml_node const &) override; + + /** + * Sandbox::Start_model::Factory + */ + Alias &create_alias(Child_policy::Name const &name) override + { + Alias &alias = *new (_heap) Alias(name); + _children.insert_alias(&alias); + return alias; + } + + /** + * Sandbox::Start_model::Factory + */ + void destroy_alias(Alias &alias) override + { + _children.remove_alias(&alias); + destroy(_heap, &alias); + } + + /** + * Sandbox::Start_model::Factory + */ + bool ready_to_create_child(Start_model::Name const &, + Start_model::Version const &) const override; + + /** + * Sandbox::Parent_provides_model::Factory + */ + Parent_service &create_parent_service(Service::Name const &name) override + { + return *new (_heap) Parent_service(_parent_services, _env, name); + } + Library(Env &env, Heap &heap, Registry &local_services, State_handler &state_handler) : @@ -180,52 +233,6 @@ struct Genode::Sandbox::Library : ::Sandbox::State_reporter::Producer, }; -void Genode::Sandbox::Library::_update_parent_services_from_config(Xml_node const &config) -{ - Xml_node const node = config.has_sub_node("parent-provides") - ? config.sub_node("parent-provides") - : Xml_node(""); - - /* remove services that are no longer present in config */ - _parent_services.for_each([&] (Parent_service &service) { - - Service::Name const name = service.name(); - - bool obsolete = true; - node.for_each_sub_node("service", [&] (Xml_node service) { - if (name == service.attribute_value("name", Service::Name())) { - obsolete = false; }}); - - if (obsolete) - service.abandon(); - }); - - /* used to prepend the list of new parent services with title */ - bool first_log = true; - - /* register new services */ - node.for_each_sub_node("service", [&] (Xml_node service) { - - Service::Name const name = service.attribute_value("name", Service::Name()); - - bool registered = false; - _parent_services.for_each([&] (Parent_service const &service) { - if (service.name() == name) - registered = true; }); - - if (!registered) { - new (_heap) ::Sandbox::Parent_service(_parent_services, _env, name); - if (_verbose->enabled()) { - if (first_log) - log("parent provides"); - log(" service \"", name, "\""); - first_log = false; - } - } - }); -} - - void Genode::Sandbox::Library::_destroy_abandoned_parent_services() { _parent_services.for_each([&] (Parent_service &service) { @@ -234,109 +241,8 @@ void Genode::Sandbox::Library::_destroy_abandoned_parent_services() } -void Genode::Sandbox::Library::_update_aliases_from_config(Xml_node const &config) +void Genode::Sandbox::Library::_destroy_abandoned_children() { - /* remove all known aliases */ - while (_children.any_alias()) { - ::Sandbox::Alias *alias = _children.any_alias(); - _children.remove_alias(alias); - destroy(_heap, alias); - } - - /* create aliases */ - config.for_each_sub_node("alias", [&] (Xml_node alias_node) { - - try { - _children.insert_alias(new (_heap) Alias(alias_node)); } - catch (Alias::Name_is_missing) { - warning("missing 'name' attribute in '' entry"); } - catch (Alias::Child_is_missing) { - warning("missing 'child' attribute in '' entry"); } - }); -} - - -void Genode::Sandbox::Library::_abandon_obsolete_children(Xml_node const &config) -{ - _children.for_each_child([&] (Child &child) { - - bool obsolete = true; - config.for_each_sub_node("start", [&] (Xml_node node) { - if (child.has_name (node.attribute_value("name", Child_policy::Name())) - && child.has_version(node.attribute_value("version", Child::Version()))) - obsolete = false; }); - - if (obsolete) - child.abandon(); - }); -} - - -void Genode::Sandbox::Library::_update_children_config(Xml_node const &config) -{ - for (;;) { - - /* - * Children are abandoned if any of their client sessions can no longer - * be routed or result in a different route. As each child may be a - * service, an avalanche effect may occur. It stops if no update causes - * a potential side effect in one iteration over all chilren. - */ - bool side_effects = false; - - config.for_each_sub_node("start", [&] (Xml_node node) { - - Child_policy::Name const start_node_name = - node.attribute_value("name", Child_policy::Name()); - - _children.for_each_child([&] (Child &child) { - if (!child.abandoned() && child.name() == start_node_name) { - switch (child.apply_config(node)) { - case Child::NO_SIDE_EFFECTS: break; - case Child::MAY_HAVE_SIDE_EFFECTS: side_effects = true; break; - }; - } - }); - }); - - if (!side_effects) - break; - } -} - - -void Genode::Sandbox::Library::apply_config(Xml_node const &config) -{ - bool update_state_report = false; - - _preserved_ram = _preserved_ram_from_config(config); - _preserved_caps = _preserved_caps_from_config(config); - - _verbose.construct(config); - _state_reporter.apply_config(config); - _heartbeat.apply_config(config); - - /* determine default route for resolving service requests */ - try { - _default_route.construct(_heap, config.sub_node("default-route")); } - catch (...) { } - - _default_caps = Cap_quota { 0 }; - try { - _default_caps = Cap_quota { config.sub_node("default") - .attribute_value("caps", 0UL) }; } - catch (...) { } - - Prio_levels const prio_levels = ::Sandbox::prio_levels_from_xml(config); - Affinity::Space const affinity_space = ::Sandbox::affinity_space_from_xml(config); - bool const space_defined = config.has_sub_node("affinity-space"); - - _update_aliases_from_config(config); - _update_parent_services_from_config(config); - _abandon_obsolete_children(config); - _update_children_config(config); - - /* kill abandoned children */ _children.for_each_child([&] (Child &child) { if (!child.abandoned()) @@ -345,7 +251,7 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config) /* make the child's services unavailable */ child.destroy_services(); child.close_all_sessions(); - update_state_report = true; + _state_report_outdated = true; /* destroy child once all environment sessions are gone */ if (child.env_sessions_closed()) { @@ -353,123 +259,174 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config) destroy(_heap, &child); } }); +} - _destroy_abandoned_parent_services(); - /* initial RAM and caps limit before starting new children */ - Ram_quota const avail_ram = _avail_ram(); - Cap_quota const avail_caps = _avail_caps(); +bool Genode::Sandbox::Library::ready_to_create_child(Start_model::Name const &name, + Start_model::Version const &version) const +{ + bool exists = false; - /* variable used to track the RAM and caps taken by new started children */ - Ram_quota used_ram { 0 }; - Cap_quota used_caps { 0 }; + unsigned num_abandoned = 0; + + _children.for_each_child([&] (Child const &child) { + if (child.name() == name && child.has_version(version)) { + if (child.abandoned()) + num_abandoned++; + else + exists = true; + } + }); + + /* defer child creation if corresponding child already exists */ + if (exists) + return false; + + /* prevent queuing up abandoned children with the same name */ + if (num_abandoned > 1) + return false; + + return true; +} + + +::Sandbox::Child &Genode::Sandbox::Library::create_child(Xml_node const &start_node) +{ + if (!_affinity_space.constructed() && start_node.has_sub_node("affinity")) + warning("affinity-space configuration missing, " + "but affinity defined for child ", + start_node.attribute_value("name", Child_policy::Name())); - /* create new children */ try { - config.for_each_sub_node("start", [&] (Xml_node start_node) { + Child &child = *new (_heap) + Child(_env, _heap, *_verbose, + Child::Id { ++_child_cnt }, _state_reporter, + start_node, *this, *this, _children, + *this, *this, _prio_levels, _effective_affinity_space(), + _parent_services, _child_services, _local_services); + _children.insert(&child); - bool exists = false; + if (start_node.has_sub_node("provides")) + _server_appeared_or_disappeared = true; - unsigned num_abandoned = 0; + _state_report_outdated = true; - typedef Child_policy::Name Name; - Name const child_name = start_node.attribute_value("name", Name()); - - _children.for_each_child([&] (Child const &child) { - if (child.name() == child_name) { - if (child.abandoned()) - num_abandoned++; - else - exists = true; - } - }); - - /* skip start node if corresponding child already exists */ - if (exists) - return; - - /* prevent queuing up abandoned children with the same name */ - if (num_abandoned > 1) - return; - - if (used_ram.value > avail_ram.value) { - error("RAM exhausted while starting child: ", child_name); - return; - } - - if (used_caps.value > avail_caps.value) { - error("capabilities exhausted while starting child: ", child_name); - return; - } - - if (!space_defined && start_node.has_sub_node("affinity")) { - warning("affinity-space configuration missing, " - "but affinity defined for child: ", child_name); - } - - try { - Child &child = *new (_heap) - Child(_env, _heap, *_verbose, - Child::Id { ++_child_cnt }, _state_reporter, - start_node, *this, *this, _children, - Ram_quota { avail_ram.value - used_ram.value }, - Cap_quota { avail_caps.value - used_caps.value }, - *this, *this, prio_levels, affinity_space, - _parent_services, _child_services, _local_services); - _children.insert(&child); - - update_state_report = true; - - /* account for the start XML node buffered in the child */ - size_t const metadata_overhead = start_node.size() + sizeof(Child); - - /* track used memory and RAM limit */ - used_ram = Ram_quota { used_ram.value - + child.ram_quota().value - + metadata_overhead }; - - used_caps = Cap_quota { used_caps.value - + child.cap_quota().value }; - } - catch (Rom_connection::Rom_connection_failed) { - /* - * The binary does not exist. An error message is printed - * by the Rom_connection constructor. - */ - } - catch (Out_of_ram) { - warning("memory exhausted during child creation"); } - catch (Out_of_caps) { - warning("local capabilities exhausted during child creation"); } - catch (Child::Missing_name_attribute) { - warning("skipped startup of nameless child"); } - catch (Region_map::Region_conflict) { - warning("failed to attach dataspace to local address space " - "during child construction"); } - catch (Region_map::Invalid_dataspace) { - warning("attempt to attach invalid dataspace to local address space " - "during child construction"); } - catch (Service_denied) { - warning("failed to create session during child construction"); } - }); + return child; } - catch (Xml_node::Nonexistent_sub_node) { error("no children to start"); } - catch (Xml_node::Invalid_syntax) { error("config has invalid syntax"); } - catch (Child_registry::Alias_name_is_not_unique) { } + catch (Rom_connection::Rom_connection_failed) { + /* + * The binary does not exist. An error message is printed + * by the Rom_connection constructor. + */ + } + catch (Out_of_ram) { + warning("memory exhausted during child creation"); } + catch (Out_of_caps) { + warning("local capabilities exhausted during child creation"); } + catch (Child::Missing_name_attribute) { + warning("skipped startup of nameless child"); } + catch (Region_map::Region_conflict) { + warning("failed to attach dataspace to local address space " + "during child construction"); } + catch (Region_map::Invalid_dataspace) { + warning("attempt to attach invalid dataspace to local address space " + "during child construction"); } + catch (Service_denied) { + warning("failed to create session during child construction"); } + + throw ::Sandbox::Start_model::Factory::Creation_failed(); +} + + +void Genode::Sandbox::Library::update_child(Child &child, Xml_node const &start) +{ + if (child.abandoned()) + return; + + switch (child.apply_config(start)) { + + case Child::NO_SIDE_EFFECTS: break; + + case Child::PROVIDED_SERVICES_CHANGED: + _server_appeared_or_disappeared = true; + _state_report_outdated = true; + break; + }; +} + + +void Genode::Sandbox::Library::apply_config(Xml_node const &config) +{ + _server_appeared_or_disappeared = false; + _state_report_outdated = false; + + _config_model.update_from_xml(config, + _heap, + _verbose, + _version, + _preservation, + _default_route, + _default_caps, + _prio_levels, + _affinity_space, + *this, *this, _server, + _state_reporter, + _heartbeat); /* - * Initiate RAM sessions of all new children + * After importing the new configuration, servers may have disappeared + * (STATE_ABANDONED) or become new available. + * + * Re-evaluate the dependencies of the existing children. + * + * - Stuck children (STATE_STUCK) may become alive. + * - Children with broken dependencies may have become stuck. + * - Children with changed dependencies need a restart. + * + * Children are restarted if any of their client sessions can no longer be + * routed or result in a different route. As each child may be a service, + * an avalanche effect may occur. It stops if no child gets scheduled to be + * restarted in one iteration over all children. */ - _children.for_each_child([&] (Child &child) { - if (!child.abandoned()) - child.initiate_env_pd_session(); }); + while (true) { - /* - * Initiate remaining environment sessions of all new children - */ - _children.for_each_child([&] (Child &child) { - if (!child.abandoned()) - child.initiate_env_sessions(); }); + bool any_restart_scheduled = false; + + _children.for_each_child([&] (Child &child) { + + if (child.abandoned()) + return; + + if (child.restart_scheduled()) { + any_restart_scheduled = true; + return; + } + + if (_server_appeared_or_disappeared || child.uncertain_dependencies()) + child.evaluate_dependencies(); + + if (child.restart_scheduled()) + any_restart_scheduled = true; + }); + + /* + * Release resources captured by abandoned children before starting + * new children. The children must be started in the order of their + * start nodes for the assignment of slack RAM. + */ + _destroy_abandoned_parent_services(); + _destroy_abandoned_children(); + + _config_model.trigger_start_children(); + + if (any_restart_scheduled) + _config_model.apply_children_restart(config); + + if (!any_restart_scheduled) + break; + } + + _server.apply_updated_policy(); /* * (Re-)distribute RAM and capability quota among the children, given their @@ -480,9 +437,7 @@ void Genode::Sandbox::Library::apply_config(Xml_node const &config) _children.for_each_child([&] (Child &child) { child.apply_downgrade(); }); _children.for_each_child([&] (Child &child) { child.apply_upgrade(); }); - _server.apply_config(config); - - if (update_state_report) + if (_state_report_outdated) _state_reporter.trigger_immediate_report_update(); } diff --git a/repos/os/src/lib/sandbox/server.cc b/repos/os/src/lib/sandbox/server.cc index 39ea53a5f1..f2fbc3c625 100644 --- a/repos/os/src/lib/sandbox/server.cc +++ b/repos/os/src/lib/sandbox/server.cc @@ -21,19 +21,21 @@ /****************************** ** Sandbox::Server::Service ** - **********...*****************/ + ******************************/ -struct Sandbox::Server::Service +struct Sandbox::Server::Service : Service_model { + typedef Genode::Service::Name Name; + + Name const _name; + Registry::Element _registry_element; - Buffered_xml _service_node; - - typedef Genode::Service::Name Name; + Allocator &_alloc; Registry &_child_services; - Name const _name { _service_node.xml().attribute_value("name", Name()) }; + Constructible _service_node { }; /** * Constructor @@ -45,11 +47,28 @@ struct Sandbox::Server::Service Xml_node service_node, Registry &child_services) : + _name(service_node.attribute_value("name", Name())), _registry_element(services, *this), - _service_node(alloc, service_node), + _alloc(alloc), _child_services(child_services) { } + /** + * Service_model interface + */ + void update_from_xml(Xml_node const &service_node) override + { + _service_node.construct(_alloc, service_node); + } + + /** + * Service_model interface + */ + bool matches(Xml_node const &service_node) override + { + return _name == service_node.attribute_value("name", Name()); + } + /** * Determine route to child service for a given label according * to the node policy @@ -65,8 +84,11 @@ struct Sandbox::Server::Service Sandbox::Server::Route Sandbox::Server::Service::resolve_session_request(Session_label const &label) { + if (!_service_node.constructed()) + throw Service_denied(); + try { - Session_policy policy(label, _service_node.xml()); + Session_policy policy(label, _service_node->xml()); if (!policy.has_sub_node("child")) throw Service_denied(); @@ -365,13 +387,20 @@ void Sandbox::Server::_handle_session_requests() } -void Sandbox::Server::apply_config(Xml_node config) +Sandbox::Service_model &Sandbox::Server::create_service(Xml_node const &node) { - _services.for_each([&] (Service &service) { destroy(_alloc, &service); }); + return *new (_alloc) Service(_services, _alloc, node, _child_services); +} - config.for_each_sub_node("service", [&] (Xml_node node) { - new (_alloc) Service(_services, _alloc, node, _child_services); }); +void Sandbox::Server::destroy_service(Service_model &service) +{ + destroy(_alloc, &service); +} + + +void Sandbox::Server::apply_updated_policy() +{ /* * Construct mechanics for responding to our parent's session requests * on demand. diff --git a/repos/os/src/lib/sandbox/server.h b/repos/os/src/lib/sandbox/server.h index 44777c7cee..ce231ce28f 100644 --- a/repos/os/src/lib/sandbox/server.h +++ b/repos/os/src/lib/sandbox/server.h @@ -19,15 +19,17 @@ #include /* local includes */ -#include "types.h" -#include "service.h" -#include "state_reporter.h" +#include +#include +#include +#include namespace Sandbox { class Server; } class Sandbox::Server : Session_state::Ready_callback, - Session_state::Closed_callback + Session_state::Closed_callback, + public Service_model::Factory { private: @@ -113,7 +115,17 @@ class Sandbox::Server : Session_state::Ready_callback, _report_update_trigger(report_update_trigger) { } - void apply_config(Xml_node); + void apply_updated_policy(); + + /** + * Service_model::Factory + */ + Service_model &create_service(Xml_node const &) override; + + /** + * Service_model::Factory + */ + void destroy_service(Service_model &) override; }; #endif /* _LIB__SANDBOX__SERVER_H_ */ diff --git a/repos/os/src/lib/sandbox/state_reporter.h b/repos/os/src/lib/sandbox/state_reporter.h index abb65e0fd6..bc0d6d5ee1 100644 --- a/repos/os/src/lib/sandbox/state_reporter.h +++ b/repos/os/src/lib/sandbox/state_reporter.h @@ -29,6 +29,8 @@ class Sandbox::State_reporter : public Report_update_trigger { public: + typedef String<64> Version; + struct Producer : Interface { virtual void produce_state_report(Xml_generator &xml, @@ -53,7 +55,6 @@ class Sandbox::State_reporter : public Report_update_trigger uint64_t _report_period_ms = 0; /* version string from config, to be reflected in the report */ - typedef String<64> Version; Version _version { }; Constructible _timer { }; @@ -111,22 +112,18 @@ class Sandbox::State_reporter : public Report_update_trigger _producer.produce_state_report(xml, *_report_detail); } - void apply_config(Xml_node config) + void apply_config(Version const &version, Xml_node const &report) { - try { - Xml_node report = config.sub_node("report"); - + if (report.type() == "report") { _report_detail.construct(report); _report_delay_ms = report.attribute_value("delay_ms", 100UL); - } - catch (Xml_node::Nonexistent_sub_node) { + } else { _report_detail.construct(); _report_delay_ms = 0; } bool trigger_update = false; - Version const version = config.attribute_value("version", Version()); if (version != _version) { _version = version; trigger_update = true; diff --git a/repos/os/src/lib/sandbox/types.h b/repos/os/src/lib/sandbox/types.h index 7e54780b6f..5d8ee16c56 100644 --- a/repos/os/src/lib/sandbox/types.h +++ b/repos/os/src/lib/sandbox/types.h @@ -14,9 +14,9 @@ #ifndef _LIB__SANDBOX__TYPES_H_ #define _LIB__SANDBOX__TYPES_H_ -#include #include -#include +#include +#include namespace Sandbox { @@ -72,6 +72,22 @@ namespace Sandbox { .avail = pd.avail_caps() }; } + struct Preservation : private Noncopyable + { + static Ram_quota default_ram() { return { 40*sizeof(long)*1024 }; } + static Cap_quota default_caps() { return { 20 }; } + + Ram_quota ram { }; + Cap_quota caps { }; + + void reset() + { + ram = default_ram(); + caps = default_caps(); + } + + Preservation() { reset(); } + }; } #endif /* _LIB__SANDBOX__TYPES_H_ */ diff --git a/repos/os/src/lib/sandbox/update_list_model.h b/repos/os/src/lib/sandbox/update_list_model.h new file mode 100644 index 0000000000..2703986826 --- /dev/null +++ b/repos/os/src/lib/sandbox/update_list_model.h @@ -0,0 +1,69 @@ +/* + * \brief Convenience wrapper around 'List_model' + * \author Norman Feske + * \date 2021-04-01 + */ + +/* + * Copyright (C) 2021 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 _UPDATE_LIST_MODEL_H_ +#define _UPDATE_LIST_MODEL_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include + +namespace Sandbox { + + template + static inline void update_list_model_from_xml(List_model &, + Xml_node const &, + CREATE_FN const &, + DESTROY_FN const &, + UPDATE_FN const &); +} + + +template +void Sandbox::update_list_model_from_xml(List_model &model, + Xml_node const &xml, + CREATE_FN const &create, + DESTROY_FN const &destroy, + UPDATE_FN const &update) +{ + struct Model_update_policy : List_model::Update_policy + { + CREATE_FN const &_create_fn; + DESTROY_FN const &_destroy_fn; + UPDATE_FN const &_update_fn; + + Model_update_policy(CREATE_FN const &create_fn, + DESTROY_FN const &destroy_fn, + UPDATE_FN const &update_fn) + : + _create_fn(create_fn), _destroy_fn(destroy_fn), _update_fn(update_fn) + { } + + void destroy_element(NODE &node) { _destroy_fn(node); } + + NODE &create_element(Xml_node xml) { return _create_fn(xml); } + + void update_element(NODE &node, Xml_node xml) { _update_fn(node, xml); } + + static bool element_matches_xml_node(NODE const &node, Xml_node xml) + { + return node.matches(xml); + } + } policy(create, destroy, update); + + model.update_from_xml(policy, xml); +} + +#endif /* _UPDATE_LIST_MODEL_H_ */ diff --git a/repos/os/src/lib/sandbox/utils.h b/repos/os/src/lib/sandbox/utils.h index a21fcca8ea..853cb6da0f 100644 --- a/repos/os/src/lib/sandbox/utils.h +++ b/repos/os/src/lib/sandbox/utils.h @@ -124,7 +124,8 @@ namespace Sandbox { /* count number of services with the specified name */ unsigned cnt = 0; services.for_each([&] (T const &service) { - cnt += (service.name() == name); }); + if (!service.abandoned()) + cnt += (service.name() == name); }); return cnt > 1; } @@ -164,7 +165,7 @@ namespace Sandbox { /** * Read priority-levels declaration from config */ - inline Prio_levels prio_levels_from_xml(Xml_node config) + inline Prio_levels prio_levels_from_xml(Xml_node const &config) { long const prio_levels = config.attribute_value("prio_levels", 0UL); @@ -233,24 +234,6 @@ namespace Sandbox { } catch (...) { return Location(0, 0, space.width(), space.height()); } } - - - /** - * Read affinity-space parameters from config - * - * If no affinity space is declared, construct a space with a single element, - * width and height being 1. If only one of both dimensions is specified, the - * other dimension is set to 1. - */ - inline Affinity::Space affinity_space_from_xml(Xml_node config) - { - try { - Xml_node node = config.sub_node("affinity-space"); - return Affinity::Space(node.attribute_value("width", 1), - node.attribute_value("height", 1)); - } catch (...) { - return Affinity::Space(1, 1); } - } } #endif /* _LIB__SANDBOX__UTILS_H_ */ diff --git a/repos/os/src/lib/sandbox/verbose.h b/repos/os/src/lib/sandbox/verbose.h index db0c7adb2e..ab3e6050a6 100644 --- a/repos/os/src/lib/sandbox/verbose.h +++ b/repos/os/src/lib/sandbox/verbose.h @@ -34,7 +34,7 @@ class Sandbox::Verbose : Genode::Noncopyable Verbose() { } - Verbose(Genode::Xml_node config) + Verbose(Xml_node const &config) : _enabled(config.attribute_value("verbose", false)) { } bool enabled() const { return _enabled; }