/*
 * \brief  Support for the Linux-specific environment
 * \author Norman Feske
 * \date   2008-12-12
 */

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

/* Genode includes */
#include <util/arg_string.h>
#include <base/thread.h>
#include <linux_dataspace/client.h>
#include <linux_syscalls.h>

/* local includes */
#include <platform_env.h>

using namespace Genode;


/****************************************************
 ** Support for Platform_env_base::Rm_session_mmap **
 ****************************************************/

Genode::size_t
Platform_env_base::Rm_session_mmap::_dataspace_size(Dataspace_capability ds)
{
	if (ds.valid())
		return Dataspace_client(ds).size();

	return Dataspace_capability::deref(ds)->size();
}


int Platform_env_base::Rm_session_mmap::_dataspace_fd(Dataspace_capability ds)
{
	return Linux_dataspace_client(ds).fd().dst().socket;
}


bool
Platform_env_base::Rm_session_mmap::_dataspace_writable(Dataspace_capability ds)
{
	return Dataspace_client(ds).writable();
}


/********************************
 ** Platform_env::Local_parent **
 ********************************/

Session_capability
Platform_env::Local_parent::session(Service_name const &service_name,
                                    Session_args const &args)
{
	if (strcmp(service_name.string(),
	    Rm_session::service_name()) == 0)
	{
		size_t size =
			Arg_string::find_arg(args.string(),"size")
				.ulong_value(~0);

		if (size == 0)
			return Parent_client::session(service_name, args);

		Rm_session_mmap *rm = new (env()->heap())
		                      Rm_session_mmap(true, size);

		return Session_capability::local_cap(rm);
	}

	return Parent_client::session(service_name, args);
}


void Platform_env::Local_parent::close(Session_capability session)
{
	/*
	 * Handle non-local capabilities
	 */
	if (session.valid()) {
		Parent_client::close(session);
		return;
	}

	/*
	 * Detect capability to local RM session
	 */
	Capability<Rm_session_mmap> rm = static_cap_cast<Rm_session_mmap>(session);

	destroy(env()->heap(), Capability<Rm_session_mmap>::deref(rm));
}


Platform_env::Local_parent::Local_parent(Parent_capability parent_cap)
: Parent_client(parent_cap)
{
}


/******************
 ** Platform_env **
 ******************/

/**
 * List of Unix environment variables, initialized by the startup code
 */
extern char **lx_environ;


/**
 * Read environment variable as long value
 */
static unsigned long get_env_ulong(const char *key)
{
	for (char **curr = lx_environ; curr && *curr; curr++) {

		Arg arg = Arg_string::find_arg(*curr, key);
		if (arg.valid())
			return arg.ulong_value(0);
	}

	return 0;
}


static Parent_capability obtain_parent_cap()
{
	long local_name = get_env_ulong("parent_local_name");

	/* produce typed capability manually */
	typedef Native_capability::Dst Dst;
	Dst const dst(PARENT_SOCKET_HANDLE);
	return reinterpret_cap_cast<Parent>(Native_capability(dst, local_name));
}


Platform_env::Local_parent &Platform_env::_parent()
{
	static Local_parent local_parent(obtain_parent_cap());
	return local_parent;
}


Platform_env::Platform_env()
:
	Platform_env_base(static_cap_cast<Ram_session>(_parent().session("Env::ram_session", "")),
	                  static_cap_cast<Cpu_session>(_parent().session("Env::cpu_session", "")),
	                  static_cap_cast<Pd_session> (_parent().session("Env::pd_session",  ""))),
	_heap(Platform_env_base::ram_session(), Platform_env_base::rm_session())
{
	/* register TID and PID of the main thread at core */
	cpu_session()->thread_id(parent()->main_thread_cap(),
	                         lx_getpid(), lx_gettid());
}


/*****************************
 ** Support for IPC library **
 *****************************/

namespace Genode {

	Native_connection_state server_socket_pair()
	{
		/*
		 * Obtain access to Linux-specific extension of the CPU session
		 * interface. We can cast to the specific type because the Linux
		 * version of 'Platform_env' is hosting a 'Linux_cpu_client' object.
		 */
		Linux_cpu_session *cpu = dynamic_cast<Linux_cpu_session *>(env()->cpu_session());

		if (!cpu) {
			PERR("could not obtain Linux extension to CPU session interface");
			struct Could_not_access_linux_cpu_session { };
			throw Could_not_access_linux_cpu_session();
		}

		Native_connection_state ncs;

		Thread_base *thread = Thread_base::myself();
		if (thread) {
			ncs.server_sd = cpu->server_sd(thread->cap()).dst().socket;
			ncs.client_sd = cpu->client_sd(thread->cap()).dst().socket;
		}
		return ncs;
	}

	void destroy_server_socket_pair(Native_connection_state const &ncs)
	{
		/* close local file descriptor if it is valid */
		if (ncs.server_sd != -1) lx_close(ncs.server_sd);
		if (ncs.client_sd != -1) lx_close(ncs.client_sd);
	}
}
