/*
 * \brief  Child representation
 * \author Norman Feske
 * \date   2010-05-04
 */

/*
 * Copyright (C) 2010-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__CHILD_H_
#define _SRC__INIT__CHILD_H_

/* Genode includes */
#include <base/log.h>
#include <base/child.h>
#include <os/session_requester.h>
#include <os/session_policy.h>
#include <os/buffered_xml.h>

/* local includes */
#include <types.h>
#include <verbose.h>
#include <report.h>
#include <name_registry.h>
#include <service.h>
#include <utils.h>

namespace Init { class Child; }

class Init::Child : Child_policy, Routed_service::Wakeup
{
	public:

		typedef String<80> Version;

		/**
		 * Exception types
		 */
		struct Child_name_is_not_unique : Exception { };
		struct Missing_name_attribute   : Exception { };

		/**
		 * Unique ID of the child, solely used for diagnostic purposes
		 */
		struct Id { unsigned value; };

		struct Default_route_accessor : Interface { virtual Xml_node default_route() = 0; };
		struct Default_caps_accessor  : Interface { virtual Cap_quota default_caps() = 0; };

		template <typename QUOTA>
		struct Resource_limit_accessor : Interface
		{
			/*
			 * The argument is unused. It exists solely as an overload selector.
			 */
			virtual QUOTA resource_limit(QUOTA const &) const = 0;
		};

		typedef Resource_limit_accessor<Ram_quota> Ram_limit_accessor;
		typedef Resource_limit_accessor<Cap_quota> Cap_limit_accessor;

	private:

		friend class Child_registry;

		Env &_env;

		Allocator &_alloc;

		Verbose const &_verbose;

		Id const _id;

		enum State { STATE_INITIAL, STATE_RAM_INITIALIZED, STATE_ALIVE,
		             STATE_ABANDONED };

		State _state = STATE_INITIAL;

		Report_update_trigger &_report_update_trigger;

		List_element<Child> _list_element;

		Reconstructible<Buffered_xml> _start_node;

		/*
		 * Version attribute of the start node, used to force child restarts.
		 */
		Version _version { _start_node->xml().attribute_value("version", Version()) };

		/*
		 * True if the binary is loaded with ld.lib.so
		 */
		bool const _use_ld = _start_node->xml().attribute_value("ld", true);

		Default_route_accessor &_default_route_accessor;
		Default_caps_accessor  &_default_caps_accessor;
		Ram_limit_accessor     &_ram_limit_accessor;
		Cap_limit_accessor     &_cap_limit_accessor;

		Name_registry &_name_registry;

		/**
		 * Read name from XML and check for name confict with other children
		 *
		 * \throw Missing_name_attribute
		 */
		static Name _name_from_xml(Xml_node start_node)
		{
			Name const name = start_node.attribute_value("name", Name());
			if (name.valid())
				return name;

			warning("missint 'name' attribute in '<start>' entry");
			throw Missing_name_attribute();
		}

		typedef String<64> Name;
		Name const _unique_name { _name_from_xml(_start_node->xml()) };

		static Binary_name _binary_from_xml(Xml_node start_node,
		                                    Name const &unique_name)
		{
			if (!start_node.has_sub_node("binary"))
				return unique_name;

			return start_node.sub_node("binary").attribute_value("name", Name());
		}

		/* updated on configuration update */
		Binary_name _binary_name { _binary_from_xml(_start_node->xml(), _unique_name) };

		/* initialized in constructor, updated by 'apply_config' */
		bool _heartbeat_enabled;

		/*
		 * Number of skipped heartbeats when last checked
		 *
		 * This variable is used for the triggering of state-report updates
		 * due to heartbeat events.
		 */
		unsigned _last_skipped_heartbeats = 0;

		/* return true if heartbeat tracking is active */
		bool _heartbeat_expected() const
		{
			/* don't expect heartbeats from a child that is not yet complete */
			return _heartbeat_enabled && (_state == STATE_ALIVE);
		}

		/**
		 * Resources assigned to the child
		 */
		struct Resources
		{
			long      prio_levels_log2;
			long      priority;
			Affinity  affinity;
			Ram_quota assigned_ram_quota;
			Cap_quota assigned_cap_quota;
			size_t    cpu_quota_pc;
			bool      constrain_phys;

			Ram_quota effective_ram_quota() const
			{
				return Genode::Child::effective_quota(assigned_ram_quota);
			}

