/*
 * \brief  Fork bomb to stress Genode
 * \author Christian Helmuth
 * \author Norman Feske
 * \author Alexander Böttcher
 * \date   2007-08-16
 */

/*
 * Copyright (C) 2007-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.
 */

#include <base/component.h>
#include <base/child.h>
#include <base/service.h>
#include <base/attached_rom_dataspace.h>
#include <init/child_policy.h>
#include <timer_session/connection.h>
#include <os/child_policy_dynamic_rom.h>
#include <os/static_parent_services.h>

using namespace Genode;


class Bomb_child : public Child_policy
{
	private:

		Env              &_env;
		Binary_name const _binary_name;
		Name        const _label;
		Cap_quota   const _cap_quota;
		Ram_quota   const _ram_quota;

		Registry<Registered<Parent_service> > &_parent_services;

		Child_policy_dynamic_rom_file _config_policy { _env.rm(), "config", _env.ep().rpc_ep(), &_env.ram() };

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

	public:

		Bomb_child(Env                                   &env,
		           Name                            const &binary_name,
		           Name                            const &label,
		           Cap_quota                       const  cap_quota,
		           Ram_quota                       const  ram_quota,
		           Registry<Registered<Parent_service> > &parent_services,
		           unsigned                               generation)
		:
			_env(env), _binary_name(binary_name), _label(label),
			_cap_quota(Child::effective_quota(cap_quota)),
			_ram_quota(Child::effective_quota(ram_quota)),
			_parent_services(parent_services)
		{
			String<64> config("<config generations=\"", generation, "\" master=\"no\"/>");
			_config_policy.load(config.string(), config.length());
		}

		~Bomb_child() { log(__PRETTY_FUNCTION__); }


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

		Name name() const override { return _label; }

		Binary_name binary_name() const override { return _binary_name; }

		void init(Pd_session &pd, Pd_session_capability pd_cap) override
		{
			pd.ref_account(_env.pd_session_cap());
			_env.pd().transfer_quota(pd_cap, _cap_quota);
			_env.pd().transfer_quota(pd_cap, _ram_quota);
		}

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

		Service &_matching_service(Service::Name const &service_name,
		                           Session_label const &label)
		{
			Service *service = nullptr;

			/* check for config file request */
			if ((service = _config_policy.resolve_session_request(service_name, label)))
				return *service;

			_parent_services.for_each([&] (Service &s) {
				if (!service && service_name == s.name())
					service = &s; });

			if (!service)
				throw Service_denied();

			return *service;
		}

		Route resolve_session_request(Service::Name const &service_name,
		                              Session_label const &label) override
		{
			return Route { .service = _matching_service(service_name, label),
			               .label   = label,
			               .diag    = Session::Diag() };
		}
};


typedef Registry<Registered<Bomb_child> > Children;


/**
 * Check if a program with the specified name already exists
 */
static bool child_name_exists(Children const &children,
                              Bomb_child::Name const &name)
{
	bool found = false;

	children.for_each([&] (Bomb_child const &child) {
		if (!found && child.name() == name)
			found = true; });

	return found;
}


/**
 * Create a unique name based on the filename
 *
 * If a program with the filename as name already exists, we
 * add a counting number as suffix.
 */
static Bomb_child::Name
unique_child_name(Children const &children, Bomb_child::Name const &binary_name,
                  unsigned const generation)
{
	/* serialize calls to this function */
	static Lock lock;
	Lock::Guard guard(lock);

	for (unsigned cnt = 1; ; cnt++) {

		/* if such a program name does not exist yet, we are happy */
		Bomb_child::Name const unique(binary_name, "_g", generation, ".", cnt);
		if (!child_name_exists(children, unique.string()))
			return unique;
	}
}


struct Bomb
{
	Genode::Env &env;

	Constructible<Timer::Connection> timer { };

	Genode::Signal_handler<Bomb> signal_timeout  { env.ep(), *this, &Bomb::destruct_children };
	Genode::Signal_handler<Bomb> signal_resource { env.ep(), *this, &Bomb::resource_request };

	Attached_rom_dataspace config { env, "config" };

	unsigned round = 0;
	unsigned const rounds     = config.xml().attribute_value("rounds", 1U);
	unsigned const generation = config.xml().attribute_value("generations", 1U);
	unsigned const children   = config.xml().attribute_value("children", 2U);
	uint64_t const sleeptime  = config.xml().attribute_value("sleep", (uint64_t)2000);
	size_t   const ram_demand = config.xml().attribute_value("demand", 1024UL * 1024);
	bool     const master     = config.xml().attribute_value("master", true);

	Heap heap { env.ram(), env.rm() };

	Children child_registry { };

	Static_parent_services<Pd_session, Cpu_session, Rom_session, Log_session, Timer::Session>
		parent_services { env };

	void construct_children()
	{
		size_t const preserved_ram = Pd_connection::RAM_QUOTA;
		size_t const avail_ram     = env.pd().avail_ram().value;

		if (avail_ram < preserved_ram + ram_demand) {
			error("RAM demand exceeds available RAM");
			return;
		}

		Ram_quota const ram_amount { (avail_ram - preserved_ram - ram_demand) / children };

		if (ram_amount.value < (ram_demand * children)) {
			log("I'm a leaf node - generation ", generation,
			    " - not enough memory.");
			return;
		}

		size_t const avail_caps     = env.pd().avail_caps().value;
		size_t const preserved_caps = children*30;

		if (avail_caps < preserved_caps) {
			log("I ran out of capabilities.");
			return;
		}

		Cap_quota const cap_quota { (avail_caps - preserved_caps) / children };

		if (generation == 0) {
			log("I'm a leaf node - generation 0");
			return;
		}

		log("[", round, "] It's time to start all my children...");

		Bomb_child::Name const binary_name("bomb");

		for (unsigned i = children; i; --i) {
			try {
				new (heap)
					Registered<Bomb_child>(child_registry, env, binary_name,
					                       unique_child_name(child_registry,
					                                         binary_name,
					                                         generation - 1),
					                       cap_quota, ram_amount,
					                       parent_services, generation - 1);
			} catch (...) {
				Genode::error("creation of child ", i, " failed");
			}
		}

		/* master if we have a timer connection */
		if (master)
			timer->trigger_once(sleeptime * 1000);
	}

	void destruct_children()
	{
		log("[", round, "] It's time to kill all my children...");

		child_registry.for_each([&] (Registered<Bomb_child> &child) {
			destroy(heap, &child); });

		log("[", round, "] Done.");

		++round;

		/* master if we have a timer connection */
		if (round == rounds && master) {
			log("Done. Going to sleep");
			return;
		}

		construct_children();
	}

	void resource_request()
	{
		Genode::error("resource request");
	}

	Bomb(Genode::Env &env) : env(env)
	{
		/*
		 * Don't ask parent for further resources if we ran out of memory.
		 * Prevent us to block for resource upgrades caused by clients
		 */
		env.parent().resource_avail_sigh(signal_resource);

		log("--- bomb started ---");

		if (master) {
			timer.construct(env);

			timer->sigh(signal_timeout);

			log("rounds=", rounds, " generations=", generation, " children=",
			    children, " sleep=", sleeptime, " demand=", ram_demand/1024, "K");
		}

		construct_children();
	}
};

void Component::construct(Genode::Env &env) { static Bomb bomb(env); }
