/*
 * \brief   Startup code
 * \author  Christian Helmuth
 * \author  Christian Prochaska
 * \author  Norman Feske
 * \date    2006-04-12
 *
 * The startup code calls constructors for static objects before calling
 * main(). Furthermore, this file contains the support of exit handlers
 * and destructors.
 *
 * Some code within this file is based on 'atexit.c' of FreeBSD's libc.
 */

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

/* Genode includes */
#include <base/env.h>
#include <base/sleep.h>
#include <base/component.h>
#include <deprecated/env.h>

/* platform-specific local helper functions */
#include <base/internal/parent_cap.h>
#include <base/internal/crt0.h>


enum { ATEXIT_SIZE = 256 };


/***************
 ** C++ stuff **
 ***************/

void * __dso_handle = 0;

enum Atexit_fn_type { ATEXIT_FN_EMPTY, ATEXIT_FN_STD, ATEXIT_FN_CXA };

struct atexit_fn
{
	Atexit_fn_type fn_type;
	union
	{
		void (*std_func)(void);
		void (*cxa_func)(void *);
	} fn_ptr;       /* function pointer */
	void *fn_arg;   /* argument for CXA callback */
	void *fn_dso;   /* shared module handle */
};

/* all members are initialized with 0 */
static struct atexit
{
	bool enabled;
	int index;
	struct atexit_fn fns[ATEXIT_SIZE];
} _atexit;


static Genode::Lock &atexit_lock()
{
	static Genode::Lock _atexit_lock;
	return _atexit_lock;
}


static void atexit_enable()
{
	_atexit.enabled = true;
}


static int atexit_register(struct atexit_fn *fn)
{
	Genode::Lock::Guard atexit_lock_guard(atexit_lock());

	if (!_atexit.enabled)
		return 0;

	if (_atexit.index >= ATEXIT_SIZE) {
		Genode::error("Cannot register exit handler - ATEXIT_SIZE reached");
		return -1;
	}

	_atexit.fns[_atexit.index++] = *fn;

	return 0;
}


/**
 * Register a function to be performed at exit
 */
int genode_atexit(void (*func)(void))
{
	struct atexit_fn fn;
	int error;

	fn.fn_type = ATEXIT_FN_STD;
	fn.fn_ptr.std_func = func;
	fn.fn_arg = 0;
	fn.fn_dso = 0;

	error = atexit_register(&fn);
	return (error);
}


/**
 * Register a function to be performed at exit or when an shared object
 * with given dso handle is unloaded dynamically.
 *
 * This function is called directly by compiler generated code, so
 * it needs to be declared as extern "C" and cannot be local to
 * the cxx lib.
 */
int genode___cxa_atexit(void (*func)(void*), void *arg, void *dso)
{
	struct atexit_fn fn;
	int error;

	fn.fn_type = ATEXIT_FN_CXA;
	fn.fn_ptr.cxa_func = func;
	fn.fn_arg = arg;
	fn.fn_dso = dso;

	error = atexit_register(&fn);
	return (error);
}


/*
 * Call all handlers registered with __cxa_atexit for the shared
 * object owning 'dso'.  Note: if 'dso' is NULL, then all remaining
 * handlers are called.
 */
void genode___cxa_finalize(void *dso)
{
	struct atexit_fn fn;
	int n = 0;

	atexit_lock().lock();
	for (n = _atexit.index; --n >= 0;) {
		if (_atexit.fns[n].fn_type == ATEXIT_FN_EMPTY)
			continue; /* already been called */
		if (dso != 0 && dso != _atexit.fns[n].fn_dso)
			continue; /* wrong DSO */
		fn = _atexit.fns[n];

		/*
		 * Mark entry to indicate that this particular handler
		 * has already been called.
		 */
		_atexit.fns[n].fn_type = ATEXIT_FN_EMPTY;
		atexit_lock().unlock();

		/* call the function of correct type */
		if (fn.fn_type == ATEXIT_FN_CXA)
			fn.fn_ptr.cxa_func(fn.fn_arg);
		else if (fn.fn_type == ATEXIT_FN_STD)
			fn.fn_ptr.std_func();

		atexit_lock().lock();
	}
	atexit_lock().unlock();
}


extern "C" void __cxa_finalize(void *dso);

static Genode::Parent *_parent_ptr;


namespace Genode { void init_exit(Parent &parent) { _parent_ptr = &parent; } }


/**
 * Terminate the process.
 */
void genode_exit(int status)
{
	/* call handlers registered with 'atexit()' or '__cxa_atexit()' */
	__cxa_finalize(0);

	/* call destructors for global static objects. */
	void (**func)();
	for (func = &_dtors_start; func != &_dtors_end; (*func++)());

	/* inform parent about the exit status */
	if (_parent_ptr)
		_parent_ptr->exit(status);

	/* wait for destruction by the parent */
	Genode::sleep_forever();
}


/**
 * Dummy default arguments for main function
 */
static char  argv0[] = { '_', 'm', 'a', 'i', 'n', 0};
static char *argv[1] = { argv0 };


/**
 * Arguments for main function
 *
 * These global variables may be initialized by a constructor provided by an
 * external library.
 */
char **genode_argv = argv;
int    genode_argc = 1;
char **genode_envp = 0;


/******************************************************
 ** C entry function called by the crt0 startup code **
 ******************************************************/


namespace Genode {

	/*
	 * To be called from the context of the initial entrypoiny before
	 * passing control to the 'Component::construct' function.
	 */
	void call_global_static_constructors()
	{
		void (**func)();
		for (func = &_ctors_end; func != &_ctors_start; (*--func)());
	}

	/* XXX move to base-internal header */
	extern void bootstrap_component();
}


extern "C" int _main()
{
	/*
	 * Allow exit handlers to be registered.
	 *
	 * This is done after the creation of the environment to prevent its
	 * destruction. The environment is still needed to notify the parent
	 * after all exit handlers (including static object destructors) have
	 * been called.
	 */
	atexit_enable();

	Genode::bootstrap_component();

	/* never reached */
	return 0;
}


static int exit_status;
static void exit_on_suspended() { genode_exit(exit_status); }


extern int main(int argc, char **argv, char **envp);


void Component::construct(Genode::Env &env) __attribute__((weak));
void Component::construct(Genode::Env &env)
{
	/* call real main function */
	exit_status = main(genode_argc, genode_argv, genode_envp);

	/* trigger suspend in the entry point */
	env.ep().schedule_suspend(exit_on_suspended, nullptr);

	/* return to entrypoint and exit via exit_on_suspended() */
}

