/*
 * \brief  Noux child process
 * \author Norman Feske
 * \date   2011-02-17
 */

/*
 * Copyright (C) 2011-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 _NOUX__CHILD_H_
#define _NOUX__CHILD_H_

/* Genode includes */
#include <base/attached_ram_dataspace.h>
#include <base/attached_rom_dataspace.h>
#include <vfs/dir_file_system.h>

/* Noux includes */
#include <file_descriptor_registry.h>
#include <noux_session/capability.h>
#include <args.h>
#include <environment.h>
#include <cpu_session_component.h>
#include <pd_session_component.h>
#include <child_policy.h>
#include <io_receptor_registry.h>
#include <destruct_queue.h>
#include <interrupt_handler.h>
#include <kill_broadcaster.h>
#include <parent_execve.h>
#include <empty_rom_service.h>
#include <local_rom_service.h>
#include <verbose.h>
#include <user_info.h>
#include <armed_timeout.h>
#include <time_info.h>

namespace Noux {

	class Pid_allocator;

	/**
	 * Return singleton instance of Io_receptor_registry
	 */
	Io_receptor_registry *io_receptor_registry();

	/*
	 * Return lock for protecting the signal queue
	 */
	Genode::Lock &signal_lock();

	class Child_config;
	class Child;

	/**
	 * Return true is child is the init process
	 */
	bool init_process(Child *child);
	void init_process_exited(int);
}


/**
 * Allocator for process IDs
 */
class Noux::Pid_allocator
{
	private:

		Lock _lock     { };
		int  _num_pids { 0 };

	public:

		int alloc()
		{
			Lock::Guard guard(_lock);
			return _num_pids++;
		}
};


struct Noux::Child_config : Attached_ram_dataspace
{
	enum { CONFIG_DS_SIZE = 4096 };

	Child_config(Ram_allocator &ram, Region_map &local_rm, Verbose const &verbose)
	:
		Attached_ram_dataspace(ram, local_rm, CONFIG_DS_SIZE)
	{
		Xml_generator xml(local_addr<char>(), CONFIG_DS_SIZE, "config", [&] ()
		{
			if (verbose.ld())
				xml.attribute("ld_verbose", "yes");

			xml.node("ld", [&] () {
				xml.node("library", [&] () {
					xml.attribute("rom", "libc_noux.lib.so"); }); });
		});
	}
};