			Cap_quota effective_cap_quota() const
			{
				/* capabilities consumed by 'Genode::Child' */
				Cap_quota const effective =
					Genode::Child::effective_quota(assigned_cap_quota);

				/* capabilities additionally consumed by init */
				enum {
					STATIC_COSTS = 1  /* possible heap backing-store
					                     allocation for session object */
					             + 1  /* buffered XML start node */
					             + 2  /* dynamic ROM for config */
					             + 2  /* dynamic ROM for session requester */
				};

				if (effective.value < STATIC_COSTS)
					return Cap_quota{0};

				return Cap_quota{effective.value - STATIC_COSTS};
			}
		};

		Resources _resources_from_start_node(Xml_node start_node, Prio_levels prio_levels,
		                                     Affinity::Space const &affinity_space,
		                                     Cap_quota default_cap_quota, Cap_quota)
		{
			size_t          cpu_quota_pc   = 0;
			bool            constrain_phys = false;
			Number_of_bytes ram_bytes      = 0;

			size_t caps = start_node.attribute_value("caps", default_cap_quota.value);

			start_node.for_each_sub_node("resource", [&] (Xml_node rsc) {

				typedef String<8> Name;
				Name const name = rsc.attribute_value("name", Name());

				if (name == "RAM") {
					ram_bytes      = rsc.attribute_value("quantum", ram_bytes);
					constrain_phys = rsc.attribute_value("constrain_phys", false);
				}

				if (name == "CPU") {
					cpu_quota_pc = rsc.attribute_value("quantum", 0UL);
				}

				if (name == "CAP") {
					caps = rsc.attribute_value("quantum", 0UL);
				}
			});

			return Resources { log2(prio_levels.value),
			                   priority_from_xml(start_node, prio_levels),
			                   Affinity(affinity_space,
			                            affinity_location_from_xml(affinity_space, start_node)),
			                   Ram_quota { ram_bytes },
			                   Cap_quota { caps },
			                   cpu_quota_pc,
			                   constrain_phys };
		}

		Resources _resources;

		/**
		 * Print diagnostic information on misconfiguration
		 */
		void _clamp_resources(Ram_quota ram_limit, Cap_quota cap_limit)
		{
			if (_resources.assigned_ram_quota.value > ram_limit.value) {
				warning(name(), " assigned RAM (", _resources.assigned_ram_quota, ") "
				        "exceeds available RAM (", ram_limit, ")");
				_resources.assigned_ram_quota = ram_limit;
			}

			if (_resources.assigned_cap_quota.value > cap_limit.value) {
				warning(name(), " assigned caps (", _resources.assigned_cap_quota, ") "
				        "exceed available caps (", cap_limit, ")");
				_resources.assigned_cap_quota = cap_limit;
			}
		}

		Ram_quota _configured_ram_quota() const;
		Cap_quota _configured_cap_quota() const;

		bool const _resources_clamped_to_limit;

		Registry<Parent_service> &_parent_services;
		Registry<Routed_service> &_child_services;

		struct Inline_config_rom_service : Abandonable, Dynamic_rom_session::Content_producer
		{
			typedef Local_service<Dynamic_rom_session> Service;

			Child &_child;

			Dynamic_rom_session _session { _child._env.ep().rpc_ep(),
			                               _child.ref_pd(), _child._env.rm(),
			                               *this };

			Service::Single_session_factory _factory { _session };
			Service                         _service { _factory };

			Inline_config_rom_service(Child &child) : _child(child) { }

			/**
			 * Dynamic_rom_session::Content_producer interface
			 */
			void produce_content(char *dst, Genode::size_t dst_len) override
			{
				Xml_node config = _child._start_node->xml().has_sub_node("config")
				                ? _child._start_node->xml().sub_node("config")
				                : Xml_node("<config/>");

				size_t const config_len = config.size();

				if (config_len + 1 /* null termination */ >= dst_len)
					throw Buffer_capacity_exceeded();

				config.with_raw_node([&] (char const *start, size_t length) {

					/*
					 * The 'length' is the number of bytes of the config-node
					 * content, which is not null-terminated. Since
					 * 'Genode::strncpy' always null-terminates the result, the
					 * last byte of the source string is not copied. Hence, it
					 * is safe to add '1' to 'length' and thereby include the
					 * last actual config-content character in the result.
					 */
					Genode::strncpy(dst, start, length + 1);
				});
			}

			void trigger_update() { _session.trigger_update(); }

			Service &service() { return _service; }
		};

		Constructible<Inline_config_rom_service> _config_rom_service { };

		Session_requester _session_requester;

		/**
		 * CPU-session priority parameters
		 */
		long const _prio_levels_log2 { _resources.prio_levels_log2 };
		long const _priority         { _resources.priority };

		/**
		 * If set to true, the child is allowed to constrain physical RAM
		 * allocations.
		 */
		bool const _constrain_phys { _resources.constrain_phys };

