/* * \brief Child creation framework * \author Norman Feske * \date 2006-07-22 */ /* * Copyright (C) 2006-2012 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ #ifndef _INCLUDE__BASE__CHILD_H_ #define _INCLUDE__BASE__CHILD_H_ #include #include #include #include #include #include #include namespace Genode { /** * Child policy interface * * A child-policy object is an argument to a 'Child'. It is responsible for * taking policy decisions regarding the parent interface. Most importantly, * it defines how session requests are resolved and how session arguments * are passed to servers when creating sessions. */ struct Child_policy { virtual ~Child_policy() { } /** * Return process name of the child */ virtual const char *name() const = 0; /** * Determine service to provide a session request * * \return Service to be contacted for the new session, or * 0 if session request could not be resolved */ virtual Service *resolve_session_request(const char *service_name, const char *args) { return 0; } /** * Apply transformations to session arguments */ virtual void filter_session_args(const char *service, char *args, size_t args_len) { } /** * Register a service provided by the child * * \param name service name * \param root interface for creating sessions for the service * \param alloc allocator to be used for child-specific * meta-data allocations * \return true if announcement succeeded, or false if * child is not permitted to announce service */ virtual bool announce_service(const char *name, Root_capability root, Allocator *alloc, Server *server) { return false; } /** * Unregister services that had been provided by the child */ virtual void unregister_services() { } /** * Exit child */ virtual void exit(int exit_value) { PDBG("child exited with exit value %d", exit_value); } /** * Reference RAM session * * The RAM session returned by this function is used for session-quota * transfers. */ virtual Ram_session *ref_ram_session() { return env()->ram_session(); } }; /** * Implementation of the parent interface that supports resource trading * * There are three possible cases of how a session can be provided to * a child: * * # The service is implemented locally * # The session was obtained by asking our parent * # The session is provided by one of our children * * These types must be differentiated for the quota management when a child * issues the closing of a session or a transfers quota via our parent * interface. * * If we close a session to a local service, we transfer the session quota * from our own account to the client. * * If we close a parent session, we receive the session quota on our own * account and must transfer this amount to the session-closing child. * * If we close a session provided by a server child, we close the session * at the server, transfer the session quota from the server's ram session * to our account, and subsequently transfer the same amount from our * account to the client. */ class Child : protected Rpc_object { private: /** * Representation of an open session */ class Session : public Object_pool::Entry, public List::Element { private: enum { IDENT_LEN = 16 }; /** * Session capability at the server */ Session_capability _cap; /** * Service interface that was used to create the session */ Service *_service; /** * Server implementing the session * * Even though we can normally determine the server of the * session via '_service->server()', this does not apply * when destructing a server. During destruction, we use * the 'Server' pointer as opaque key for revoking active * sessions of the server. So we keep a copy independent of * the 'Service' object. */ Server *_server; /** * Total of quota associated with this session */ size_t _donated_ram_quota; /** * Name of session, used for debugging */ char _ident[IDENT_LEN]; public: /** * Constructor * * \param session session capability * \param service service that implements the session * \param ram_quota initial quota donation associated with * the session * \param ident optional session identifier, used for * debugging */ Session(Session_capability session, Service *service, size_t ram_quota, const char *ident = "") : Object_pool::Entry(session), _cap(session), _service(service), _server(service->server()), _donated_ram_quota(ram_quota) { strncpy(_ident, ident, sizeof(_ident)); } /** * Default constructor creates invalid session */ Session() : _service(0), _donated_ram_quota(0) { } /** * Extend amount of ram attached to the session */ void upgrade_ram_quota(size_t ram_quota) { _donated_ram_quota += ram_quota; } /** * Accessors */ Session_capability cap() const { return _cap; } size_t donated_ram_quota() const { return _donated_ram_quota; } bool valid() const { return _service != 0; } Service *service() const { return _service; } Server *server() const { return _server; } const char *ident() const { return _ident; } }; /** * Guard for transferring quota donation * * This class is used to provide transactional semantics of quota * transfers. Establishing a new session involves several steps, in * particular subsequent quota transfers. If one intermediate step * fails, we need to revert all quota transfers that already took * place. When instantated at a local scope, a 'Transfer' object * guards a quota transfer. If the scope is left without prior an * explicit acknowledgement of the transfer (for example via an * exception), the destructor the 'Transfer' object reverts the * transfer in flight. */ class Transfer { bool _ack; size_t _quantum; Ram_session_capability _from; Ram_session_capability _to; public: /** * Constructor * * \param quantim number of bytes to transfer * \param from donator RAM session * \param to receiver RAM session */ Transfer(size_t quantum, Ram_session_capability from, Ram_session_capability to) : _ack(false), _quantum(quantum), _from(from), _to(to) { if (_from.valid() && _to.valid() && Ram_session_client(_from).transfer_quota(_to, quantum)) { PWRN("not enough quota for a donation of %zd bytes", quantum); throw Quota_exceeded(); } } /** * Destructor * * The destructor will be called when leaving the scope of * the 'session' function. If the scope is left because of * an error (e.g., an exception), the donation will be * reverted. */ ~Transfer() { if (!_ack && _from.valid() && _to.valid()) Ram_session_client(_to).transfer_quota(_from, _quantum); } /** * Acknowledge quota donation */ void acknowledge() { _ack = true; } }; /* RAM session that contains the quota of the child */ Ram_session_capability _ram; Ram_session_client _ram_session_client; /* CPU session that contains the quota of the child */ Cpu_session_capability _cpu; /* RM session representing the address space of the child */ Rm_session_capability _rm; /* heap for child-specific allocations using the child's quota */ Heap _heap; Rpc_entrypoint *_entrypoint; Parent_capability _parent_cap; Process _process; /* sessions opened by the child */ Lock _lock; /* protect list manipulation */ Object_pool _session_pool; List _session_list; /* child policy */ Child_policy *_policy; /* server role */ Server _server; /** * Session-argument buffer */ char _args[Parent::Session_args::MAX_SIZE]; /** * Attach session information to a child * * \throw Ram_session::Quota_exceeded the child's heap partition cannot * hold the session meta data */ void _add_session(const Session &s) { Lock::Guard lock_guard(_lock); /* * Store session information in a new child's meta data * structure. The allocation from 'heap()' may throw a * 'Ram_session::Quota_exceeded' exception. */ Session *session = 0; try { session = new (heap()) Session(s.cap(), s.service(), s.donated_ram_quota(), s.ident()); } catch (Allocator::Out_of_memory) { throw Parent::Quota_exceeded(); } /* these functions may also throw 'Ram_session::Quota_exceeded' */ _session_pool.insert(session); _session_list.insert(session); } /** * Close session and revert quota donation associated with it */ void _remove_session(Session *s) { Lock::Guard lock_guard(_lock); /* forget about this session */ _session_pool.remove(s); _session_list.remove(s); /* return session quota to the ram session of the child */ if (_policy->ref_ram_session()->transfer_quota(_ram, s->donated_ram_quota())) PERR("We ran out of our own quota"); destroy(heap(), s); } public: /** * Constructor * * \param elf_ds dataspace containing the binary * \param ram RAM session with the child's quota * \param cpu CPU session with the child's quota * \param entrypoint server entrypoint to serve the parent interface * \param policy child policy * * If assigning a separate entry point to each child, the host of * multiple children is able to handle a blocking invocation of * the parent interface of one child while still maintaining the * service to other children, each having an independent entry * point. */ Child(Dataspace_capability elf_ds, Ram_session_capability ram, Cpu_session_capability cpu, Rm_session_capability rm, Rpc_entrypoint *entrypoint, Child_policy *policy) : _ram(ram), _ram_session_client(ram), _cpu(cpu), _rm(rm), _heap(&_ram_session_client, env()->rm_session()), _entrypoint(entrypoint), _parent_cap(_entrypoint->manage(this)), _process(elf_ds, ram, cpu, rm, _parent_cap, policy->name(), 0), _policy(policy), _server(ram) { } /** * Destructor * * On destruction of a child, we close all sessions of the child to * other services. */ virtual ~Child() { _entrypoint->dissolve(this); _policy->unregister_services(); for (Session *s; (s = _session_pool.first()); ) close(s->cap()); } /** * Return heap that uses the child's quota */ Allocator *heap() { return &_heap; } Ram_session_capability ram_session_cap() const { return _ram; } Cpu_session_capability cpu_session_cap() const { return _cpu; } Rm_session_capability rm_session_cap() const { return _rm; } Parent_capability parent_cap() const { return cap(); } /** * Discard all sessions to specified service * * When this function is called, we assume the server protection * domain to be dead and all that all server quota was already * transferred back to our own 'env()->ram_session()' account. Note * that the specified server object may not exist anymore. We do * not de-reference the server argument in here! */ void revoke_server(const Server *server) { while (1) { /* search session belonging to the specified server */ Session *s = _session_list.first(); for ( ; s && (s->server() != server); s = s->next()); /* if no matching session exists, we are done */ if (!s) return; _remove_session(s); } } /********************** ** Parent interface ** **********************/ void announce(Service_name const &name, Root_capability root) { if (!name.is_valid_string()) return; _policy->announce_service(name.string(), root, heap(), &_server); } Session_capability session(Service_name const &name, Session_args const &args) { if (!name.is_valid_string() || !args.is_valid_string()) throw Unavailable(); /* return sessions that we created for the child */ if (!strcmp("Env::ram_session", name.string())) return _ram; if (!strcmp("Env::cpu_session", name.string())) return _cpu; if (!strcmp("Env::rm_session", name.string())) return _rm; if (!strcmp("Env::pd_session", name.string())) return _process.pd_session_cap(); /* filter session arguments according to the child policy */ strncpy(_args, args.string(), sizeof(_args)); _policy->filter_session_args(name.string(), _args, sizeof(_args)); /* transfer the quota donation from the child's account to ourself */ size_t ram_quota = Arg_string::find_arg(_args, "ram_quota").long_value(0); Transfer donation_from_child(ram_quota, _ram, env()->ram_session_cap()); Service *service = _policy->resolve_session_request(name.string(), _args); /* raise an error if no matching service provider could be found */ if (!service) throw Service_denied(); /* transfer session quota from ourself to the service provider */ Transfer donation_to_service(ram_quota, env()->ram_session_cap(), service->ram_session_cap()); /* create session */ Session_capability cap; try { cap = service->session(_args); } catch (Service::Invalid_args) { throw Service_denied(); } catch (Service::Unavailable) { throw Service_denied(); } catch (Service::Quota_exceeded) { throw Quota_exceeded(); } /* register session */ try { _add_session(Session(cap, service, ram_quota, name.string())); } catch (Ram_session::Quota_exceeded) { throw Quota_exceeded(); } /* finish transaction */ donation_from_child.acknowledge(); donation_to_service.acknowledge(); return cap; } void upgrade(Session_capability to_session, Upgrade_args const &args) { Session *s = _session_pool.obj_by_cap(to_session); if (!s) { PWRN("no session structure found - nothing to be done\n"); return; } if (!args.is_valid_string()) { PWRN("no valid session-upgrade arguments"); return; } size_t ram_quota = Arg_string::find_arg(args.string(), "ram_quota").ulong_value(0); /* transfer quota from client to ourself */ Transfer donation_from_child(ram_quota, _ram, env()->ram_session_cap()); /* transfer session quota from ourself to the service provider */ Transfer donation_to_service(ram_quota, env()->ram_session_cap(), s->service()->ram_session_cap()); try { s->service()->upgrade(to_session, args.string()); } catch (Service::Quota_exceeded) { throw Quota_exceeded(); } /* remember new amount attached to the session */ s->upgrade_ram_quota(ram_quota); /* finish transaction */ donation_from_child.acknowledge(); donation_to_service.acknowledge(); } void close(Session_capability session_cap) { /* refuse to close the child's initial sessions */ if (session_cap.local_name() == _ram.local_name() || session_cap.local_name() == _cpu.local_name() || session_cap.local_name() == _rm.local_name() || session_cap.local_name() == _process.pd_session_cap().local_name()) return; Session *s = _session_pool.obj_by_cap(session_cap); if (!s) { PWRN("no session structure found"); return; } /* * There is a chance that the server is not responding to * the 'close' call, making us block infinitely. However, * by using core's cancel-blocking mechanism, we can cancel * the 'close' call by another (watchdog) thread that * invokes 'cancel_blocking' at our thread after a timeout. * The unblocking is reflected at the API level as an * 'Blocking_canceled' exception. We catch this exception * to proceed with normal operation after being unblocked. */ try { s->service()->close(s->cap()); } catch (Blocking_canceled) { PDBG("Got Blocking_canceled exception during %s->close call\n", s->ident()); } /* * If the session was provided by a child of us, * 'server()->ram_session_cap()' returns the RAM session of the * corresponding child. Since the session to the server is * closed now, we expect that the server released all donated * resources and we can decrease the servers' quota. * * If this goes wrong, the server is misbehaving. */ if (s->service()->ram_session_cap().valid()) { Ram_session_client server_ram(s->service()->ram_session_cap()); if (server_ram.transfer_quota(env()->ram_session_cap(), s->donated_ram_quota())) { PERR("Misbehaving server '%s'!", s->service()->name()); } } _remove_session(s); } void exit(int exit_value) { /* * This function receives the hint from the child that now, its * a good time to kill it. An inherited child class could use * this hint to schedule the destruction of the child object. * * Note that the child object must not be destructed from by * this function because it is executed by the thread contained * in the child object. */ return _policy->exit(exit_value); } }; } #endif /* _INCLUDE__BASE__CHILD_H_ */