class Noux::Child : public Rpc_object<Session>,
                    public File_descriptor_registry,
                    public Family_member,
                    public Destruct_queue::Element<Child>,
                    public Interrupt_handler
{
	private:

		/*
		 * Noncopyable
		 */
		Child(Child const &);
		Child &operator = (Child const &);

		Child_policy::Name const _name;

		Verbose const &_verbose;

		User_info const &_user_info;

		Time_info const &_time_info;

		Parent_exit       *_parent_exit;
		Kill_broadcaster  &_kill_broadcaster;
		Timer::Connection &_timer_connection;
		Parent_execve     &_parent_execve;
		Pid_allocator     &_pid_allocator;

		Env &_env;

		Vfs::File_system &_root_dir;

		Vfs_io_waiter_registry &_vfs_io_waiter_registry;
		Vfs_handle_context      _vfs_handle_context { };

		Destruct_queue &_destruct_queue;

		void _handle_destruct() { _destruct_queue.insert(this); }

		Signal_handler<Child> _destruct_handler {
			_env.ep(), *this, &Child::_handle_destruct };

		Allocator &_heap;

		/**
		 * Entrypoint used to serve the RPC interfaces of the
		 * locally-provided services
		 */
		enum { STACK_SIZE = 64*1024 };
		Rpc_entrypoint _ep { &_env.pd(), STACK_SIZE, "noux_process", false };

		Pd_session                  &_ref_pd;
		Pd_session_capability  const _ref_pd_cap;

		/**
		 * Registry of dataspaces owned by the Noux process
		 */
		Dataspace_registry _ds_registry { _heap };

		/**
		 * Locally-provided PD service
		 */
		typedef Local_service<Pd_session_component> Pd_service;
		Pd_session_component _pd { _heap, _env, _ep, _name, _ds_registry };
		Pd_service::Single_session_factory _pd_factory { _pd };
		Pd_service                         _pd_service { _pd_factory };

		/**
		 * Locally-provided CPU service
		 */
		typedef Local_service<Cpu_session_component> Cpu_service;
		Cpu_session_component _cpu { _env, _ep, _name, false, _ds_registry };
		Cpu_service::Single_session_factory _cpu_factory { _cpu };
		Cpu_service                         _cpu_service { _cpu_factory };

		/*
		 * Locally-provided Noux service
		 */
		Capability_guard _cap_guard { _ep, *this };

		typedef Local_service<Rpc_object<Session> > Noux_service;
		Noux_service::Single_session_factory _noux_factory { *this };
		Noux_service                         _noux_service { _noux_factory };

		/*
		 * Locally-provided ROM service
		 */
		Empty_rom_factory _empty_rom_factory { _heap, _ep };
		Empty_rom_service _empty_rom_service { _empty_rom_factory };
		Local_rom_factory _rom_factory { _heap, _env, _ep, _root_dir,
		                                 _vfs_io_waiter_registry, _ds_registry };
		Local_rom_service _rom_service { _rom_factory };

		/**
		 * Command line arguments
		 */
		enum { ARGS_DS_SIZE = sizeof(Sysio::Args) };

		Args_dataspace _args;

		/**
		 * Environment variables
		 */
		Environment _sysio_env;

		/*
		 * Child configuration
		 */
		Child_config _config { _ref_pd, _env.rm(), _verbose };

		enum { PAGE_SIZE = 4096, PAGE_MASK = ~(PAGE_SIZE - 1) };
		enum { SYSIO_DS_SIZE = PAGE_MASK & (sizeof(Sysio) + PAGE_SIZE - 1) };

		Attached_ram_dataspace _sysio_ds { _ref_pd, _env.rm(), SYSIO_DS_SIZE };
		Sysio &_sysio = *_sysio_ds.local_addr<Sysio>();

		typedef Ring_buffer<enum Sysio::Signal, Sysio::SIGNAL_QUEUE_SIZE>
		        Signal_queue;
		Signal_queue _pending_signals { };

		Parent_services &_parent_services;

		Static_dataspace_info _sysio_ds_info;
		Static_dataspace_info _args_ds_info;
		Static_dataspace_info _sysio_env_ds_info;
		Static_dataspace_info _config_ds_info;

		Child_policy _child_policy;

		Genode::Child _child;

		/**
		 * Exception type for failed file-descriptor lookup
		 */
		class Invalid_fd { };

		Shared_pointer<Io_channel> _lookup_channel(int fd) const
		{
			Shared_pointer<Io_channel> channel = io_channel_by_fd(fd);

			if (channel)
				return channel;

			throw Invalid_fd();
		}

		/**
		 * Let specified child inherit our file descriptors
		 */
		void _assign_io_channels_to(Child *child, bool skip_when_close_on_execve_set)
		{
			for (int fd = 0; fd < MAX_FILE_DESCRIPTORS; fd++)
				if (fd_in_use(fd)) {
					if (skip_when_close_on_execve_set && close_fd_on_execve(fd))
						continue;
					child->add_io_channel(io_channel_by_fd(fd), fd);
					child->close_fd_on_execve(fd, close_fd_on_execve(fd));
				}
		}

		/**
		 * Block until the IO channel is ready for reading or writing or an
		 * exception occured.
		 *
		 * \param io  the IO channel
		 * \param rd  check for data available for reading
		 * \param wr  check for readiness for writing
		 * \param ex  check for exceptions
		 */
		void _block_for_io_channel(Shared_pointer<Io_channel> &io,
		                           bool rd, bool wr, bool ex)
		{
			/* reset the blocker lock to the 'locked' state */
			_blocker.unlock();
			_blocker.lock();

			Wake_up_notifier notifier(&_blocker);
			io->register_wake_up_notifier(&notifier);

			for (;;) {
				if (io->check_unblock(rd, wr, ex) ||
				    !_pending_signals.empty())
					break;

				/* block (unless the lock got unlocked in the meantime) */
				_blocker.lock();
			}

			io->unregister_wake_up_notifier(&notifier);
		}

		void _destruct()
		{
			_ep.dissolve(this);

			if (init_process(this))
				init_process_exited(_child_policy.exit_value());
		}

	public:

		struct Insufficient_memory : Exception { };

		/**
		 * Constructor
		 *
		 * \param forked  false if the child is spawned directly from
		 *                an executable binary (i.e., the init process,
		 *                or children created via execve, or
		 *                true if the child is a fork from another child
		 *
		 * \throw Insufficent_memory if the child could not be started by
		 *                           the parent
		 */
		Child(Child_policy::Name const &name,
		      Verbose            const &verbose,
		      User_info          const &user_info,
		      Time_info          const &time_info,
		      Parent_exit              *parent_exit,
		      Kill_broadcaster         &kill_broadcaster,
		      Timer::Connection        &timer_connection,
		      Parent_execve            &parent_execve,
		      Pid_allocator            &pid_allocator,
		      int                       pid,
		      Env                      &env,
		      Vfs::File_system         &root_dir,
		      Vfs_io_waiter_registry   &vfs_io_waiter_registry,
		      Args               const &args,
		      Sysio::Env         const &sysio_env,
		      Allocator                &heap,
		      Pd_session               &ref_pd,
		      Pd_session_capability     ref_pd_cap,
		      Parent_services          &parent_services,
		      bool                      forked,
		      Destruct_queue           &destruct_queue)
		:
			Family_member(pid),
			Destruct_queue::Element<Child>(heap),
			_name(name),
			_verbose(verbose),
			_user_info(user_info),
			_time_info(time_info),
			_parent_exit(parent_exit),
			_kill_broadcaster(kill_broadcaster),
			_timer_connection(timer_connection),
			_parent_execve(parent_execve),
			_pid_allocator(pid_allocator),
			_env(env),
			_root_dir(root_dir),
			_vfs_io_waiter_registry(vfs_io_waiter_registry),
			_destruct_queue(destruct_queue),
			_heap(heap),
			_ref_pd (ref_pd), _ref_pd_cap (ref_pd_cap),
			_args(ref_pd, _env.rm(), ARGS_DS_SIZE, args),
			_sysio_env(_ref_pd, _env.rm(), sysio_env),
			_parent_services(parent_services),
			_sysio_ds_info(_ds_registry, _sysio_ds.cap()),
			_args_ds_info(_ds_registry, _args.cap()),
			_sysio_env_ds_info(_ds_registry, _sysio_env.cap()),
			_config_ds_info(_ds_registry, _config.cap()),
			_child_policy(name, forked,
			              _args.cap(), _sysio_env.cap(), _config.cap(),
			              _ep, _pd_service, _cpu_service,
			              _noux_service, _empty_rom_service,
			              _rom_service, _parent_services,
			              *this, parent_exit, *this, _destruct_handler,
			              ref_pd, ref_pd_cap,
			              _verbose.enabled()),
			_child(_env.rm(), _ep, _child_policy)
		{
			if (_verbose.enabled())
				_args.dump();

			if (!_child.main_thread_cap().valid()) {
				_destruct();
				throw Insufficient_memory();
			}
		}

		~Child() { _destruct(); }

		void start() { _ep.activate(); }

		void start_forked_main_thread(addr_t ip, addr_t sp, addr_t parent_cap_addr)
		{
			/* poke parent_cap_addr into child's address space */
			Capability<Parent>::Raw const raw = _child.parent_cap().raw();

			_pd.poke(_env.rm(), parent_cap_addr, (char *)&raw, sizeof(raw));

			/* start execution of new main thread at supplied trampoline */
			_cpu.start_main_thread(ip, sp);
		}

		void submit_exit_signal()
		{
			if (init_process(this)) {
				log("init process exited");

				/* trigger exit of main event loop */
				init_process_exited(_child_policy.exit_value());
			} else {
				Signal_transmitter(_destruct_handler).submit();
			}
		}

		Pd_session_component &pd() { return _pd;  }

		Dataspace_registry &ds_registry()  { return _ds_registry; }


		/****************************
		 ** Noux session interface **
		 ****************************/

		Dataspace_capability sysio_dataspace() override
		{
			return _sysio_ds.cap();
		}

		Capability<Region_map> lookup_region_map(addr_t const addr) override
		{
			return _pd.lookup_region_map(addr);
		}

		bool syscall(Syscall sc) override;

		int next_open_fd(int start_fd) override
		{
			if (start_fd >= 0)
				for (int fd = start_fd; fd < MAX_FILE_DESCRIPTORS; fd++)
					if (fd_in_use(fd))
						return fd;
			return -1;
		}


		/****************************************
		 ** File_descriptor_registry overrides **
		 ****************************************/

		/**
		 * Find out if the IO channel associated with 'fd' has more file
		 * descriptors associated with it
		 */
		bool _is_the_only_fd_for_io_channel(int fd,
		                                    Shared_pointer<Io_channel> io_channel)
		{
			for (int f = 0; f < MAX_FILE_DESCRIPTORS; f++) {
				if ((f != fd) &&
				    fd_in_use(f) &&
				    (io_channel_by_fd(f) == io_channel))
				return false;
			}

			return true;
		}

		int add_io_channel(Shared_pointer<Io_channel> io_channel, int fd = -1) override
		{
			fd = File_descriptor_registry::add_io_channel(io_channel, fd);

			/* Register the interrupt handler only once per IO channel */
			if (_is_the_only_fd_for_io_channel(fd, io_channel)) {
				Io_channel_listener *l = new (_heap) Io_channel_listener(this);
				io_channel->register_interrupt_handler(l);
			}

			return fd;
		}

		void remove_io_channel(int fd) override
		{
			Shared_pointer<Io_channel> io_channel = _lookup_channel(fd);

			/*
			 * Unregister the interrupt handler only if there are no other
			 * file descriptors associated with the IO channel.
			 */
			if (_is_the_only_fd_for_io_channel(fd, io_channel)) {
				Io_channel_listener *l = io_channel->lookup_io_channel_listener(this);
				io_channel->unregister_interrupt_handler(l);
				Genode::destroy(_heap, l);
			}

			File_descriptor_registry::remove_io_channel(fd);
		}

		void flush() override
		{
			for (int fd = 0; fd < MAX_FILE_DESCRIPTORS; fd++)
				try {
					remove_io_channel(fd);
				} catch (Invalid_fd) { }
		}


		/*****************************
		 ** Family_member interface **
		 *****************************/

		void submit_signal(Noux::Sysio::Signal sig) override
		{
			try {
				_pending_signals.add(sig);
			} catch (Signal_queue::Overflow) {
				error("signal queue is full - signal dropped");
			}

			_blocker.unlock();
		}

		Family_member *do_execve(const char *filename,
		                         Args const &args,
		                         Sysio::Env const &env) override
		{
			Lock::Guard signal_lock_guard(signal_lock());

			Child *child = new (_heap) Child(filename,
			                                 _verbose,
			                                 _user_info,
			                                 _time_info,
			                                 _parent_exit,
			                                 _kill_broadcaster,
			                                 _timer_connection,
			                                 _parent_execve,
			                                 _pid_allocator,
			                                 pid(),
			                                 _env,
			                                 _root_dir,
			                                 _vfs_io_waiter_registry,
			                                 args,
			                                 env,
			                                 _heap,
			                                 _ref_pd, _ref_pd_cap,
			                                 _parent_services,
			                                 false,
			                                 _destruct_queue);

			_assign_io_channels_to(child, true);

			/* move the signal queue */
			while (!_pending_signals.empty())
				child->_pending_signals.add(_pending_signals.get());

			/*
			 * Close all open files.
			 *
			 * This action is not part of the child destructor,
			 * because in the case that a child exits itself,
			 * it may need to close all files to unblock the
			 * parent (which might be reading from a pipe) before
			 * the parent can destroy the child object.
			 */
			flush();

			/* signal main thread to remove ourself */
			Signal_transmitter(_destruct_handler).submit();

			/* start executing the new process */
			child->start();

			/* this child will be removed by the execve_finalization_dispatcher */

			return child;
		}


		/*********************************
		 ** Interrupt_handler interface **
		 *********************************/

		void handle_interrupt(Sysio::Signal signal) override
		{
			submit_signal(signal);
		}
};

#endif /* _NOUX__CHILD_H_ */