		/**
		 * Resource request initiated by the child
		 */
		struct Requested_resources
		{
			Ram_quota const ram;
			Cap_quota const caps;

			Requested_resources(Parent::Resource_args const &args)
			:
				ram (ram_quota_from_args(args.string())),
				caps(cap_quota_from_args(args.string()))
			{ }
		};

		Constructible<Requested_resources> _requested_resources { };

		Genode::Child _child { _env.rm(), _env.ep().rpc_ep(), *this };

		struct Pd_accessor : Routed_service::Pd_accessor
		{
			Genode::Child &_child;

			Pd_accessor(Genode::Child &child) : _child(child) { }

			Pd_session           &pd()            override { return _child.pd(); }
			Pd_session_capability pd_cap()  const override { return _child.pd_session_cap(); }

		} _pd_accessor { _child };

		struct Ram_accessor : Routed_service::Ram_accessor
		{
			Genode::Child &_child;

			Ram_accessor(Genode::Child &child) : _child(child) { }

			Pd_session           &ram()            override { return _child.pd(); }
			Pd_session_capability ram_cap()  const override { return _child.pd_session_cap(); }

		} _ram_accessor { _child };

		/**
		 * Async_service::Wakeup callback
		 */
		void wakeup_async_service() override
		{
			_session_requester.trigger_update();
		}

		/**
		 * Return true if the policy results in the current route of the session
		 *
		 * This method is used to check if a policy change affects an existing
		 * client session of a child, i.e., to determine whether the child must
		 * be restarted.
		 */
		bool _route_valid(Session_state const &session)
		{
			try {
				Route const route =
					resolve_session_request(session.service().name(),
					                        session.client_label());

				return (session.service() == route.service)
				    && (route.label == session.label());
			}
			catch (Service_denied) { return false; }
		}

		static Xml_node _provides_sub_node(Xml_node start_node)
		{
			return start_node.has_sub_node("provides")
			     ? start_node.sub_node("provides") : Xml_node("<provides/>");
		}

		/**
		 * Return true if service is provided by this child
		 */
		bool _provided_by_this(Routed_service const &service) const
		{
			return service.has_id_space(_session_requester.id_space());
		}

		/**
		 * Return true if service of specified <provides> sub node is known
		 */
		bool _service_exists(Xml_node node) const
		{
			bool exists = false;
			_child_services.for_each([&] (Routed_service const &service) {
				if (_provided_by_this(service) &&
				    service.name() == node.attribute_value("name", Service::Name()))
					exists = true; });

			return exists && !abandoned();
		}

		void _add_service(Xml_node service)
		{
			Service::Name const name =
				service.attribute_value("name", Service::Name());

			if (_verbose.enabled())
				log("  provides service ", name);

			new (_alloc)
				Routed_service(_child_services, this->name(),
				               _pd_accessor, _ram_accessor,
				               _session_requester.id_space(),
				               _child.session_factory(),
				               name, *this);
		}

		/*
		 * Exit state of the child set when 'exit()' is executed
		 * and reported afterwards through the state report.
		 */

		bool _exited     { false };
		int  _exit_value { -1 };

		void _destroy_services();

	public:

		/**
		 * Constructor
		 *
		 * \param alloc               allocator solely used for configuration-
		 *                            dependent allocations. It is not used for
		 *                            allocations on behalf of the child's
		 *                            behavior.
		 *
		 * \param ram_limit           maximum amount of RAM to be consumed at
		 *                            creation time.
		 *
		 * \param ram_limit_accessor  interface for querying the available
		 *                            RAM, used for dynamic RAM balancing at
		 *                            runtime.
		 *
		 * \throw Allocator::Out_of_memory  could not buffer the XML start node
		 */
		Child(Env                      &env,
		      Allocator                &alloc,
		      Verbose            const &verbose,
		      Id                        id,
		      Report_update_trigger    &report_update_trigger,
		      Xml_node                  start_node,
		      Default_route_accessor   &default_route_accessor,
		      Default_caps_accessor    &default_caps_accessor,
		      Name_registry            &name_registry,
		      Ram_quota                 ram_limit,
		      Cap_quota                 cap_limit,
		      Ram_limit_accessor       &ram_limit_accessor,
		      Cap_limit_accessor       &cap_limit_accessor,
		      Prio_levels               prio_levels,
		      Affinity::Space const    &affinity_space,
		      Registry<Parent_service> &parent_services,
		      Registry<Routed_service> &child_services);

		virtual ~Child();

		/**
		 * Return true if the child has the specified name
		 */
		bool has_name(Child_policy::Name const &str) const { return str == name(); }

