/* * \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 #include /* local includes */ #include "server.h" /*************************** ** 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 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 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 && service.name() == name()) match = &service; }); if (!match || match->abandoned()) throw Service_denied(); return Route { *match, target_label }; } catch (Session_policy::No_policy_defined) { throw 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 Service_not_present(); 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; } if (session.phase == Session_state::SERVICE_DENIED) _close_session(session, Parent::SERVICE_DENIED); if (session.phase == Session_state::INSUFFICIENT_RAM_QUOTA) _close_session(session, Parent::INSUFFICIENT_RAM_QUOTA); if (session.phase == Session_state::INSUFFICIENT_CAP_QUOTA) _close_session(session, Parent::INSUFFICIENT_CAP_QUOTA); } void Init::Server::_close_session(Session_state &session, Parent::Session_response response) { _report_update_trigger.trigger_report_update(); Ram_transfer::Account &service_ram_account = session.service(); Cap_transfer::Account &service_cap_account = session.service(); service_ram_account.try_transfer(_env.pd_session_cap(), session.donated_ram_quota()); service_cap_account.try_transfer(_env.pd_session_cap(), session.donated_cap_quota()); Parent::Server::Id id { session.id_at_client().value }; session.destroy(); _env.parent().session_response(id, response); } void Init::Server::session_closed(Session_state &session) { _close_session(session, Parent::SESSION_CLOSED); } void Init::Server::_handle_create_session_request(Xml_node request, Parent::Client::Id id) { /* * Ignore requests that are already successfully forwarded (by a prior call * of '_handle_create_session_request') but still remain present in the * 'session_requests' ROM because the server child has not responded yet. */ try { _client_id_space.apply(id, [&] (Parent::Client const &) { }); return; } catch (Id_space::Unknown_id) { /* normal case */ } 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)); Cap_quota const cap_quota = cap_quota_from_args(argbuf); Ram_quota const ram_quota = ram_quota_from_args(argbuf); size_t const keep_quota = route.service.factory().session_costs(); if (ram_quota.value < keep_quota) throw Genode::Insufficient_ram_quota(); Ram_quota const forward_ram_quota { ram_quota.value - keep_quota }; Arg_string::set_arg(argbuf, sizeof(argbuf), "ram_quota", forward_ram_quota.value); Session_state &session = route.service.create_session(route.service.factory(), _client_id_space, id, route.label, argbuf, Affinity()); /* transfer session quota */ try { Ram_transfer::Remote_account env_ram_account(_env.pd(), _env.pd_session_cap()); Cap_transfer::Remote_account env_cap_account(_env.pd(), _env.pd_session_cap()); Ram_transfer ram_transfer(forward_ram_quota, env_ram_account, route.service); Cap_transfer cap_transfer(cap_quota, env_cap_account, route.service); ram_transfer.acknowledge(); cap_transfer.acknowledge(); } catch (...) { /* * 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, ", cap_quota, " caps) " "of forwarded ", name, " session"); session.destroy(); throw 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::SERVICE_DENIED) throw Service_denied(); if (session.phase == Session_state::INSUFFICIENT_RAM_QUOTA) throw Insufficient_ram_quota(); if (session.phase == Session_state::INSUFFICIENT_CAP_QUOTA) throw Insufficient_cap_quota(); } catch (Service_denied) { _env.parent().session_response(Parent::Server::Id { id.value }, Parent::SERVICE_DENIED); } catch (Insufficient_ram_quota) { _env.parent().session_response(Parent::Server::Id { id.value }, Parent::INSUFFICIENT_RAM_QUOTA); } catch (Insufficient_cap_quota) { _env.parent().session_response(Parent::Server::Id { id.value }, Parent::INSUFFICIENT_CAP_QUOTA); } catch (Service_not_present) { /* keep request pending */ } } void Init::Server::_handle_upgrade_session_request(Xml_node request, Parent::Client::Id id) { _client_id_space.apply(id, [&] (Session_state &session) { Ram_quota const ram_quota { request.attribute_value("ram_quota", 0UL) }; Cap_quota const cap_quota { request.attribute_value("cap_quota", 0UL) }; session.phase = Session_state::UPGRADE_REQUESTED; try { Ram_transfer::Remote_account env_ram_account(_env.pd(), _env.pd_session_cap()); Cap_transfer::Remote_account env_cap_account(_env.pd(), _env.pd_session_cap()); Ram_transfer ram_transfer(ram_quota, env_ram_account, session.service()); Cap_transfer cap_transfer(cap_quota, env_cap_account, session.service()); ram_transfer.acknowledge(); cap_transfer.acknowledge(); } catch (...) { warning("unable to upgrade session quota " "(", ram_quota, " bytes, ", cap_quota, " caps) " "of forwarded ", session.service().name(), " session"); return; } session.increase_donated_quota(ram_quota, cap_quota); session.service().initiate_request(session); session.service().wakeup(); }); } void Init::Server::_handle_close_session_request(Xml_node, 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); } /* * Try to resolve pending session requests that may become serviceable with * the new configuration. */ if (services_provided && _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 Service_denied(); } catch (Service_denied) { close_session(session); } catch (Service_not_present) { close_session(session); } }); }