412 lines
12 KiB
C++
412 lines
12 KiB
C++
/*
|
|
* \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 <base/quota_transfer.h>
|
|
#include <os/session_policy.h>
|
|
|
|
/* local includes */
|
|
#include "server.h"
|
|
|
|
|
|
/******************************
|
|
** Sandbox::Server::Service **
|
|
**********...*****************/
|
|
|
|
struct Sandbox::Server::Service
|
|
{
|
|
Registry<Service>::Element _registry_element;
|
|
|
|
Buffered_xml _service_node;
|
|
|
|
typedef Genode::Service::Name Name;
|
|
|
|
Registry<Routed_service> &_child_services;
|
|
|
|
Name const _name { _service_node.xml().attribute_value("name", Name()) };
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* \param alloc allocator used for buffering the 'service_node'
|
|
*/
|
|
Service(Registry<Service> &services,
|
|
Allocator &alloc,
|
|
Xml_node service_node,
|
|
Registry<Routed_service> &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 <service> node policy
|
|
*
|
|
* \throw Service_denied
|
|
*/
|
|
Route resolve_session_request(Session_label const &);
|
|
|
|
Name name() const { return _name; }
|
|
};
|
|
|
|
|
|
Sandbox::Server::Route
|
|
Sandbox::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<Session_label::capacity()> 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(); }
|
|
}
|
|
|
|
|
|
/*********************
|
|
** Sandbox::Server **
|
|
*********************/
|
|
|
|
Sandbox::Server::Route
|
|
Sandbox::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 Sandbox::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 Sandbox::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 Sandbox::Server::session_closed(Session_state &session)
|
|
{
|
|
_close_session(session, Parent::SESSION_CLOSED);
|
|
}
|
|
|
|
|
|
void Sandbox::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<Parent::Client>(id, [&] (Parent::Client const &) { });
|
|
return;
|
|
} catch (Id_space<Parent::Client>::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<Args>();
|
|
|
|
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 Sandbox::Server::_handle_upgrade_session_request(Xml_node request,
|
|
Parent::Client::Id id)
|
|
{
|
|
_client_id_space.apply<Session_state>(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 Sandbox::Server::_handle_close_session_request(Xml_node, Parent::Client::Id id)
|
|
{
|
|
_client_id_space.apply<Session_state>(id, [&] (Session_state &session) {
|
|
close_session(session); });
|
|
}
|
|
|
|
|
|
void Sandbox::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 Sandbox::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 Sandbox::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_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); }
|
|
});
|
|
}
|