		bool has_version(Version const &version) const { return version == _version; }

		Ram_quota ram_quota() const { return _resources.assigned_ram_quota; }
		Cap_quota cap_quota() const { return _resources.assigned_cap_quota; }

		void initiate_env_pd_session()
		{
			if (_state == STATE_INITIAL) {
				_child.initiate_env_pd_session();
				_state = STATE_RAM_INITIALIZED;
			}
		}

		void initiate_env_sessions()
		{
			if (_state == STATE_RAM_INITIALIZED) {

				_child.initiate_env_sessions();

				/* check for completeness of the child's environment */
				if (_verbose.enabled())
					_child.for_each_session([&] (Session_state const &session) {
						if (!session.alive())
							warning(name(), ": incomplete environment ",
							        session.service().name(), " session "
							        "(", session.label(), ")"); });

				_state = STATE_ALIVE;
			}
		}

		void abandon()
		{
			_state = STATE_ABANDONED;

			_child_services.for_each([&] (Routed_service &service) {
				if (service.has_id_space(_session_requester.id_space()))
					service.abandon(); });
		}

		void destroy_services();

		void close_all_sessions() { _child.close_all_sessions(); }

		bool abandoned() const { return _state == STATE_ABANDONED; }

		bool env_sessions_closed() const { return _child.env_sessions_closed(); }

		enum Apply_config_result { MAY_HAVE_SIDE_EFFECTS, NO_SIDE_EFFECTS };

		/**
		 * Apply new configuration to child
		 *
		 * \throw Allocator::Out_of_memory  unable to allocate buffer for new
		 *                                  config
		 */
		Apply_config_result apply_config(Xml_node start_node);

		/* common code for upgrading RAM and caps */
		template <typename QUOTA, typename LIMIT_ACCESSOR>
		void _apply_resource_upgrade(QUOTA &, QUOTA, LIMIT_ACCESSOR const &);

		template <typename QUOTA, typename CHILD_AVAIL_QUOTA_FN>
		void _apply_resource_downgrade(QUOTA &, QUOTA, QUOTA,
                                       CHILD_AVAIL_QUOTA_FN const &);

		void apply_upgrade();
		void apply_downgrade();

		void heartbeat()
		{
			if (_heartbeat_expected())
				_child.heartbeat();

			unsigned const skipped_heartbeats = _child.skipped_heartbeats();

			if (_last_skipped_heartbeats != skipped_heartbeats)
				_report_update_trigger.trigger_report_update();

			_last_skipped_heartbeats = skipped_heartbeats;

		}

		unsigned skipped_heartbeats() const
		{
			return _heartbeat_expected() ? _child.skipped_heartbeats() : 0;
		}

		void report_state(Xml_generator &xml, Report_detail const &detail) const;


		/****************************
		 ** Child-policy interface **
		 ****************************/

		Child_policy::Name name() const override { return _unique_name; }

		Pd_session           &ref_pd()           override { return _env.pd(); }
		Pd_session_capability ref_pd_cap() const override { return _env.pd_session_cap(); }

		void init(Pd_session  &, Pd_session_capability)  override;
		void init(Cpu_session &, Cpu_session_capability) override;

		Id_space<Parent::Server> &server_id_space() override {
			return _session_requester.id_space(); }

		Route resolve_session_request(Service::Name const &,
		                              Session_label const &) override;

		void     filter_session_args(Service::Name const &, char *, size_t) override;
		Affinity filter_session_affinity(Affinity const &) override;
		void     announce_service(Service::Name const &) override;
		void     resource_request(Parent::Resource_args const &) override;

		void exit(int exit_value) override
		{
			try {
				if (_start_node->xml().sub_node("exit").attribute_value("propagate", false)) {
					_env.parent().exit(exit_value);
					return;
				}
			} catch (...) { }

			/*
			 * Trigger a new report for exited children so that any management
			 * component may react upon it.
			 */
			_exited     = true;
			_exit_value = exit_value;

			_child.close_all_sessions();

			_report_update_trigger.trigger_report_update();

			/*
			 * Print a message as the exit is not handled otherwise. There are
			 * a number of automated tests that rely on this message. It is
			 * printed by the default implementation of 'Child_policy::exit'.
			 */
			Child_policy::exit(exit_value);
		}

		void session_state_changed() override
		{
			_report_update_trigger.trigger_report_update();
		}

		bool initiate_env_sessions() const override { return false; }

		void yield_response() override
		{
			apply_downgrade();
			_report_update_trigger.trigger_report_update();
		}
};

#endif /* _SRC__INIT__CHILD_H_ */
