From 1fde4d638c2181d3df2588b5a60bd917539912d5 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Wed, 8 Mar 2017 10:21:49 +0100 Subject: [PATCH] init: service forwarding This patch equips init with the ability to act as a server that forwards session requests to its children. Session requests can be routed depending of the requested service type and the session label originating from init's parent. The feature is configured by one or multiple nodes hosted in init's node. The routing policy is selected by via the regular server-side policy-selection mechanism, for example: ... ... Each policy node must have a sub node, which denotes name of the server with the 'name' attribute. The optional 'label' attribute defines the session label presented to the server, analogous to how the rewriting of session labels works in session routes. If not specified, the client-provided label is presented to the server as is. Fixes #2247 --- repos/os/run/init.run | 114 +++++++++- repos/os/src/app/dummy/main.cc | 59 ++++- repos/os/src/init/buffered_xml.h | 3 + repos/os/src/init/main.cc | 5 + repos/os/src/init/server.cc | 341 +++++++++++++++++++++++++++++ repos/os/src/init/server.h | 112 ++++++++++ repos/os/src/init/service.h | 18 +- repos/os/src/init/state_reporter.h | 4 + repos/os/src/init/target.mk | 2 +- repos/os/src/init/types.h | 1 + 10 files changed, 648 insertions(+), 11 deletions(-) create mode 100644 repos/os/src/init/server.cc create mode 100644 repos/os/src/init/server.h diff --git a/repos/os/run/init.run b/repos/os/run/init.run index 56e58db4d..4f1c77038 100644 --- a/repos/os/run/init.run +++ b/repos/os/run/init.run @@ -743,6 +743,118 @@ append config { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -786,5 +898,5 @@ build_boot_image $boot_modules append qemu_args " -nographic " -run_genode_until {.*child "test-init" exited with exit value 0.*} 60 +run_genode_until {.*child "test-init" exited with exit value 0.*} 120 diff --git a/repos/os/src/app/dummy/main.cc b/repos/os/src/app/dummy/main.cc index b010eab29..f6f965490 100644 --- a/repos/os/src/app/dummy/main.cc +++ b/repos/os/src/app/dummy/main.cc @@ -37,11 +37,27 @@ struct Dummy::Log_service Heap _heap { _env.ram(), _env.rm() }; + bool const _verbose; + struct Session_component : Rpc_object { Session_label const _label; - Session_component(Session_label const &label) : _label(label) { } + bool const _verbose; + + Session_component(Session_label const &label, bool verbose) + : + _label(label), _verbose(verbose) + { + if (_verbose) + log("opening session with label ", _label); + } + + ~Session_component() + { + if (_verbose) + log("closing session with label ", _label); + } size_t write(String const &string) override { @@ -60,17 +76,33 @@ struct Dummy::Log_service struct Root : Root_component { - Root(Entrypoint &ep, Allocator &alloc) : Root_component(ep, alloc) { } + Ram_session &_ram; + + bool const _verbose; + + Root(Entrypoint &ep, Allocator &alloc, Ram_session &ram, bool verbose) + : + Root_component(ep, alloc), _ram(ram), _verbose(verbose) + { } Session_component *_create_session(const char *args, Affinity const &) override { - return new (md_alloc()) Session_component(label_from_args(args)); + return new (md_alloc()) Session_component(label_from_args(args), _verbose); + } + + void _upgrade_session(Session_component *, const char *args) override + { + size_t const ram_quota = + Arg_string::find_arg(args, "ram_quota").ulong_value(0); + + if (_ram.avail() >= ram_quota) + log("received session quota upgrade"); } }; - Root _root { _env.ep(), _heap }; + Root _root { _env.ep(), _heap, _env.ram(), _verbose }; - Log_service(Env &env) : _env(env) + Log_service(Env &env, bool verbose) : _env(env), _verbose(verbose) { _env.parent().announce(_env.ep().manage(_root)); log("created LOG service"); @@ -94,10 +126,21 @@ struct Dummy::Log_connections { unsigned const count = node.attribute_value("count", 0UL); + Number_of_bytes const ram_upgrade = + node.attribute_value("ram_upgrade", Number_of_bytes()); + log("going to create ", count, " LOG connections"); - for (unsigned i = 0; i < count; i++) - new (_heap) Connection(_connections, _env, Session_label { i }); + for (unsigned i = 0; i < count; i++) { + + Connection *connection = + new (_heap) Connection(_connections, _env, Session_label { i }); + + if (ram_upgrade > 0) { + log("upgrade connection ", i); + connection->upgrade_ram(ram_upgrade); + } + } log("created all LOG connections"); } @@ -212,7 +255,7 @@ struct Dummy::Main _log_connections.destruct(); if (node.type() == "log_service") - _log_service.construct(_env); + _log_service.construct(_env, node.attribute_value("verbose", false)); if (node.type() == "consume_ram") _ram_consumer.consume(node.attribute_value("amount", Number_of_bytes())); diff --git a/repos/os/src/init/buffered_xml.h b/repos/os/src/init/buffered_xml.h index 8efda75b7..c3d183311 100644 --- a/repos/os/src/init/buffered_xml.h +++ b/repos/os/src/init/buffered_xml.h @@ -14,6 +14,9 @@ #ifndef _SRC__INIT__BUFFERED_XML_H_ #define _SRC__INIT__BUFFERED_XML_H_ +/* Genode includes */ +#include + namespace Init { class Buffered_xml; } diff --git a/repos/os/src/init/main.cc b/repos/os/src/init/main.cc index ee2d61045..a95a5df03 100644 --- a/repos/os/src/init/main.cc +++ b/repos/os/src/init/main.cc @@ -20,6 +20,7 @@ #include #include #include +#include namespace Init { struct Main; } @@ -109,6 +110,8 @@ struct Init::Main : State_reporter::Producer, Child::Default_route_accessor, Signal_handler
_config_handler { _env.ep(), *this, &Main::_handle_config }; + Server _server { _env, _heap, _child_services, _state_reporter }; + Main(Env &env) : _env(env) { _config.sigh(_config_handler); @@ -358,6 +361,8 @@ void Init::Main::_handle_config() */ _children.for_each_child([&] (Child &child) { child.apply_ram_downgrade(); }); _children.for_each_child([&] (Child &child) { child.apply_ram_upgrade(); }); + + _server.apply_config(_config.xml()); } diff --git a/repos/os/src/init/server.cc b/repos/os/src/init/server.cc new file mode 100644 index 000000000..ae523247e --- /dev/null +++ b/repos/os/src/init/server.cc @@ -0,0 +1,341 @@ +/* + * \brief Server role of init, forwarding session requests to children + * \author Norman Feske + * \date 2017-03-07 + */ + +/* + * Copyright (C) 2017 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. + */ + +/* Genode includes */ +#include + +/* local includes */ +#include + + +/*************************** + ** Init::Server::Service ** + ***************************/ + +struct Init::Server::Service +{ + Registry::Element _registry_element; + + Buffered_xml _service_node; + + typedef Genode::Service::Name Name; + + Registry &_child_services; + + Name const _name { _service_node.xml().attribute_value("name", Name()) }; + + /** + * Constructor + * + * \param alloc allocator used for buffering the 'service_node' + */ + Service(Registry &services, + Allocator &alloc, + Xml_node service_node, + Registry &child_services) + : + _registry_element(services, *this), + _service_node(alloc, service_node), + _child_services(child_services) + { } + + /** + * Determine route to child service for a given label according + * to the node policy + * + * \throw Parent::Service_denied + */ + Route resolve_session_request(Session_label const &); + + Name name() const { return _name; } +}; + + +Init::Server::Route +Init::Server::Service::resolve_session_request(Session_label const &label) +{ + try { + Session_policy policy(label, _service_node.xml()); + + if (!policy.has_sub_node("child")) + throw Parent::Service_denied(); + + Xml_node target_node = policy.sub_node("child"); + + Child_policy::Name const child_name = + target_node.attribute_value("name", Child_policy::Name()); + + typedef String Label; + Label const target_label = + target_node.attribute_value("label", Label(label.string())); + + Routed_service *match = nullptr; + _child_services.for_each([&] (Routed_service &service) { + if (service.child_name() == child_name) + match = &service; }); + + if (!match || match->abandoned()) + throw Parent::Service_denied(); + + return Route { *match, target_label }; + } + catch (Session_policy::No_policy_defined) { + throw Parent::Service_denied(); } +} + + +/****************** + ** Init::Server ** + ******************/ + +Init::Server::Route +Init::Server::_resolve_session_request(Service::Name const &service_name, + Session_label const &label) +{ + Service *matching_service = nullptr; + _services.for_each([&] (Service &service) { + if (service.name() == service_name) + matching_service = &service; }); + + if (!matching_service) + throw Parent::Service_denied(); + + return matching_service->resolve_session_request(label); +} + + +static void close_session(Genode::Session_state &session) +{ + session.phase = Genode::Session_state::CLOSE_REQUESTED; + session.service().initiate_request(session); + session.service().wakeup(); +} + + +void Init::Server::session_ready(Session_state &session) +{ + _report_update_trigger.trigger_report_update(); + + /* + * If 'session_ready' is called as response to a session-quota upgrade, + * the 'phase' is set to 'CAP_HANDED_OUT' by 'Child::session_response'. + * We just need to forward the state change to our parent. + */ + if (session.phase == Session_state::CAP_HANDED_OUT) { + Parent::Server::Id id { session.id_at_client().value }; + _env.parent().session_response(id, Parent::SESSION_OK); + } + + if (session.phase == Session_state::AVAILABLE) { + Parent::Server::Id id { session.id_at_client().value }; + _env.parent().deliver_session_cap(id, session.cap); + session.phase = Session_state::CAP_HANDED_OUT; + } +} + + +void Init::Server::session_closed(Session_state &session) +{ + _report_update_trigger.trigger_report_update(); + + Parent::Server::Id id { session.id_at_client().value }; + _env.parent().session_response(id, Parent::SESSION_CLOSED); + + Ram_session_client(session.service().ram()) + .transfer_quota(_env.ram_session_cap(), session.donated_ram_quota()); + + session.destroy(); +} + + +void Init::Server::_handle_create_session_request(Xml_node request, + Parent::Client::Id id) +{ + if (!request.has_sub_node("args")) + return; + + typedef Session_state::Args Args; + Args const args = request.sub_node("args").decoded_content(); + + Service::Name const name = request.attribute_value("service", Service::Name()); + + Session_label const label = label_from_args(args.string()); + + try { + Route const route = _resolve_session_request(name, label); + + /* + * Reduce session quota by local session costs + */ + char argbuf[Parent::Session_args::MAX_SIZE]; + strncpy(argbuf, args.string(), sizeof(argbuf)); + + size_t const ram_quota = Arg_string::find_arg(argbuf, "ram_quota").ulong_value(0); + size_t const keep_quota = route.service.factory().session_costs(); + + if (ram_quota < keep_quota) + throw Genode::Service::Quota_exceeded(); + + size_t const forward_ram_quota = ram_quota - keep_quota; + + Arg_string::set_arg(argbuf, sizeof(argbuf), "ram_quota", forward_ram_quota); + + Session_state &session = + route.service.create_session(route.service.factory(), + _client_id_space, id, + route.label, argbuf, Affinity()); + + /* transfer session quota */ + if (_env.ram().transfer_quota(route.service.ram(), ram_quota)) { + + /* + * This should never happen unless our parent missed to + * transfor the session quota to us prior issuing the session + * request. + */ + warning("unable to transfer session quota (", ram_quota, " bytes) " + "of forwarded ", name, " session"); + session.destroy(); + throw Parent::Service_denied(); + } + + session.ready_callback = this; + session.closed_callback = this; + + /* initiate request */ + route.service.initiate_request(session); + + /* if request was not handled synchronously, kick off async operation */ + if (session.phase == Session_state::CREATE_REQUESTED) + route.service.wakeup(); + + if (session.phase == Session_state::INVALID_ARGS) + throw Parent::Service_denied(); + + if (session.phase == Session_state::QUOTA_EXCEEDED) + throw Genode::Service::Quota_exceeded(); + } + catch (Parent::Service_denied) { + _env.parent().session_response(Parent::Server::Id { id.value }, Parent::INVALID_ARGS); } + catch (Genode::Service::Quota_exceeded) { + _env.parent().session_response(Parent::Server::Id { id.value }, Parent::QUOTA_EXCEEDED); } +} + + +void Init::Server::_handle_upgrade_session_request(Xml_node request, + Parent::Client::Id id) +{ + _client_id_space.apply(id, [&] (Session_state &session) { + + size_t const ram_quota = request.attribute_value("ram_quota", 0UL); + + session.phase = Session_state::UPGRADE_REQUESTED; + + if (_env.ram().transfer_quota(session.service().ram(), ram_quota)) { + warning("unable to upgrade session quota (", ram_quota, " bytes) " + "of forwarded ", session.service().name(), " session"); + return; + } + + session.increase_donated_quota(ram_quota); + session.service().initiate_request(session); + session.service().wakeup(); + }); +} + + +void Init::Server::_handle_close_session_request(Xml_node request, + Parent::Client::Id id) +{ + _client_id_space.apply(id, [&] (Session_state &session) { + close_session(session); }); +} + + +void Init::Server::_handle_session_request(Xml_node request) +{ + if (!request.has_attribute("id")) + return; + + /* + * We use the 'Parent::Server::Id' of the incoming request as the + * 'Parent::Client::Id' of the forwarded request. + */ + Parent::Client::Id const id { request.attribute_value("id", 0UL) }; + + if (request.has_type("create")) + _handle_create_session_request(request, id); + + if (request.has_type("upgrade")) + _handle_upgrade_session_request(request, id); + + if (request.has_type("close")) + _handle_close_session_request(request, id); +} + + +void Init::Server::_handle_session_requests() +{ + _session_requests->update(); + + Xml_node const requests = _session_requests->xml(); + + requests.for_each_sub_node([&] (Xml_node request) { + _handle_session_request(request); }); + + _report_update_trigger.trigger_report_update(); +} + + +void Init::Server::apply_config(Xml_node config) +{ + _services.for_each([&] (Service &service) { destroy(_alloc, &service); }); + + config.for_each_sub_node("service", [&] (Xml_node node) { + new (_alloc) Service(_services, _alloc, node, _child_services); }); + + /* + * Construct mechanics for responding to our parent's session requests + * on demand. + */ + bool services_provided = false; + _services.for_each([&] (Service const &) { services_provided = true; }); + + if (services_provided && !_session_requests.constructed()) { + _session_requests.construct(_env, "session_requests"); + _session_request_handler.construct(_env.ep(), *this, + &Server::_handle_session_requests); + _session_requests->sigh(*_session_request_handler); + + if (_session_requests.constructed()) + _handle_session_requests(); + } + + /* + * Re-validate routes of existing sessions, close sessions whose routes + * changed. + */ + _client_id_space.for_each([&] (Session_state &session) { + try { + Route const route = _resolve_session_request(session.service().name(), + session.client_label()); + + bool const route_unchanged = (route.service == session.service()) + && (route.label == session.label()); + if (!route_unchanged) + throw Parent::Service_denied(); + } + catch (Parent::Service_denied) { + close_session(session); } + }); +} diff --git a/repos/os/src/init/server.h b/repos/os/src/init/server.h new file mode 100644 index 000000000..b458bb5fc --- /dev/null +++ b/repos/os/src/init/server.h @@ -0,0 +1,112 @@ +/* + * \brief Server role of init, forwarding session requests to children + * \author Norman Feske + * \date 2017-03-07 + */ + +/* + * Copyright (C) 2017 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 _SRC__INIT__SERVER_H_ +#define _SRC__INIT__SERVER_H_ + +/* Genode includes */ +#include + +/* local includes */ +#include +#include +#include +#include + +namespace Init { class Server; } + + +class Init::Server : Session_state::Ready_callback, + Session_state::Closed_callback +{ + private: + + struct Route + { + Routed_service &service; + Session_label label; + }; + + Env &_env; + + Allocator &_alloc; + + /* + * ID space of requests originating from the parent + */ + Id_space _server_id_space; + + /* + * ID space of requests issued to the children of init + */ + Id_space _client_id_space; + + /** + * Meta data of service provided to our parent + */ + struct Service; + + Registry _services; + + /** + * Services provided by our children + */ + Registry &_child_services; + + Report_update_trigger &_report_update_trigger; + + Constructible _session_requests; + Constructible > _session_request_handler; + + /** + * \throw Parent::Service_denied + */ + Route _resolve_session_request(Genode::Service::Name const &, + Session_label const &); + + void _handle_create_session_request (Xml_node, Parent::Client::Id); + void _handle_upgrade_session_request(Xml_node, Parent::Client::Id); + void _handle_close_session_request (Xml_node, Parent::Client::Id); + + void _handle_session_request(Xml_node); + void _handle_session_requests(); + + /** + * Session_state::Closed_callback interface + */ + void session_closed(Session_state &) override; + + /** + * Session_state::Ready_callback interface + */ + void session_ready(Session_state &) override; + + public: + + /** + * Constructor + * + * \param alloc allocator used for buffering XML config data and + * for allocating per-service meta data + */ + Server(Env &env, Allocator &alloc, Registry &services, + Report_update_trigger &report_update_trigger) + : + _env(env), _alloc(alloc), _child_services(services), + _report_update_trigger(report_update_trigger) + { } + + void apply_config(Xml_node); +}; + +#endif /* _SRC__INIT__SERVER_H_ */ diff --git a/repos/os/src/init/service.h b/repos/os/src/init/service.h index 3ee0cf33f..2172e3f02 100644 --- a/repos/os/src/init/service.h +++ b/repos/os/src/init/service.h @@ -14,10 +14,15 @@ #ifndef _SRC__INIT__SERVICE_H_ #define _SRC__INIT__SERVICE_H_ +/* Genode includes */ +#include +#include + namespace Init { class Abandonable; class Parent_service; class Routed_service; + class Forwarded_service; } @@ -68,6 +73,8 @@ class Init::Routed_service : public Child_service, public Abandonable Ram_accessor &_ram_accessor; + Session_state::Factory &_factory; + Registry::Element _registry_element; public: @@ -91,12 +98,21 @@ class Init::Routed_service : public Child_service, public Abandonable Child_service(server_id_space, factory, name, Ram_session_capability(), wakeup), _child_name(child_name), _ram_accessor(ram_accessor), - _registry_element(services, *this) + _factory(factory), _registry_element(services, *this) { } Child_name const &child_name() const { return _child_name; } Ram_session_capability ram() const { return _ram_accessor.ram(); } + + /** + * Return factory for creating/destroying session-state objects + * + * This accessor is solely meant to be used by 'Forwarded_service' to + * allocate session-state objects for sessions requested by init's + * parent. + */ + Session_state::Factory &factory() { return _factory; } }; #endif /* _SRC__INIT__SERVICE_H_ */ diff --git a/repos/os/src/init/state_reporter.h b/repos/os/src/init/state_reporter.h index 3391f8cea..4b22e16b9 100644 --- a/repos/os/src/init/state_reporter.h +++ b/repos/os/src/init/state_reporter.h @@ -14,9 +14,13 @@ #ifndef _SRC__INIT__STATE_REPORTER_H_ #define _SRC__INIT__STATE_REPORTER_H_ +/* Genode includes */ #include #include +/* local includes */ +#include + namespace Init { class State_reporter; } class Init::State_reporter : public Report_update_trigger diff --git a/repos/os/src/init/target.mk b/repos/os/src/init/target.mk index 9180584f6..f42589499 100644 --- a/repos/os/src/init/target.mk +++ b/repos/os/src/init/target.mk @@ -1,4 +1,4 @@ TARGET = init -SRC_CC = main.cc child.cc +SRC_CC = main.cc child.cc server.cc LIBS = base INC_DIR += $(PRG_DIR) diff --git a/repos/os/src/init/types.h b/repos/os/src/init/types.h index 46f08d80a..418723899 100644 --- a/repos/os/src/init/types.h +++ b/repos/os/src/init/types.h @@ -15,6 +15,7 @@ #define _SRC__INIT__TYPES_H_ #include +#include namespace Init {