Merged refactored sandbox library with upstream.

This commit is contained in:
Michael Mueller
2025-01-21 18:31:53 +01:00
parent 504bc57806
commit 2b695b2392
4 changed files with 1051 additions and 876 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@
#include <sandbox/server.h> #include <sandbox/server.h>
#include <sandbox/heartbeat.h> #include <sandbox/heartbeat.h>
#include <sandbox/config_model.h> #include <sandbox/config_model.h>
#include <sandbox/sandbox.h>
#include <base/env.h> #include <base/env.h>
#include <base/heap.h> #include <base/heap.h>
@@ -21,106 +22,111 @@ namespace Sandbox {
namespace Genode { namespace Genode {
class Sandbox; class Sandbox;
} }
struct Sandbox::Library : ::Sandbox::State_reporter::Producer, struct Sandbox::Library : ::Sandbox::State_reporter::Producer,
::Sandbox::Child::Default_route_accessor, ::Sandbox::Child::Default_route_accessor,
::Sandbox::Child::Default_caps_accessor, ::Sandbox::Child::Default_caps_accessor,
::Sandbox::Child::Ram_limit_accessor, ::Sandbox::Child::Ram_limit_accessor,
::Sandbox::Child::Cap_limit_accessor, ::Sandbox::Child::Cap_limit_accessor,
::Sandbox::Child::Cpu_limit_accessor, ::Sandbox::Child::Cpu_limit_accessor,
::Sandbox::Child::Cpu_quota_transfer, ::Sandbox::Child::Cpu_quota_transfer,
::Sandbox::Start_model::Factory, ::Sandbox::Start_model::Factory,
::Sandbox::Parent_provides_model::Factory ::Sandbox::Parent_provides_model::Factory
{ {
using Routed_service = ::Sandbox::Routed_service; using Routed_service = ::Sandbox::Routed_service;
using Parent_service = ::Sandbox::Parent_service; using Parent_service = ::Sandbox::Parent_service;
using Local_service = ::Genode::Sandbox::Local_service_base; using Local_service = ::Genode::Sandbox::Local_service_base;
using Report_detail = ::Sandbox::Report_detail; using Report_detail = ::Sandbox::Report_detail;
using Child_registry = ::Sandbox::Child_registry; using Child_registry = ::Sandbox::Child_registry;
using Verbose = ::Sandbox::Verbose; using Verbose = ::Sandbox::Verbose;
using State_reporter = ::Sandbox::State_reporter; using State_reporter = ::Sandbox::State_reporter;
using Heartbeat = ::Sandbox::Heartbeat; using Heartbeat = ::Sandbox::Heartbeat;
using Server = ::Sandbox::Server; using Server = ::Sandbox::Server;
using Alias = ::Sandbox::Alias; using Alias = ::Sandbox::Alias;
using Child = ::Sandbox::Child; using Child = ::Sandbox::Child;
using Prio_levels = ::Sandbox::Prio_levels; using Prio_levels = ::Sandbox::Prio_levels;
using Ram_info = ::Sandbox::Ram_info; using Ram_info = ::Sandbox::Ram_info;
using Cap_info = ::Sandbox::Cap_info; using Cap_info = ::Sandbox::Cap_info;
using Cpu_quota = ::Sandbox::Cpu_quota; using Cpu_quota = ::Sandbox::Cpu_quota;
using Config_model = ::Sandbox::Config_model; using Config_model = ::Sandbox::Config_model;
using Start_model = ::Sandbox::Start_model; using Start_model = ::Sandbox::Start_model;
using Preservation = ::Sandbox::Preservation; using Preservation = ::Sandbox::Preservation;
using Pd_intrinsics = Genode::Sandbox::Pd_intrinsics;
public: using State_handler = Genode::Sandbox::State_handler;
Env &_env;
Env &_env;
Heap &_heap; Heap &_heap;
Registry<Parent_service> _parent_services { }; Pd_intrinsics &_pd_intrinsics;
Registry<Routed_service> _child_services { };
Registry<Local_service> &_local_services; Registry<Parent_service> _parent_services{};
Child_registry _children { }; Registry<Routed_service> _child_services{};
Registry<Local_service> &_local_services;
Child_registry _children{};
/* /*
* Global parameters obtained from config * Global parameters obtained from config
*/ */
Reconstructible<Verbose> _verbose { }; Reconstructible<Verbose> _verbose{};
Config_model::Version _version { }; Config_model::Version _version{};
Constructible<Buffered_xml> _default_route { }; Constructible<Buffered_xml> _default_route{};
Cap_quota _default_caps { 0 }; Cap_quota _default_caps{0};
Prio_levels _prio_levels { }; Prio_levels _prio_levels{};
Constructible<Affinity::Space> _affinity_space { }; Constructible<Affinity::Space> _affinity_space{};
Preservation _preservation { }; Preservation _preservation{};
Affinity::Space _effective_affinity_space() const Affinity::Space _effective_affinity_space() const
{ {
return _affinity_space.constructed() ? *_affinity_space return _affinity_space.constructed() ? *_affinity_space
: Affinity::Space { 1, 1 }; : Affinity::Space{1, 1};
} }
State_reporter _state_reporter; State_reporter _state_reporter;
Heartbeat _heartbeat { _env, _children, _state_reporter }; Heartbeat _heartbeat{_env, _children, _state_reporter};
/* /*
* Internal representation of the XML configuration * Internal representation of the XML configuration
*/ */
Config_model _config_model { }; Config_model _config_model{};
/* /*
* Variables for tracking the side effects of updating the config model * Variables for tracking the side effects of updating the config model
*/ */
bool _server_appeared_or_disappeared = false; bool _server_appeared_or_disappeared = false;
bool _state_report_outdated = false; bool _state_report_outdated = false;
unsigned _child_cnt = 0; unsigned _child_cnt = 0;
Cpu_quota _avail_cpu { .percent = 100 }; Cpu_quota _avail_cpu{.percent = 100};
Cpu_quota _transferred_cpu { .percent = 0 }; Cpu_quota _transferred_cpu{.percent = 0};
Ram_quota _avail_ram() const Ram_quota _avail_ram() const
{ {
Ram_quota avail_ram = _env.pd().avail_ram(); Ram_quota avail_ram = _env.pd().avail_ram();
if (_preservation.ram.value > avail_ram.value) { if (_preservation.ram.value > avail_ram.value)
{
error("RAM preservation exceeds available memory"); error("RAM preservation exceeds available memory");
return Ram_quota { 0 }; return Ram_quota{0};
} }
/* deduce preserved quota from available quota */ /* deduce preserved quota from available quota */
return Ram_quota { avail_ram.value - _preservation.ram.value }; return Ram_quota{avail_ram.value - _preservation.ram.value};
} }
Cap_quota _avail_caps() const Cap_quota _avail_caps() const
{ {
Cap_quota avail_caps { _env.pd().avail_caps().value }; Cap_quota avail_caps{_env.pd().avail_caps().value};
if (_preservation.caps.value > avail_caps.value) { if (_preservation.caps.value > avail_caps.value)
{
error("Capability preservation exceeds available capabilities"); error("Capability preservation exceeds available capabilities");
return Cap_quota { 0 }; return Cap_quota{0};
} }
/* deduce preserved quota from available quota */ /* deduce preserved quota from available quota */
return Cap_quota { avail_caps.value - _preservation.caps.value }; return Cap_quota{avail_caps.value - _preservation.caps.value};
} }
/** /**
@@ -144,9 +150,10 @@ public:
/** /**
* Child::Cpu_quota_transfer interface * Child::Cpu_quota_transfer interface
*/ */
void transfer_cpu_quota(Cpu_session_capability cap, Cpu_quota quota) override void transfer_cpu_quota(Capability<Pd_session> pd_cap, Pd_session &pd,
Capability<Cpu_session> cpu, Cpu_quota quota) override
{ {
Cpu_quota const remaining { 100 - min(100u, _transferred_cpu.percent) }; Cpu_quota const remaining{100 - min(100u, _transferred_cpu.percent)};
/* prevent division by zero in 'quota_lim_upscale' */ /* prevent division by zero in 'quota_lim_upscale' */
if (remaining.percent == 0) if (remaining.percent == 0)
@@ -155,7 +162,8 @@ public:
size_t const fraction = size_t const fraction =
Cpu_session::quota_lim_upscale(quota.percent, remaining.percent); Cpu_session::quota_lim_upscale(quota.percent, remaining.percent);
_env.cpu().transfer_quota(cap, fraction); Child::with_pd_intrinsics(_pd_intrinsics, pd_cap, pd, [&](auto &intrinsics)
{ intrinsics.ref_cpu.transfer_quota(cpu, fraction); });
_transferred_cpu.percent += quota.percent; _transferred_cpu.percent += quota.percent;
} }
@@ -166,10 +174,12 @@ public:
void produce_state_report(Xml_generator &xml, Report_detail const &detail) const override void produce_state_report(Xml_generator &xml, Report_detail const &detail) const override
{ {
if (detail.init_ram()) if (detail.init_ram())
xml.node("ram", [&] () { Ram_info::from_pd(_env.pd()).generate(xml); }); xml.node("ram", [&]()
{ Ram_info::from_pd(_env.pd()).generate(xml); });
if (detail.init_caps()) if (detail.init_caps())
xml.node("caps", [&] () { Cap_info::from_pd(_env.pd()).generate(xml); }); xml.node("caps", [&]()
{ Cap_info::from_pd(_env.pd()).generate(xml); });
if (detail.children()) if (detail.children())
_children.report_state(xml, detail); _children.report_state(xml, detail);
@@ -189,7 +199,7 @@ public:
Xml_node default_route() override Xml_node default_route() override
{ {
return _default_route.constructed() ? _default_route->xml() return _default_route.constructed() ? _default_route->xml()
: Xml_node("<empty/>"); : Xml_node("<empty/>");
} }
/** /**
@@ -201,19 +211,19 @@ public:
void _update_parent_services_from_config(Xml_node const &); void _update_parent_services_from_config(Xml_node const &);
void _update_children_config(Xml_node const &); void _update_children_config(Xml_node const &);
void _destroy_abandoned_parent_services(); void _destroy_abandoned_parent_services();
virtual void _destroy_abandoned_children(); void _destroy_abandoned_children();
Server _server { _env, _heap, _child_services, _state_reporter }; Server _server{_env, _heap, _child_services, _state_reporter};
/** /**
* Sandbox::Start_model::Factory * Sandbox::Start_model::Factory
*/ */
virtual Child &create_child(Xml_node const &) override; Child &create_child(Xml_node const &) override;
/** /**
* Sandbox::Start_model::Factory * Sandbox::Start_model::Factory
*/ */
virtual void update_child(Child &, Xml_node const &) override; void update_child(Child &, Xml_node const &) override;
/** /**
* Sandbox::Start_model::Factory * Sandbox::Start_model::Factory
@@ -237,8 +247,8 @@ public:
/** /**
* Sandbox::Start_model::Factory * Sandbox::Start_model::Factory
*/ */
bool ready_to_create_child(Start_model::Name const &, bool ready_to_create_child(Start_model::Name const &,
Start_model::Version const &) const override; Start_model::Version const &) const override;
/** /**
* Sandbox::Parent_provides_model::Factory * Sandbox::Parent_provides_model::Factory
@@ -248,16 +258,48 @@ public:
return *new (_heap) Parent_service(_parent_services, _env, name); return *new (_heap) Parent_service(_parent_services, _env, name);
} }
/**
* Default way of using the 'Env::pd' as the child's 'ref_pd' and accessing
* the child's address space via RPC.
*/
struct Default_pd_intrinsics : Pd_intrinsics
{
Env &_env;
void with_intrinsics(Capability<Pd_session>, Pd_session &pd, Fn const &fn) override
{
Region_map_client region_map(pd.address_space());
Intrinsics intrinsics{_env.pd(), _env.pd_session_cap(),
_env.cpu(), _env.cpu_session_cap(), region_map};
fn.call(intrinsics);
}
void start_initial_thread(Capability<Cpu_thread> cap, addr_t ip) override
{
Cpu_thread_client(cap).start(ip, 0);
}
Default_pd_intrinsics(Env &env) : _env(env) {}
} _default_pd_intrinsics{_env};
Library(Env &env, Heap &heap, Registry<Local_service> &local_services, Library(Env &env, Heap &heap, Registry<Local_service> &local_services,
Genode::Sandbox::State_handler &state_handler) State_handler &state_handler, Pd_intrinsics &pd_intrinsics)
: : _env(env), _heap(heap), _pd_intrinsics(pd_intrinsics),
_env(env), _heap(heap), _local_services(local_services), _local_services(local_services), _state_reporter(_env, *this, state_handler)
_state_reporter(_env, *this, state_handler) {
{ } }
virtual void apply_config(Xml_node const &); Library(Env &env, Heap &heap, Registry<Local_service> &local_services,
State_handler &state_handler)
: Library(env, heap, local_services, state_handler, _default_pd_intrinsics)
{
}
virtual void generate_state_report(Xml_generator &xml) const void apply_config(Xml_node const &);
void generate_state_report(Xml_generator &xml) const
{ {
_state_reporter.generate(xml); _state_reporter.generate(xml);
} }

View File

@@ -15,17 +15,16 @@
#include <vm_session/vm_session.h> #include <vm_session/vm_session.h>
/* local includes */ /* local includes */
#include <sandbox/child.h> #include <child.h>
void Sandbox::Child::destroy_services() void Sandbox::Child::destroy_services()
{ {
_child_services.for_each([&] (Routed_service &service) { _child_services.for_each([&](Routed_service &service)
{
if (service.has_id_space(_session_requester.id_space())) if (service.has_id_space(_session_requester.id_space()))
destroy(_alloc, &service); }); destroy(_alloc, &service); });
} }
Sandbox::Child::Apply_config_result Sandbox::Child::Apply_config_result
Sandbox::Child::apply_config(Xml_node start_node) Sandbox::Child::apply_config(Xml_node start_node)
{ {
@@ -36,15 +35,17 @@ Sandbox::Child::apply_config(Xml_node start_node)
* If the child was started but its environment is incomplete, mark it as * If the child was started but its environment is incomplete, mark it as
* being stuck in order to restart it once the environment changes. * being stuck in order to restart it once the environment changes.
*/ */
if (_state != State::INITIAL) { if (_state != State::INITIAL)
{
bool env_log_exists = false, env_binary_exists = false; bool env_log_exists = false, env_binary_exists = false;
_child.for_each_session([&] (Session_state const &session) { _child.for_each_session([&](Session_state const &session)
{
Parent::Client::Id const id = session.id_at_client(); Parent::Client::Id const id = session.id_at_client();
env_log_exists |= (id == Parent::Env::log()); env_log_exists |= (id == Parent::Env::log());
env_binary_exists |= (id == Parent::Env::binary()); env_binary_exists |= (id == Parent::Env::binary()); });
});
if (!env_binary_exists || !env_log_exists) { if (!env_binary_exists || !env_log_exists)
{
_state = State::STUCK; _state = State::STUCK;
return NO_SIDE_EFFECTS; return NO_SIDE_EFFECTS;
} }
@@ -52,22 +53,29 @@ Sandbox::Child::apply_config(Xml_node start_node)
bool provided_services_changed = false; bool provided_services_changed = false;
enum Config_update { CONFIG_APPEARED, CONFIG_VANISHED, enum Config_update
CONFIG_CHANGED, CONFIG_UNCHANGED }; {
CONFIG_APPEARED,
CONFIG_VANISHED,
CONFIG_CHANGED,
CONFIG_UNCHANGED
};
Config_update config_update = CONFIG_UNCHANGED; Config_update config_update = CONFIG_UNCHANGED;
/* /*
* Import new start node if it differs * Import new start node if it differs
*/ */
if (start_node.differs_from(_start_node->xml())) { if (start_node.differs_from(_start_node->xml()))
{
/* /*
* The <route> node may affect the availability or unavailability * The <route> node may affect the availability or unavailability
* of dependencies. * of dependencies.
*/ */
start_node.with_optional_sub_node("route", [&] (Xml_node const &route) { start_node.with_optional_sub_node("route", [&](Xml_node const &route)
_start_node->xml().with_optional_sub_node("route", [&] (Xml_node const &orig) { { _start_node->xml().with_optional_sub_node("route", [&](Xml_node const &orig)
{
if (route.differs_from(orig)) { if (route.differs_from(orig)) {
_construct_route_model_from_start_node(start_node); _construct_route_model_from_start_node(start_node);
_uncertain_dependencies = true; } }); }); _uncertain_dependencies = true; } }); });
@@ -75,9 +83,9 @@ Sandbox::Child::apply_config(Xml_node start_node)
/* /*
* Determine how the inline config is affected. * Determine how the inline config is affected.
*/ */
char const * const tag = "config"; char const *const tag = "config";
bool const config_was_present = _start_node->xml().has_sub_node(tag); bool const config_was_present = _start_node->xml().has_sub_node(tag);
bool const config_is_present = start_node.has_sub_node(tag); bool const config_is_present = start_node.has_sub_node(tag);
if (config_was_present != config_is_present) if (config_was_present != config_is_present)
_uncertain_dependencies = true; _uncertain_dependencies = true;
@@ -88,7 +96,8 @@ Sandbox::Child::apply_config(Xml_node start_node)
if (!config_was_present && config_is_present) if (!config_was_present && config_is_present)
config_update = CONFIG_APPEARED; config_update = CONFIG_APPEARED;
if (config_was_present && config_is_present) { if (config_was_present && config_is_present)
{
Xml_node const old_config = _start_node->xml().sub_node(tag); Xml_node const old_config = _start_node->xml().sub_node(tag);
Xml_node const new_config = start_node.sub_node(tag); Xml_node const new_config = start_node.sub_node(tag);
@@ -103,7 +112,8 @@ Sandbox::Child::apply_config(Xml_node start_node)
* First abandon services that are no longer present in the * First abandon services that are no longer present in the
* <provides> node. Then add services that have newly appeared. * <provides> node. Then add services that have newly appeared.
*/ */
_child_services.for_each([&] (Routed_service &service) { _child_services.for_each([&](Routed_service &service)
{
if (!_provided_by_this(service)) if (!_provided_by_this(service))
return; return;
@@ -120,17 +130,15 @@ Sandbox::Child::apply_config(Xml_node start_node)
if (!still_provided) { if (!still_provided) {
service.abandon(); service.abandon();
provided_services_changed = true; provided_services_changed = true;
} } });
});
_provides_sub_node(start_node).for_each_sub_node("service", _provides_sub_node(start_node).for_each_sub_node("service", [&](Xml_node node)
[&] (Xml_node node) { {
if (_service_exists(node)) if (_service_exists(node))
return; return;
_add_service(node); _add_service(node);
provided_services_changed = true; provided_services_changed = true; });
});
/* /*
* Import new binary name. A change may affect the route for * Import new binary name. A change may affect the route for
@@ -154,11 +162,19 @@ Sandbox::Child::apply_config(Xml_node start_node)
* may in turn prompt the routing-check by 'evaluate_dependencies' * may in turn prompt the routing-check by 'evaluate_dependencies'
* to restart the child. * to restart the child.
*/ */
switch (config_update) { switch (config_update)
case CONFIG_UNCHANGED: break; {
case CONFIG_CHANGED: _config_rom_service->trigger_update(); break; case CONFIG_UNCHANGED:
case CONFIG_APPEARED: _config_rom_service.construct(*this); break; break;
case CONFIG_VANISHED: _config_rom_service->abandon(); break; case CONFIG_CHANGED:
_config_rom_service->trigger_update();
break;
case CONFIG_APPEARED:
_config_rom_service.construct(*this);
break;
case CONFIG_VANISHED:
_config_rom_service->abandon();
break;
} }
if (provided_services_changed) if (provided_services_changed)
@@ -167,24 +183,24 @@ Sandbox::Child::apply_config(Xml_node start_node)
return NO_SIDE_EFFECTS; return NO_SIDE_EFFECTS;
} }
void Sandbox::Child::evaluate_dependencies() void Sandbox::Child::evaluate_dependencies()
{ {
bool any_route_changed = false; bool any_route_changed = false;
bool any_route_unavailable = false; bool any_route_unavailable = false;
_child.for_each_session([&] (Session_state const &session) { _child.for_each_session([&](Session_state const &session)
{
switch (_route_valid(session)) { switch (_route_valid(session)) {
case Route_state::VALID: break; case Route_state::VALID: break;
case Route_state::UNAVAILABLE: any_route_unavailable = true; break; case Route_state::UNAVAILABLE: any_route_unavailable = true; break;
case Route_state::MISMATCH: any_route_changed = true; break; case Route_state::MISMATCH: any_route_changed = true; break;
} } });
});
_uncertain_dependencies = false; _uncertain_dependencies = false;
if (any_route_unavailable) { if (any_route_unavailable)
{
_state = State::STUCK; _state = State::STUCK;
return; return;
} }
@@ -193,35 +209,33 @@ void Sandbox::Child::evaluate_dependencies()
_schedule_restart(); _schedule_restart();
} }
Sandbox::Ram_quota Sandbox::Child::_configured_ram_quota() const Sandbox::Ram_quota Sandbox::Child::_configured_ram_quota() const
{ {
size_t assigned = 0; size_t assigned = 0;
_start_node->xml().for_each_sub_node("resource", [&] (Xml_node resource) { _start_node->xml().for_each_sub_node("resource", [&](Xml_node resource)
{
if (resource.attribute_value("name", String<8>()) == "RAM") if (resource.attribute_value("name", String<8>()) == "RAM")
assigned = resource.attribute_value("quantum", Number_of_bytes()); }); assigned = resource.attribute_value("quantum", Number_of_bytes()); });
return Ram_quota { assigned }; return Ram_quota{assigned};
} }
Sandbox::Cap_quota Sandbox::Child::_configured_cap_quota() const Sandbox::Cap_quota Sandbox::Child::_configured_cap_quota() const
{ {
size_t const default_caps = _default_caps_accessor.default_caps().value; size_t const default_caps = _default_caps_accessor.default_caps().value;
return Cap_quota { _start_node->xml().attribute_value("caps", default_caps) }; return Cap_quota{_start_node->xml().attribute_value("caps", default_caps)};
} }
template <typename QUOTA, typename LIMIT_ACCESSOR> template <typename QUOTA, typename LIMIT_ACCESSOR>
void Sandbox::Child::_apply_resource_upgrade(QUOTA &assigned, QUOTA const configured, void Sandbox::Child::_apply_resource_upgrade(QUOTA &assigned, QUOTA const configured,
LIMIT_ACCESSOR const &limit_accessor) LIMIT_ACCESSOR const &limit_accessor)
{ {
if (configured.value <= assigned.value) if (configured.value <= assigned.value)
return; return;
QUOTA const limit = limit_accessor.resource_limit(QUOTA{}); QUOTA const limit = limit_accessor.resource_limit(QUOTA{});
size_t const increment = configured.value - assigned.value; size_t const increment = configured.value - assigned.value;
/* /*
@@ -232,7 +246,7 @@ void Sandbox::Child::_apply_resource_upgrade(QUOTA &assigned, QUOTA const config
if (_verbose.enabled()) if (_verbose.enabled())
warn_insuff_quota(limit.value); warn_insuff_quota(limit.value);
QUOTA const transfer { min(increment, limit.value) }; QUOTA const transfer{min(increment, limit.value)};
/* /*
* Remember assignment and apply upgrade to child * Remember assignment and apply upgrade to child
@@ -243,21 +257,22 @@ void Sandbox::Child::_apply_resource_upgrade(QUOTA &assigned, QUOTA const config
* This way, a future config update will attempt the completion of the * This way, a future config update will attempt the completion of the
* upgrade if memory become available. * upgrade if memory become available.
*/ */
if (transfer.value) { if (transfer.value)
{
assigned.value += transfer.value; assigned.value += transfer.value;
ref_pd().transfer_quota(_child.pd_session_cap(), transfer); ref_pd().transfer_quota(_child.pd_session_cap(), transfer);
/* wake up child that blocks on a resource request */ /* wake up child that blocks on a resource request */
if (_requested_resources.constructed()) { if (_requested_resources.constructed())
{
_child.notify_resource_avail(); _child.notify_resource_avail();
_requested_resources.destruct(); _requested_resources.destruct();
} }
} }
} }
void Sandbox::Child::apply_upgrade() void Sandbox::Child::apply_upgrade()
{ {
/* pd_session_cap of exited child is invalid and unusable for transfers */ /* pd_session_cap of exited child is invalid and unusable for transfers */
@@ -268,25 +283,24 @@ void Sandbox::Child::apply_upgrade()
warning(name(), ": no valid RAM quota defined"); warning(name(), ": no valid RAM quota defined");
_apply_resource_upgrade(_resources.assigned_ram_quota, _apply_resource_upgrade(_resources.assigned_ram_quota,
_configured_ram_quota(), _ram_limit_accessor); _configured_ram_quota(), _ram_limit_accessor);
if (_resources.effective_cap_quota().value == 0) if (_resources.effective_cap_quota().value == 0)
warning(name(), ": no valid capability quota defined"); warning(name(), ": no valid capability quota defined");
_apply_resource_upgrade(_resources.assigned_cap_quota, _apply_resource_upgrade(_resources.assigned_cap_quota,
_configured_cap_quota(), _cap_limit_accessor); _configured_cap_quota(), _cap_limit_accessor);
} }
template <typename QUOTA, typename CHILD_AVAIL_QUOTA_FN> template <typename QUOTA, typename CHILD_AVAIL_QUOTA_FN>
void Sandbox::Child::_apply_resource_downgrade(QUOTA &assigned, QUOTA const configured, void Sandbox::Child::_apply_resource_downgrade(QUOTA &assigned, QUOTA const configured,
QUOTA const preserved, QUOTA const preserved,
CHILD_AVAIL_QUOTA_FN const &child_avail_quota_fn) CHILD_AVAIL_QUOTA_FN const &child_avail_quota_fn)
{ {
if (configured.value >= assigned.value) if (configured.value >= assigned.value)
return; return;
QUOTA const decrement { assigned.value - configured.value }; QUOTA const decrement{assigned.value - configured.value};
/* /*
* The child may concurrently consume quota from its PD session, * The child may concurrently consume quota from its PD session,
@@ -294,39 +308,45 @@ void Sandbox::Child::_apply_resource_downgrade(QUOTA &assigned, QUOTA const conf
* attempt the transfer. * attempt the transfer.
*/ */
unsigned max_attempts = 4, attempts = 0; unsigned max_attempts = 4, attempts = 0;
for (; attempts < max_attempts; attempts++) { for (; attempts < max_attempts; attempts++)
{
/* give up if the child's available quota is exhausted */ /* give up if the child's available quota is exhausted */
size_t const avail = child_avail_quota_fn().value; size_t const avail = child_avail_quota_fn().value;
if (avail < preserved.value) if (avail < preserved.value)
break; break;
QUOTA const transfer { min(avail - preserved.value, decrement.value) }; QUOTA const transfer{min(avail - preserved.value, decrement.value)};
try { try
{
_child.pd().transfer_quota(ref_pd_cap(), transfer); _child.pd().transfer_quota(ref_pd_cap(), transfer);
assigned.value -= transfer.value; assigned.value -= transfer.value;
break; break;
} catch (...) { } }
catch (...)
{
}
} }
if (attempts == max_attempts) if (attempts == max_attempts)
warning(name(), ": downgrade failed after ", max_attempts, " attempts"); warning(name(), ": downgrade failed after ", max_attempts, " attempts");
} }
void Sandbox::Child::apply_downgrade() void Sandbox::Child::apply_downgrade()
{ {
Ram_quota const configured_ram_quota = _configured_ram_quota(); Ram_quota const configured_ram_quota = _configured_ram_quota();
Cap_quota const configured_cap_quota = _configured_cap_quota(); Cap_quota const configured_cap_quota = _configured_cap_quota();
_apply_resource_downgrade(_resources.assigned_ram_quota, _apply_resource_downgrade(_resources.assigned_ram_quota,
configured_ram_quota, Ram_quota{16*1024}, configured_ram_quota, Ram_quota{16 * 1024},
[&] () { return _child.pd().avail_ram(); }); [&]()
{ return _child.pd().avail_ram(); });
_apply_resource_downgrade(_resources.assigned_cap_quota, _apply_resource_downgrade(_resources.assigned_cap_quota,
configured_cap_quota, Cap_quota{5}, configured_cap_quota, Cap_quota{5},
[&] () { return _child.pd().avail_caps(); }); [&]()
{ return _child.pd().avail_caps(); });
/* /*
* If designated resource quota is lower than the child's consumed quota, * If designated resource quota is lower than the child's consumed quota,
@@ -341,21 +361,22 @@ void Sandbox::Child::apply_downgrade()
if (configured_cap_quota.value < _resources.assigned_cap_quota.value) if (configured_cap_quota.value < _resources.assigned_cap_quota.value)
demanded_cap_quota = _resources.assigned_cap_quota.value - configured_cap_quota.value; demanded_cap_quota = _resources.assigned_cap_quota.value - configured_cap_quota.value;
if (demanded_ram_quota || demanded_cap_quota) { if (demanded_ram_quota || demanded_cap_quota)
{
Parent::Resource_args const Parent::Resource_args const
args { "ram_quota=", Number_of_bytes(demanded_ram_quota), ", ", args{"ram_quota=", Number_of_bytes(demanded_ram_quota), ", ",
"cap_quota=", demanded_cap_quota}; "cap_quota=", demanded_cap_quota};
_child.yield(args); _child.yield(args);
} }
} }
void Sandbox::Child::report_state(Xml_generator &xml, Report_detail const &detail) const void Sandbox::Child::report_state(Xml_generator &xml, Report_detail const &detail) const
{ {
if (abandoned()) if (abandoned())
return; return;
xml.node("child", [&] () { xml.node("child", [&]()
{
xml.attribute("name", _unique_name); xml.attribute("name", _unique_name);
xml.attribute("binary", _binary_name); xml.attribute("binary", _binary_name);
@@ -422,11 +443,9 @@ void Sandbox::Child::report_state(Xml_generator &xml, Report_detail const &detai
_session_requester.id_space().for_each<Session_state const>(fn); _session_requester.id_space().for_each<Session_state const>(fn);
}); });
} } });
});
} }
Sandbox::Child::Sample_state_result Sandbox::Child::sample_state() Sandbox::Child::Sample_state_result Sandbox::Child::sample_state()
{ {
if (!_pd_alive()) if (!_pd_alive())
@@ -437,40 +456,42 @@ Sandbox::Child::Sample_state_result Sandbox::Child::sample_state()
_sampled_state = Sampled_state::from_pd(_child.pd()); _sampled_state = Sampled_state::from_pd(_child.pd());
return (orig_state != _sampled_state) ? Sample_state_result::CHANGED return (orig_state != _sampled_state) ? Sample_state_result::CHANGED
: Sample_state_result::UNCHANGED; : Sample_state_result::UNCHANGED;
} }
void Sandbox::Child::init(Pd_session &session, Pd_session_capability cap) void Sandbox::Child::init(Pd_session &session, Pd_session_capability cap)
{ {
size_t const initial_session_costs = size_t const initial_session_costs =
session_alloc_batch_size()*_child.session_factory().session_costs(); session_alloc_batch_size() * _child.session_factory().session_costs();
Ram_quota ram_quota { _resources.effective_ram_quota().value > initial_session_costs Ram_quota ram_quota{_resources.effective_ram_quota().value > initial_session_costs
? _resources.effective_ram_quota().value - initial_session_costs ? _resources.effective_ram_quota().value - initial_session_costs
: 0 }; : 0};
Ram_quota avail_ram = _ram_limit_accessor.resource_limit(Ram_quota()); Ram_quota avail_ram = _ram_limit_accessor.resource_limit(Ram_quota());
avail_ram = Genode::Child::effective_quota(avail_ram); avail_ram = Genode::Child::effective_quota(avail_ram);
if (ram_quota.value > avail_ram.value) { if (ram_quota.value > avail_ram.value)
{
warning(name(), ": configured RAM exceeds available RAM, proceed with ", avail_ram); warning(name(), ": configured RAM exceeds available RAM, proceed with ", avail_ram);
ram_quota = avail_ram; ram_quota = avail_ram;
} }
Cap_quota cap_quota { _resources.effective_cap_quota().value }; Cap_quota cap_quota{_resources.effective_cap_quota().value};
Cap_quota avail_caps = _cap_limit_accessor.resource_limit(avail_caps); Cap_quota avail_caps = _cap_limit_accessor.resource_limit(avail_caps);
avail_caps = Genode::Child::effective_quota(avail_caps); avail_caps = Genode::Child::effective_quota(avail_caps);
if (cap_quota.value > avail_caps.value) { if (cap_quota.value > avail_caps.value)
{
warning(name(), ": configured caps exceed available caps, proceed with ", avail_caps); warning(name(), ": configured caps exceed available caps, proceed with ", avail_caps);
cap_quota = avail_caps; cap_quota = avail_caps;
} }
_with_pd_intrinsics([&] (Pd_intrinsics::Intrinsics &intrinsics) { _with_pd_intrinsics([&](Pd_intrinsics::Intrinsics &intrinsics)
{
_ref_pd_cap = intrinsics.ref_pd_cap; _ref_pd_cap = intrinsics.ref_pd_cap;
@@ -482,42 +503,41 @@ void Sandbox::Child::init(Pd_session &session, Pd_session_capability cap)
try { intrinsics.ref_pd.transfer_quota(cap, ram_quota); } try { intrinsics.ref_pd.transfer_quota(cap, ram_quota); }
catch (Out_of_ram) { catch (Out_of_ram) {
error(name(), ": unable to initialize RAM quota of PD"); } error(name(), ": unable to initialize RAM quota of PD"); } });
});
} }
void Sandbox::Child::init(Cpu_session &session, Cpu_session_capability cap) void Sandbox::Child::init(Cpu_session &session, Cpu_session_capability cap)
{ {
Cpu_quota const assigned = _resources.assigned_cpu_quota; Cpu_quota const assigned = _resources.assigned_cpu_quota;
Cpu_quota const effective = _effective_cpu_quota; Cpu_quota const effective = _effective_cpu_quota;
if (assigned.percent > effective.percent) if (assigned.percent > effective.percent)
warning(name(), ": configured CPU quota of ", assigned, " exceeds " warning(name(), ": configured CPU quota of ", assigned, " exceeds "
"available quota, proceeding with a quota of ", effective); "available quota, proceeding with a quota of ",
effective);
_with_pd_intrinsics([&] (Pd_intrinsics::Intrinsics &intrinsics) { _with_pd_intrinsics([&](Pd_intrinsics::Intrinsics &intrinsics)
session.ref_account(intrinsics.ref_cpu_cap); }); { session.ref_account(intrinsics.ref_cpu_cap); });
_cpu_quota_transfer.transfer_cpu_quota(_child.pd_session_cap(), _child.pd(), _cpu_quota_transfer.transfer_cpu_quota(_child.pd_session_cap(), _child.pd(),
cap, effective); cap, effective);
} }
Sandbox::Child::Route Sandbox::Child::Route
Sandbox::Child::resolve_session_request(Service::Name const &service_name, Sandbox::Child::resolve_session_request(Service::Name const &service_name,
Session_label const &label, Session_label const &label,
Session::Diag const diag) Session::Diag const diag)
{ {
bool const rom_service = (service_name == Rom_session::service_name()); bool const rom_service = (service_name == Rom_session::service_name());
/* check for "config" ROM request */ /* check for "config" ROM request */
if (rom_service && label.last_element() == "config") { if (rom_service && label.last_element() == "config")
{
if (_config_rom_service.constructed() && if (_config_rom_service.constructed() &&
!_config_rom_service->abandoned()) !_config_rom_service->abandoned())
return Route { _config_rom_service->service(), label, return Route{_config_rom_service->service(), label,
Session::Diag{false} }; Session::Diag{false}};
/* /*
* If there is no inline '<config>', we apply the regular session * If there is no inline '<config>', we apply the regular session
@@ -543,9 +563,9 @@ Sandbox::Child::resolve_session_request(Service::Name const &service_name,
/* check for "session_requests" ROM request */ /* check for "session_requests" ROM request */
if (rom_service && 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 }; return Route{_session_requester.service(), Session::Label(), diag};
auto resolve_at_target = [&] (Xml_node const &target) -> Route auto resolve_at_target = [&](Xml_node const &target) -> Route
{ {
/* /*
* Determine session label to be provided to the server * Determine session label to be provided to the server
@@ -561,54 +581,77 @@ Sandbox::Child::resolve_session_request(Service::Name const &service_name,
target.attribute_value("label", Label(label.string())); target.attribute_value("label", Label(label.string()));
Session::Diag const Session::Diag const
target_diag { target.attribute_value("diag", diag.enabled) }; target_diag{target.attribute_value("diag", diag.enabled)};
auto no_filter = [] (Service &) -> bool { return false; }; auto no_filter = [](Service &) -> bool
{ return false; };
if (target.has_type("parent")) { if (target.has_type("parent"))
{
try { try
return Route { find_service(_parent_services, service_name, no_filter), {
target_label, target_diag }; return Route{find_service(_parent_services, service_name, no_filter),
} catch (Service_denied) { } target_label, target_diag};
}
catch (Service_denied)
{
}
} }
if (target.has_type("local")) { if (target.has_type("local"))
{
try { try
return Route { find_service(_local_services, service_name, no_filter), {
target_label, target_diag }; return Route{find_service(_local_services, service_name, no_filter),
} catch (Service_denied) { } target_label, target_diag};
}
catch (Service_denied)
{
}
} }
if (target.has_type("child")) { if (target.has_type("child"))
{
using Name = Name_registry::Name; using Name = Name_registry::Name;
Name server_name = target.attribute_value("name", Name()); Name server_name = target.attribute_value("name", Name());
server_name = _name_registry.deref_alias(server_name); server_name = _name_registry.deref_alias(server_name);
auto filter_server_name = [&] (Routed_service &s) -> bool { auto filter_server_name = [&](Routed_service &s) -> bool
return s.child_name() != server_name; }; { return s.child_name() != server_name; };
try { try
return Route { find_service(_child_services, service_name, {
filter_server_name), target_label, target_diag }; return Route{find_service(_child_services, service_name,
filter_server_name),
} catch (Service_denied) { } target_label, target_diag};
}
catch (Service_denied)
{
}
} }
if (target.has_type("any-child")) { if (target.has_type("any-child"))
{
if (is_ambiguous(_child_services, service_name)) { if (is_ambiguous(_child_services, service_name))
{
error(name(), ": ambiguous routes to " error(name(), ": ambiguous routes to "
"service \"", service_name, "\""); "service \"",
service_name, "\"");
throw Service_denied(); throw Service_denied();
} }
try { try
return Route { find_service(_child_services, service_name, {
no_filter), target_label, target_diag }; return Route{find_service(_child_services, service_name,
no_filter),
} catch (Service_denied) { } target_label, target_diag};
}
catch (Service_denied)
{
}
} }
throw Service_denied(); throw Service_denied();
@@ -619,16 +662,16 @@ Sandbox::Child::resolve_session_request(Service::Name const &service_name,
return _route_model->resolve(query, resolve_at_target); return _route_model->resolve(query, resolve_at_target);
} }
void Sandbox::Child::filter_session_args(Service::Name const &service, void Sandbox::Child::filter_session_args(Service::Name const &service,
char *args, size_t args_len) char *args, size_t args_len)
{ {
/* /*
* Intercept CPU session requests to scale priorities * Intercept CPU session requests to scale priorities
*/ */
if ((service == Cpu_session::service_name() || if ((service == Cpu_session::service_name() ||
service == Vm_session::service_name()) service == Vm_session::service_name()) &&
&& _prio_levels_log2 > 0) { _prio_levels_log2 > 0)
{
unsigned priority = (unsigned)Arg_string::find_arg(args, "priority").ulong_value(0); unsigned priority = (unsigned)Arg_string::find_arg(args, "priority").ulong_value(0);
@@ -642,11 +685,10 @@ void Sandbox::Child::filter_session_args(Service::Name const &service,
priority >>= _prio_levels_log2; priority >>= _prio_levels_log2;
/* assign child priority to the most significant priority bits */ /* assign child priority to the most significant priority bits */
priority = priority priority = priority | (unsigned)(_priority * (Cpu_session::PRIORITY_LIMIT >> _prio_levels_log2));
| (unsigned)(_priority*(Cpu_session::PRIORITY_LIMIT >> _prio_levels_log2));
/* override priority when delegating the session request to the parent */ /* override priority when delegating the session request to the parent */
String<64> value { Hex(priority) }; String<64> value{Hex(priority)};
Arg_string::set_arg(args, args_len, "priority", value.string()); Arg_string::set_arg(args, args_len, "priority", value.string());
} }
@@ -654,7 +696,8 @@ void Sandbox::Child::filter_session_args(Service::Name const &service,
* Unset the 'managing_system' argument unless explicitly permitted by the * Unset the 'managing_system' argument unless explicitly permitted by the
* child configuration. * child configuration.
*/ */
if (service == Pd_session::service_name()) { if (service == Pd_session::service_name())
{
/* /*
* For an environment PD session created by us for a direct child, the * For an environment PD session created by us for a direct child, the
@@ -680,53 +723,51 @@ void Sandbox::Child::filter_session_args(Service::Name const &service,
} }
} }
Genode::Affinity Sandbox::Child::filter_session_affinity(Affinity const &session_affinity) Genode::Affinity Sandbox::Child::filter_session_affinity(Affinity const &session_affinity)
{ {
Affinity::Space const &child_space = _resources.affinity.space(); Affinity::Space const &child_space = _resources.affinity.space();
Affinity::Location const &child_location = _resources.affinity.location(); Affinity::Location const &child_location = _resources.affinity.location();
/* check if no valid affinity space was specified */ /* check if no valid affinity space was specified */
if (session_affinity.space().total() == 0) if (session_affinity.space().total() == 0)
return Affinity(child_space, child_location); return Affinity(child_space, child_location);
Affinity::Space const &session_space = session_affinity.space(); Affinity::Space const &session_space = session_affinity.space();
Affinity::Location const &session_location = session_affinity.location(); Affinity::Location const &session_location = session_affinity.location();
/* scale resolution of resulting space */ /* scale resolution of resulting space */
Affinity::Space space(child_space.multiply(session_space)); Affinity::Space space(child_space.multiply(session_space));
Affinity::Location child_session(child_location.xpos(), child_location.ypos(), Affinity::Location child_session(child_location.xpos(), child_location.ypos(),
child_location.width() * session_location.width(), child_location.width() * session_location.width(),
child_location.height() * session_location.height()); child_location.height() * session_location.height());
/* subordinate session affinity to child affinity subspace */ /* subordinate session affinity to child affinity subspace */
Affinity::Location location(child_session Affinity::Location location(child_session
.multiply_position(session_space) .multiply_position(session_space)
.transpose(session_location.xpos() * child_location.width(), .transpose(session_location.xpos() * child_location.width(),
session_location.ypos() * child_location.height())); session_location.ypos() * child_location.height()));
return Affinity(space, location); return Affinity(space, location);
} }
void Sandbox::Child::announce_service(Service::Name const &service_name) void Sandbox::Child::announce_service(Service::Name const &service_name)
{ {
if (_verbose.enabled()) if (_verbose.enabled())
log("child \"", name(), "\" announces service \"", service_name, "\""); log("child \"", name(), "\" announces service \"", service_name, "\"");
bool found = false; bool found = false;
_child_services.for_each([&] (Routed_service &service) { _child_services.for_each([&](Routed_service &service)
{
if (service.has_id_space(_session_requester.id_space()) if (service.has_id_space(_session_requester.id_space())
&& service.name() == service_name) && service.name() == service_name)
found = true; }); found = true; });
if (!found) if (!found)
error(name(), ": illegal announcement of " error(name(), ": illegal announcement of "
"service \"", service_name, "\""); "service \"",
service_name, "\"");
} }
void Sandbox::Child::resource_request(Parent::Resource_args const &args) void Sandbox::Child::resource_request(Parent::Resource_args const &args)
{ {
log("child \"", name(), "\" requests resources: ", args); log("child \"", name(), "\" requests resources: ", args);
@@ -735,50 +776,48 @@ void Sandbox::Child::resource_request(Parent::Resource_args const &args)
_report_update_trigger.trigger_immediate_report_update(); _report_update_trigger.trigger_immediate_report_update();
} }
Sandbox::Child::Child(Env &env,
Sandbox::Child::Child(Env &env, Allocator &alloc,
Allocator &alloc, Verbose const &verbose,
Verbose const &verbose, Id id,
Id id, Report_update_trigger &report_update_trigger,
Report_update_trigger &report_update_trigger, Xml_node start_node,
Xml_node start_node, Default_route_accessor &default_route_accessor,
Default_route_accessor &default_route_accessor, Default_caps_accessor &default_caps_accessor,
Default_caps_accessor &default_caps_accessor, Name_registry &name_registry,
Name_registry &name_registry, Ram_limit_accessor &ram_limit_accessor,
Ram_limit_accessor &ram_limit_accessor, Cap_limit_accessor &cap_limit_accessor,
Cap_limit_accessor &cap_limit_accessor, Cpu_limit_accessor &cpu_limit_accessor,
Cpu_limit_accessor &cpu_limit_accessor, Cpu_quota_transfer &cpu_quota_transfer,
Cpu_quota_transfer &cpu_quota_transfer, Prio_levels prio_levels,
Prio_levels prio_levels, Affinity::Space const &affinity_space,
Affinity::Space const &affinity_space, Registry<Parent_service> &parent_services,
Affinity::Location const &location, Registry<Routed_service> &child_services,
Registry<Parent_service> &parent_services, Registry<Local_service> &local_services,
Registry<Routed_service> &child_services, Pd_intrinsics &pd_intrinsics)
Registry<Local_service> &local_services, : _env(env), _alloc(alloc), _verbose(verbose), _id(id),
Pd_intrinsics &pd_intrinsics) _report_update_trigger(report_update_trigger),
: _list_element(this),
_env(env), _alloc(alloc), _verbose(verbose), _id(id), _start_node(_alloc, start_node),
_report_update_trigger(report_update_trigger), _default_route_accessor(default_route_accessor),
_list_element(this), _default_caps_accessor(default_caps_accessor),
_start_node(_alloc, start_node), _ram_limit_accessor(ram_limit_accessor),
_default_route_accessor(default_route_accessor), _cap_limit_accessor(cap_limit_accessor),
_default_caps_accessor(default_caps_accessor), _cpu_limit_accessor(cpu_limit_accessor),
_ram_limit_accessor(ram_limit_accessor), _cpu_quota_transfer(cpu_quota_transfer),
_cap_limit_accessor(cap_limit_accessor), _name_registry(name_registry),
_cpu_limit_accessor(cpu_limit_accessor), _heartbeat_enabled(start_node.has_sub_node("heartbeat")),
_cpu_quota_transfer(cpu_quota_transfer), _resources(_resources_from_start_node(start_node, prio_levels, affinity_space,
_name_registry(name_registry), default_caps_accessor.default_caps())),
_heartbeat_enabled(start_node.has_sub_node("heartbeat")), _pd_intrinsics(pd_intrinsics),
_resources(_resources_from_start_node(start_node, prio_levels, affinity_space, location, _parent_services(parent_services),
default_caps_accessor.default_caps())), _child_services(child_services),
_pd_intrinsics(pd_intrinsics), _local_services(local_services),
_parent_services(parent_services), _session_requester(_env.ep().rpc_ep(), _env.ram(), _env.rm())
_child_services(child_services),
_local_services(local_services),
_session_requester(_env.ep().rpc_ep(), _env.ram(), _env.rm())
{ {
if (_verbose.enabled()) { if (_verbose.enabled())
log("child \"", _unique_name, "\""); {
log("child \"", _unique_name, "\"");
log(" RAM quota: ", _resources.effective_ram_quota()); log(" RAM quota: ", _resources.effective_ram_quota());
log(" cap quota: ", _resources.effective_cap_quota()); log(" cap quota: ", _resources.effective_cap_quota());
log(" ELF binary: ", _binary_name); log(" ELF binary: ", _binary_name);
@@ -792,7 +831,8 @@ Sandbox::Child::Child(Env &env,
*/ */
_provides_sub_node(start_node) _provides_sub_node(start_node)
.for_each_sub_node("service", .for_each_sub_node("service",
[&] (Xml_node node) { _add_service(node); }); [&](Xml_node node)
{ _add_service(node); });
/* /*
* Construct inline config ROM service if "config" node is present. * Construct inline config ROM service if "config" node is present.
@@ -801,5 +841,4 @@ Sandbox::Child::Child(Env &env,
_config_rom_service.construct(*this); _config_rom_service.construct(*this);
} }
Sandbox::Child::~Child() {}
Sandbox::Child::~Child() { }

View File

@@ -385,7 +385,7 @@ void Genode::Sandbox::generate_state_report(Xml_generator &xml) const
Genode::Sandbox::Sandbox(Env &env, State_handler &state_handler, Pd_intrinsics &pd_intrinsics) Genode::Sandbox::Sandbox(Env &env, State_handler &state_handler, Pd_intrinsics &pd_intrinsics)
: :
_heap(env.ram(), env.rm()), _heap(env.ram(), env.rm()),
_library(*new (_heap) Library(env, _heap, _local_services, state_handler, pd_intrinsics)) _library(*new (_heap) ::Sandbox::Library(env, _heap, _local_services, state_handler, pd_intrinsics))
{ } { }