diff --git a/repos/os/src/lib/sandbox/child.cc b/repos/os/src/lib/sandbox/child.cc index 6fc765dd5f..a123c2c590 100644 --- a/repos/os/src/lib/sandbox/child.cc +++ b/repos/os/src/lib/sandbox/child.cc @@ -68,8 +68,9 @@ Sandbox::Child::apply_config(Xml_node start_node) */ 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; }); }); + if (route.differs_from(orig)) { + _construct_route_model_from_start_node(start_node); + _uncertain_dependencies = true; } }); }); /* * Determine how the inline config is affected. @@ -505,9 +506,10 @@ Sandbox::Child::resolve_session_request(Service::Name const &service_name, Session_label const &label, Session::Diag const diag) { + bool const rom_service = (service_name == Rom_session::service_name()); + /* check for "config" ROM request */ - if (service_name == Rom_session::service_name() && - label.last_element() == "config") { + if (rom_service && label.last_element() == "config") { if (_config_rom_service.constructed() && !_config_rom_service->abandoned()) @@ -529,113 +531,89 @@ Sandbox::Child::resolve_session_request(Service::Name const &service_name, * we resolve the session request with the binary name as label. * Otherwise the regular routing is applied. */ - if (service_name == Rom_session::service_name() && - label == _unique_name && _unique_name != _binary_name) + if (rom_service && label == _unique_name && _unique_name != _binary_name) return resolve_session_request(service_name, _binary_name, diag); /* supply binary as dynamic linker if '' */ - if (!_use_ld && service_name == Rom_session::service_name() && label == "ld.lib.so") + if (rom_service && !_use_ld && label == "ld.lib.so") return resolve_session_request(service_name, _binary_name, diag); /* check for "session_requests" ROM request */ - if (service_name == Rom_session::service_name() - && label.last_element() == Session_requester::rom_name()) + if (rom_service && label.last_element() == Session_requester::rom_name()) return Route { _session_requester.service(), Session::Label(), diag }; - try { - Xml_node route_node = _default_route_accessor.default_route(); - try { - route_node = _start_node->xml().sub_node("route"); } - catch (...) { } - Xml_node service_node = route_node.sub_node(); + auto resolve_at_target = [&] (Xml_node const &target) -> Route + { + /* + * Determine session label to be provided to the server + * + * By default, the client's identity (accompanied with the a + * client-provided label) is presented as session label to the + * server. However, the target node can explicitly override the + * client's identity by a custom label via the 'label' + * attribute. + */ + typedef String Label; + Label const target_label = + target.attribute_value("label", Label(label.string())); - for (; ; service_node = service_node.next()) { + Session::Diag const + target_diag { target.attribute_value("diag", diag.enabled) }; - bool service_wildcard = service_node.has_type("any-service"); + auto no_filter = [] (Service &) -> bool { return false; }; - if (!service_node_matches(service_node, label, name(), service_name)) - continue; + if (target.has_type("parent")) { - Xml_node target = service_node.sub_node(); - for (; ; target = target.next()) { - - /* - * Determine session label to be provided to the server - * - * By default, the client's identity (accompanied with the a - * client-provided label) is presented as session label to the - * server. However, the target node can explicitly override the - * client's identity by a custom label via the 'label' - * attribute. - */ - typedef String Label; - Label const target_label = - target.attribute_value("label", Label(label.string())); - - Session::Diag const - target_diag { target.attribute_value("diag", diag.enabled) }; - - auto no_filter = [] (Service &) -> bool { return false; }; - - if (target.has_type("parent")) { - - try { - return Route { find_service(_parent_services, service_name, no_filter), - target_label, target_diag }; - } catch (Service_denied) { } - } - - if (target.has_type("local")) { - - try { - return Route { find_service(_local_services, service_name, no_filter), - target_label, target_diag }; - } catch (Service_denied) { } - } - - if (target.has_type("child")) { - - typedef Name_registry::Name Name; - Name server_name = target.attribute_value("name", Name()); - server_name = _name_registry.deref_alias(server_name); - - auto filter_server_name = [&] (Routed_service &s) -> bool { - return s.child_name() != server_name; }; - - try { - return Route { find_service(_child_services, service_name, filter_server_name), - target_label, target_diag }; - - } catch (Service_denied) { } - } - - if (target.has_type("any-child")) { - - if (is_ambiguous(_child_services, service_name)) { - error(name(), ": ambiguous routes to " - "service \"", service_name, "\""); - throw Service_denied(); - } - try { - return Route { find_service(_child_services, service_name, no_filter), - target_label, target_diag }; - - } catch (Service_denied) { } - } - - if (!service_wildcard) { - warning(name(), ": lookup for service \"", service_name, "\" failed"); - throw Service_denied(); - } - - if (target.last()) - break; - } + try { + return Route { find_service(_parent_services, service_name, no_filter), + target_label, target_diag }; + } catch (Service_denied) { } } - } catch (Xml_node::Nonexistent_sub_node) { } - warning(name(), ": no route to service \"", service_name, "\" (label=\"", label, "\")"); - throw Service_denied(); + if (target.has_type("local")) { + + try { + return Route { find_service(_local_services, service_name, no_filter), + target_label, target_diag }; + } catch (Service_denied) { } + } + + if (target.has_type("child")) { + + typedef Name_registry::Name Name; + Name server_name = target.attribute_value("name", Name()); + server_name = _name_registry.deref_alias(server_name); + + auto filter_server_name = [&] (Routed_service &s) -> bool { + return s.child_name() != server_name; }; + + try { + return Route { find_service(_child_services, service_name, + filter_server_name), target_label, target_diag }; + + } catch (Service_denied) { } + } + + if (target.has_type("any-child")) { + + if (is_ambiguous(_child_services, service_name)) { + error(name(), ": ambiguous routes to " + "service \"", service_name, "\""); + throw Service_denied(); + } + try { + return Route { find_service(_child_services, service_name, + no_filter), target_label, target_diag }; + + } catch (Service_denied) { } + } + + throw Service_denied(); + }; + + Route_model::Query const query(name(), service_name, label); + + return _route_model->resolve(query, resolve_at_target); } @@ -796,6 +774,8 @@ Sandbox::Child::Child(Env &env, log(" priority: ", _resources.priority); } + _construct_route_model_from_start_node(start_node); + /* * Determine services provided by the child */ diff --git a/repos/os/src/lib/sandbox/child.h b/repos/os/src/lib/sandbox/child.h index 11fca7ca63..2c27e4ea8a 100644 --- a/repos/os/src/lib/sandbox/child.h +++ b/repos/os/src/lib/sandbox/child.h @@ -29,6 +29,7 @@ #include #include #include +#include namespace Sandbox { class Child; } @@ -113,6 +114,21 @@ class Sandbox::Child : Child_policy, Routed_service::Wakeup Reconstructible _start_node; + Constructible _route_model { }; + + void _construct_route_model_from_start_node(Xml_node const &start) + { + _route_model.destruct(); + + start.with_sub_node("route", [&] (Xml_node const &route) { + _route_model.construct(_alloc, route); }); + + if (_route_model.constructed()) + return; + + _route_model.construct(_alloc, _default_route_accessor.default_route()); + } + /* * Version attribute of the start node, used to force child restarts. */ diff --git a/repos/os/src/lib/sandbox/route_model.h b/repos/os/src/lib/sandbox/route_model.h new file mode 100644 index 0000000000..619a4a9e39 --- /dev/null +++ b/repos/os/src/lib/sandbox/route_model.h @@ -0,0 +1,280 @@ +/* + * \brief Internal model of routing rules + * \author Norman Feske + * \date 2021-04-05 + */ + +/* + * 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 _ROUTE_MODEL_H_ +#define _ROUTE_MODEL_H_ + +/* local includes */ +#include + +namespace Sandbox { + + struct Checksum; + class Route_model; +} + + +struct Sandbox::Checksum +{ + unsigned long value = 0; + + bool valid; + + /** + * Constructor + */ + Checksum(char const *s) : valid(s != nullptr) + { + if (!s) + return; + + while (uint8_t const byte = *s++) { + + /* rotate value */ + unsigned long const sign_bit = ((long)value < 0); + value = (value << 1) | sign_bit; + + /* xor byte to lowest 8 bit */ + value = value ^ (unsigned long)byte; + } + } + + template + Checksum(String const &s) : Checksum(s.string()) { } + + bool operator != (Checksum const &other) const + { + return (other.value != value) || !valid; + } +}; + + +class Sandbox::Route_model : Noncopyable +{ + public: + + struct Query : Noncopyable + { + Child_policy::Name const &child; + Service::Name const &service; + Session_label const &label; + + Checksum const service_checksum { service }; + Checksum const label_checksum { skip_label_prefix(child.string(), + label.string()) }; + + Query(Child_policy::Name const &child, + Service::Name const &service, + Session_label const &label) + : + child(child), service(service), label(label) + { } + }; + + class Rule : Noncopyable, List::Element + { + private: + + friend class List; + friend class Route_model; + friend void destroy(Allocator &, Rule *); + + Allocator &_alloc; + + Xml_node const _node; /* points to 'Route_model::_route_node' */ + + struct Selector + { + typedef String Label; + + enum class Type + { + NO_LABEL, SPECIFIC_LABEL, + + /* + * Presence of 'label_last', 'label_prefix', + * 'label_suffix', 'unscoped_label', or even + * a combination of attributes. + */ + COMPLICATED + + } type = Type::NO_LABEL; + + Checksum label_checksum { "" }; + + Selector(Xml_node const &node) + { + bool const complicated = + node.has_attribute("label_prefix") || + node.has_attribute("label_suffix") || + node.has_attribute("label_last") || + node.has_attribute("unscoped_label"); + + if (complicated) { + type = Type::COMPLICATED; + return; + } + + Label const label = node.attribute_value("label", Label()); + if (label.valid()) { + type = Type::SPECIFIC_LABEL; + label_checksum = Checksum(label); + } + } + }; + + Selector const _selector; + Checksum const _service_checksum; + bool const _specific_service { _node.has_type("service") }; + + struct Target : Noncopyable, private List::Element + { + friend class List; + friend class Rule; + + Xml_node const node; /* points to 'Route_model::_route_node' */ + + Target(Xml_node const &node) : node(node) { } + }; + + List _targets { }; + + /** + * Constructor is private to 'Route_model' + */ + Rule(Allocator &alloc, Xml_node const &node) + : + _alloc(alloc), _node(node), _selector(node), + _service_checksum(node.attribute_value("name", Service::Name())) + { + Target const *at_ptr = nullptr; + node.for_each_sub_node([&] (Xml_node sub_node) { + Target &target = *new (_alloc) Target(sub_node); + _targets.insert(&target, at_ptr); + at_ptr = ⌖ + }); + } + + ~Rule() + { + while (Target *target_ptr = _targets.first()) { + _targets.remove(target_ptr); + destroy(_alloc, target_ptr); + } + } + + /** + * Quick check for early detection of definite mismatches + * + * \return true if query definitely mismatches the rule, + * false if the undecided + */ + bool _mismatches(Query const &query) const + { + if (_specific_service + && query.service_checksum != _service_checksum) + return true; + + if (_selector.type == Selector::Type::SPECIFIC_LABEL + && query.label_checksum != _selector.label_checksum) + return true; + + return false; + } + + public: + + bool matches(Query const &query) const + { + /* handle common case */ + if (_mismatches(query)) + return false; + + return service_node_matches(_node, + query.label, + query.child, + query.service); + } + + template + Child_policy::Route resolve(FN const &fn) const + { + for (Target const *t = _targets.first(); t; t = t->next()) { + try { return fn(t->node); } + catch (Service_denied) { /* try next target */ } + } + + /* query is not accepted by any of the targets */ + throw Service_denied(); + } + }; + + private: + + Allocator &_alloc; + + Buffered_xml const _route_node; + + List _rules { }; + + public: + + Route_model(Allocator &alloc, Xml_node const &route) + : + _alloc(alloc), _route_node(_alloc, route) + { + Rule const *at_ptr = nullptr; + _route_node.xml().for_each_sub_node([&] (Xml_node const &node) { + Rule &rule = *new (_alloc) Rule(_alloc, node); + _rules.insert(&rule, at_ptr); /* append */ + at_ptr = &rule; + }); + } + + ~Route_model() + { + while (Rule *rule_ptr = _rules.first()) { + _rules.remove(rule_ptr); + destroy(_alloc, rule_ptr); + } + } + + template + Child_policy::Route resolve(Query const &query, FN const &fn) const + { + for (Rule const *r = _rules.first(); r; r = r->next()) + if (r->matches(query)) { + try { + return r->resolve(fn); + } + catch (Service_denied) { + if (r->_specific_service) + throw; + + /* + * If none of the targets of a wildcard rule was + * satisfied with the query, continue with the next + * rule. + */ + } + } + + warning(query.child, ": no route to " + "service \"", query.service, "\" " + "(label=\"", query.label, "\")"); + + throw Service_denied(); + } +}; + +#endif /* _ROUTE_MODEL_H_ */