/*
 * \brief  Noux libc plugin
 * \author Norman Feske
 * \date   2011-02-14
 */

/*
 * Copyright (C) 2011-2012 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/misc_math.h>
#include <util/arg_string.h>
#include <base/printf.h>
#include <rom_session/connection.h>
#include <base/sleep.h>
#include <dataspace/client.h>

/* noux includes */
#include <noux_session/connection.h>
#include <noux_session/sysio.h>

/* libc plugin includes */
#include <libc-plugin/plugin.h>
#include <libc-plugin/fd_alloc.h>

/* libc includes */
#include <errno.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <sys/dirent.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <unistd.h>
#include <termios.h>


enum { verbose = false };


void *operator new (size_t, void *ptr) { return ptr; }


class Noux_connection
{
	private:

		Noux::Connection _connection;
		Noux::Sysio      *_sysio;

		Noux::Sysio *_obtain_sysio()
		{
			return Genode::env()->rm_session()->attach(_connection.sysio_dataspace());
		}

	public:

		Noux_connection() : _sysio(_obtain_sysio()) { }

		void reconnect()
		{
			new (&_connection) Noux_connection;
			Genode::env()->rm_session()->detach(_sysio);
			_sysio = _obtain_sysio();
		}

		Noux::Session *session() { return &_connection; }
		Noux::Sysio   *sysio()   { return  _sysio; }
};


Noux_connection *noux_connection()
{
	static Noux_connection inst;
	return &inst;
}


Noux::Session *noux()  { return noux_connection()->session(); }
Noux::Sysio   *sysio() { return noux_connection()->sysio();   }


enum { FS_BLOCK_SIZE = 1024 };


/***********************************************
 ** Overrides of libc default implementations **
 ***********************************************/

extern "C" int __getcwd(char *dst, Genode::size_t dst_size)
{
	noux()->syscall(Noux::Session::SYSCALL_GETCWD);
	Genode::size_t path_size = Genode::strlen(sysio()->getcwd_out.path);

	if (dst_size < path_size + 1)
		return -ERANGE;

	Genode::strncpy(dst, sysio()->getcwd_out.path, dst_size);
	return 0;
}


/**
 * Utility to copy-out syscall results to buf struct
 *
 * Code shared between 'stat' and 'fstat'.
 */
static void _sysio_to_stat_struct(Noux::Sysio const *sysio, struct stat *buf)
{
	Genode::memset(buf, 0, sizeof(*buf));
	buf->st_uid     = sysio->stat_out.st.uid;
	buf->st_gid     = sysio->stat_out.st.gid;
	buf->st_mode    = sysio->stat_out.st.mode;
	buf->st_size    = sysio->stat_out.st.size;
	buf->st_blksize = FS_BLOCK_SIZE;
	buf->st_blocks  = (buf->st_size + FS_BLOCK_SIZE - 1) / FS_BLOCK_SIZE;
	buf->st_ino     = sysio->stat_out.st.inode;
	buf->st_dev     = sysio->stat_out.st.device;
}


static int _stat(const char *path, struct stat *buf, bool lstat = false)
{
	if ((path == NULL) or (buf == NULL)) {
		errno = EFAULT;
		return -1;
	}

	Genode::strncpy(sysio()->stat_in.path, path, sizeof(sysio()->stat_in.path));

	if (!noux()->syscall(Noux::Session::SYSCALL_STAT)) {
		PWRN("stat syscall failed for path \"%s\"", path);
		switch (sysio()->error.stat) {
		case Noux::Sysio::STAT_ERR_NO_ENTRY: errno = ENOENT; break;
		}
		return -1;
	}

	_sysio_to_stat_struct(sysio(), buf);
	return 0;
}


extern "C" int lstat(const char *path, struct stat *buf) { return _stat(path, buf, true);  }


static bool serialize_string_array(char const * const * array, char *dst, Genode::size_t dst_len)
{
	for (unsigned i = 0; array[i]; i++)
	{
		Genode::size_t const curr_len = Genode::strlen(array[i]) + 1;
		if (curr_len + 1 >= dst_len)
			return false;

		Genode::strncpy(dst, array[i], dst_len);

		dst += curr_len;
		dst_len -= curr_len;
	}

	dst[0] = 0;
	return true;
}


extern "C" int execve(const char *filename, char *const argv[],
                      char *const envp[])
{
	if (verbose) {
		PDBG("filename=%s", filename);

		for (int i = 0; argv[i]; i++)
			PDBG("argv[%d]='%s'", i, argv[i]);

		for (int i = 0; envp[i]; i++)
			PDBG("envp[%d]='%s'", i, envp[i]);
	}

	Genode::strncpy(sysio()->execve_in.filename, filename, sizeof(sysio()->execve_in.filename));
	if (!serialize_string_array(argv, sysio()->execve_in.args,
	                           sizeof(sysio()->execve_in.args))) {
	    PERR("execve: argument buffer exceeded");
	    errno = E2BIG;
	    return -1;
	}

	if (!serialize_string_array(envp, sysio()->execve_in.env,
	                            sizeof(sysio()->execve_in.env))) {
	    PERR("execve: environment buffer exceeded");
	    errno = E2BIG;
	    return -1;
	}

	if (!noux()->syscall(Noux::Session::SYSCALL_EXECVE)) {
		PWRN("exec syscall failed for path \"%s\"", filename);
		switch (sysio()->error.execve) {
		case Noux::Sysio::EXECVE_NONEXISTENT: errno = ENOENT; break;
		}
		return -1;
	}

	/*
	 * In the success case, we never return from execve, the execution is
	 * resumed in the new program.
	 */
	Genode::sleep_forever();
	return 0;
}


/**
 * Return number of marhalled file descriptors into select argument buffer
 *
 * \return number of marshalled file descriptors, this value is guaranteed to
 *         not exceed the 'dst_fds_len' argument
 */
static size_t marshal_fds(fd_set *src_fds, int nfds,
                       int *dst_fds, size_t dst_fds_len)
{
	if (!src_fds) return 0;

	size_t num_fds = 0;

	for (int fd = 0; (fd < nfds) && (num_fds < dst_fds_len); fd++)
		if (FD_ISSET(fd, src_fds))
			dst_fds[num_fds++] = fd;

	return num_fds;
}


/**
 * Unmarshal result of select syscall into fd set
 */
static void unmarshal_fds(int *src_fds, size_t src_fds_len, fd_set *dst_fds)
{
	if (!dst_fds) return;

	FD_ZERO(dst_fds);

	for (size_t i = 0; i < src_fds_len; i++)
		FD_SET(src_fds[i], dst_fds);
}


extern "C" int select(int nfds, fd_set *readfds, fd_set *writefds,
                      fd_set *exceptfds, struct timeval *timeout)
{

	/*
	 * Marshal file descriptor into sysio page
	 */
	Noux::Sysio::Select_fds &in_fds = sysio()->select_in.fds;

	int   *dst     = in_fds.array;
	size_t dst_len = Noux::Sysio::Select_fds::MAX_FDS;

	in_fds.num_rd = marshal_fds(readfds, nfds, dst, dst_len);

	dst     += in_fds.num_rd;
	dst_len -= in_fds.num_rd;

	in_fds.num_wr = marshal_fds(writefds, nfds, dst, dst_len);

	dst     += in_fds.num_wr;
	dst_len -= in_fds.num_wr;

	in_fds.num_ex = marshal_fds(exceptfds, nfds, dst, dst_len);

	if (in_fds.max_fds_exceeded()) {
		errno = ENOMEM;
		return -1;
	}

	/*
	 * Marshal timeout
	 */
	if (timeout) {
		sysio()->select_in.timeout.sec  = timeout->tv_sec;
		sysio()->select_in.timeout.usec = timeout->tv_usec;

		if (!sysio()->select_in.timeout.zero()) {
//			PDBG("timeout=%d,%d -> replaced by zero timeout",
//			     (int)timeout->tv_sec, (int)timeout->tv_usec);

			sysio()->select_in.timeout.sec  = 0;
			sysio()->select_in.timeout.usec = 0;
		}
	} else {
		sysio()->select_in.timeout.set_infinite();
	}

	/*
	 * Perform syscall
	 */
	if (!noux()->syscall(Noux::Session::SYSCALL_SELECT)) {
		PWRN("select syscall failed");
//		switch (sysio()->error.select) {
//		case Noux::Sysio::SELECT_NONEXISTENT: errno = ENOENT; break;
//		}
		return -1;
	}

	/*
	 * Unmarshal file selectors reported by the select syscall
	 */
	Noux::Sysio::Select_fds &out_fds = sysio()->select_out.fds;

	int *src = out_fds.array;

	unmarshal_fds(src, out_fds.num_rd, readfds);
	src += out_fds.num_rd;

	unmarshal_fds(src, out_fds.num_wr, writefds);
	src += out_fds.num_wr;

	unmarshal_fds(src, out_fds.num_ex, exceptfds);

	return out_fds.total_fds();
}

#include <setjmp.h>
#include <base/platform_env.h>


static jmp_buf fork_jmp_buf;
static Genode::Capability<Genode::Parent>::Raw new_parent;

extern "C" void stdout_reconnect(); /* provided by 'log_console.cc' */


/*
 * The new process created via fork will start its execution here.
 */
extern "C" void fork_trampoline()
{
	static_cast<Genode::Platform_env *>(Genode::env())
		->reload_parent_cap(new_parent.dst, new_parent.local_name);

	stdout_reconnect();
	noux_connection()->reconnect();

	longjmp(fork_jmp_buf, 1);
}


extern "C" pid_t fork(void)
{
	/* stack used for executing 'fork_trampoline' */
	enum { STACK_SIZE = 1024 };
	static long stack[STACK_SIZE];

	if (setjmp(fork_jmp_buf)) {

		/*
		 * We got here via longjmp from 'fork_trampoline'.
		 */
		return 0;

	} else {

		/* got here during the normal control flow of the fork call */
		sysio()->fork_in.ip              = (Genode::addr_t)(&fork_trampoline);
		sysio()->fork_in.sp              = (Genode::addr_t)(&stack[STACK_SIZE]);
		sysio()->fork_in.parent_cap_addr = (Genode::addr_t)(&new_parent);

		if (!noux()->syscall(Noux::Session::SYSCALL_FORK)) {
			PERR("fork error %d", sysio()->error.general);
		}

		return sysio()->fork_out.pid;
	}
}


extern "C" pid_t getpid(void)
{
	noux()->syscall(Noux::Session::SYSCALL_GETPID);
	return sysio()->getpid_out.pid;
}


extern "C" pid_t _wait4(pid_t pid, int *status, int options,
                        struct rusage *rusage)
{
	sysio()->wait4_in.pid    = pid;
	sysio()->wait4_in.nohang = !!(options & WNOHANG);
	if (!noux()->syscall(Noux::Session::SYSCALL_WAIT4)) {
		PERR("wait4 error %d", sysio()->error.general);
		return -1;
	}

	if (status)
		*status = sysio()->wait4_out.status;

	return sysio()->wait4_out.pid;
}


/********************
 ** Time functions **
 ********************/

/*
 * The default implementations as provided by the libc rely on a dedicated
 * thread. But on Noux, no thread other than the main thread is allowed. For
 * this reason, we need to override the default implementations here.
 */

extern "C" int clock_gettime(clockid_t clk_id, struct timespec *tp)
{
	PDBG("clock_gettime called - not implemented");
	errno = EINVAL;
	return -1;
}


extern "C" int gettimeofday(struct timeval *tv, struct timezone *tz)
{
	PDBG("gettimeofdaye called - not implemented");
	errno = EINVAL;
	return -1;
}


/*********************
 ** Signal handling **
 *********************/

extern "C" int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
{
	/* XXX todo */
	errno = ENOSYS;
	return -1;
}


extern "C" int _sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
{
	return sigprocmask(how, set, oldset);
}


extern "C" int sigaction(int signum, const struct sigaction *act,
                         struct sigaction *oldact)
{
	/* XXX todo */
	errno = ENOSYS;
	return -1;
}


/*********************
 ** File operations **
 *********************/

static int noux_fd(Libc::Plugin_context *context)
{
	/*
	 * We use the 'context' pointer only as container for an int value. It is
	 * never used as a pointer. To make 64-bit compilers happy, we need to keep
	 * the bit width of the cast intact. The upper 32 bit are discarded when
	 * leaving the function.
	 */
	return (long)context;
}


static Libc::Plugin_context *noux_context(int noux_fd)
{
	return reinterpret_cast<Libc::Plugin_context*>(noux_fd);
}


namespace {

	class Plugin : public Libc::Plugin
	{
		private:

			Libc::File_descriptor *_stdin;
			Libc::File_descriptor *_stdout;
			Libc::File_descriptor *_stderr;

		public:

			/**
			 * Constructor
			 */
			Plugin() :
				_stdin (Libc::file_descriptor_allocator()->alloc(this, noux_context(0), 0)),
				_stdout(Libc::file_descriptor_allocator()->alloc(this, noux_context(1), 1)),
				_stderr(Libc::file_descriptor_allocator()->alloc(this, noux_context(2), 2))
			{ }

			bool supports_chdir(const char *)     { return true; }
			bool supports_open(const char *, int) { return true; }
			bool supports_stat(const char *)      { return true; }
			bool supports_pipe()                  { return true; }

			Libc::File_descriptor *open(const char *, int);
			ssize_t write(Libc::File_descriptor *, const void *, ::size_t);
			int close(Libc::File_descriptor *);
			int dup2(Libc::File_descriptor *, Libc::File_descriptor *);
			int fstat(Libc::File_descriptor *, struct stat *);
			int fstatfs(Libc::File_descriptor *, struct statfs *);
			int fcntl(Libc::File_descriptor *, int, long);
			ssize_t getdirentries(Libc::File_descriptor *, char *, ::size_t, ::off_t *);
			::off_t lseek(Libc::File_descriptor *, ::off_t offset, int whence);
			int fchdir(Libc::File_descriptor *);
			ssize_t read(Libc::File_descriptor *, void *, ::size_t);
			int stat(const char *, struct stat *);
			int ioctl(Libc::File_descriptor *, int request, char *argp);
			int pipe(Libc::File_descriptor *pipefd[2]);
	};


	int Plugin::stat(const char *path, struct stat *buf)
	{
		return _stat(path, buf, false);
	}


	Libc::File_descriptor *Plugin::open(const char *pathname, int flags)
	{
		if (Genode::strlen(pathname) + 1 > sizeof(sysio()->open_in.path)) {
			errno = ENAMETOOLONG;
			return 0;
		}

		Genode::strncpy(sysio()->open_in.path, pathname, sizeof(sysio()->open_in.path));
		sysio()->open_in.mode = flags;

		if (!noux()->syscall(Noux::Session::SYSCALL_OPEN)) {
			/*
			 * XXX  we should return meaningful errno values
			 */
			errno = ENOENT;
			return 0;
		}

		Libc::Plugin_context *context = noux_context(sysio()->open_out.fd);
		return Libc::file_descriptor_allocator()->alloc(this, context, sysio()->open_out.fd);
	}


	int Plugin::fstatfs(Libc::File_descriptor *, struct statfs *buf)
	{
		return 0;
	}


	ssize_t Plugin::write(Libc::File_descriptor *fd, const void *buf,
	                      ::size_t count)
	{
		if (fd != _stdout && fd != _stderr) {
			errno = EBADF;
			return -1;
		}

		/* remember original len for the return value */
		int const orig_count = count;

		char *src = (char *)buf;
		while (count > 0) {

			Genode::size_t curr_count = Genode::min((::size_t)Noux::Sysio::CHUNK_SIZE, count);

			sysio()->write_in.fd = noux_fd(fd->context);
			sysio()->write_in.count = curr_count;
			Genode::memcpy(sysio()->write_in.chunk, src, curr_count);

			if (!noux()->syscall(Noux::Session::SYSCALL_WRITE)) {
				PERR("write error %d", sysio()->error.general);
			}

			count -= curr_count;
			src   += curr_count;
		}
		return orig_count;
	}


	ssize_t Plugin::read(Libc::File_descriptor *fd, void *buf, ::size_t count)
	{
		Genode::size_t sum_read_count = 0;

		while (count) {

			Genode::size_t curr_count =
				Genode::min(count, sizeof(sysio()->read_out.chunk));

			sysio()->read_in.fd    = noux_fd(fd->context);
			sysio()->read_in.count = curr_count;

			if (!noux()->syscall(Noux::Session::SYSCALL_READ)) {
				PERR("read error");
				/* XXX set errno */
				return -1;
			}

			Genode::memcpy(buf, sysio()->read_out.chunk, sysio()->read_out.count);

			sum_read_count += sysio()->read_out.count;

			if (sysio()->read_out.count < sysio()->read_in.count)
				break; /* end of file */

			if (sysio()->read_out.count <= count)
				count -= sysio()->read_out.count;
			else
				break; /* should not happen */
		}

		return sum_read_count;
	}


	int Plugin::close(Libc::File_descriptor *fd)
	{
		sysio()->close_in.fd = noux_fd(fd->context);
		if (!noux()->syscall(Noux::Session::SYSCALL_CLOSE)) {
			PERR("close error");
			/* XXX set errno */
			return -1;
		}
		Libc::file_descriptor_allocator()->free(fd);
		return 0;
	}


	int Plugin::ioctl(Libc::File_descriptor *fd, int request, char *argp)
	{
		/*
		 * Marshal ioctl arguments
		 */
		sysio()->ioctl_in.fd = noux_fd(fd->context);
		sysio()->ioctl_in.request = Noux::Sysio::Ioctl_in::OP_UNDEFINED;

		switch (request) {

		case TIOCGWINSZ:
			sysio()->ioctl_in.request = Noux::Sysio::Ioctl_in::OP_TIOCGWINSZ;
			break;

		case TIOCGETA:
			{
				PDBG("TIOCGETA - argp=0x%p", argp);
				::termios *termios = (::termios *)argp;

				/*
				 * Set 'ECHO' flag, needed by libreadline. Otherwise, echoing
				 * user input doesn't work in bash.
				 */
				termios->c_lflag = ECHO;
				return 0;
			}

			break;

		default:

			PWRN("unsupported ioctl (request=0x%x", request);
			break;
		}

		if (sysio()->ioctl_in.request == Noux::Sysio::Ioctl_in::OP_UNDEFINED) {
			errno = ENOTTY;
			return -1;
		}

		/* perform syscall */
		if (!noux()->syscall(Noux::Session::SYSCALL_IOCTL)) {
			PERR("ioctl error");
			/* XXX set errno */
			return -1;
		}

		/*
		 * Unmarshal ioctl results
		 */
		switch (request) {

		case TIOCGWINSZ:
			{
				::winsize *winsize = (::winsize *)argp;
				winsize->ws_row = sysio()->ioctl_out.tiocgwinsz.rows;
				winsize->ws_col = sysio()->ioctl_out.tiocgwinsz.columns;
				return 0;
			}

		default:
			return -1;
		}
	}


	int Plugin::pipe(Libc::File_descriptor *pipefd[2])
	{
		/* perform syscall */
		if (!noux()->syscall(Noux::Session::SYSCALL_PIPE)) {
			PERR("pipe error");
			/* XXX set errno */
			return -1;
		}

		for (int i = 0; i < 2; i++) {
			Libc::Plugin_context *context = noux_context(sysio()->pipe_out.fd[i]);
			pipefd[i] = Libc::file_descriptor_allocator()->alloc(this, context, sysio()->pipe_out.fd[i]);
		}
		return 0;
	}


	int Plugin::dup2(Libc::File_descriptor *fd, Libc::File_descriptor *new_fd)
	{
		/*
		 * We use a one-to-one mapping of libc fds and Noux fds.
		 */
		new_fd->context = noux_context(new_fd->libc_fd);

		sysio()->dup2_in.fd    = noux_fd(fd->context);
		sysio()->dup2_in.to_fd = noux_fd(new_fd->context);

		/* perform syscall */
		if (!noux()->syscall(Noux::Session::SYSCALL_DUP2)) {
			PERR("dup2 error");
			/* XXX set errno */
			return -1;
		}

		return 0;
	}


	int Plugin::fstat(Libc::File_descriptor *fd, struct stat *buf)
	{
		sysio()->fstat_in.fd = noux_fd(fd->context);
		if (!noux()->syscall(Noux::Session::SYSCALL_FSTAT)) {
			PERR("fstat error");
			/* XXX set errno */
			return -1;
		}

		_sysio_to_stat_struct(sysio(), buf);
		return 0;
	}


	int Plugin::fcntl(Libc::File_descriptor *fd, int cmd, long arg)
	{
		/* copy arguments to sysio */
		sysio()->fcntl_in.fd = noux_fd(fd->context);
		switch (cmd) {

		case F_SETFD:
			sysio()->fcntl_in.cmd      = Noux::Sysio::FCNTL_CMD_SET_FD_FLAGS;
			sysio()->fcntl_in.long_arg = arg;
			break;

		case F_GETFL:
			PINF("fcntl: F_GETFL for libc_fd=%d", fd->libc_fd);
			sysio()->fcntl_in.cmd = Noux::Sysio::FCNTL_CMD_GET_FILE_STATUS_FLAGS;
			break;

		default:
			PERR("fcntl: unsupported command %d", cmd);
			errno = EINVAL;
			return -1;
		};

		/* invoke system call */
		if (!noux()->syscall(Noux::Session::SYSCALL_FCNTL)) {
			PWRN("fcntl failed (libc_fd= %d, cmd=%x)", fd->libc_fd, cmd);
			/* XXX read error code from sysio */
			errno = EINVAL;
			return -1;
		}

		/* read result from sysio */
		return sysio()->fcntl_out.result;
	}


	ssize_t Plugin::getdirentries(Libc::File_descriptor *fd, char *buf,
	                              ::size_t nbytes, ::off_t *basep)
	{
		if (nbytes < sizeof(struct dirent)) {
			PERR("buf too small");
			return -1;
		}

		unsigned const curr_offset = *basep;

		sysio()->dirent_in.fd = noux_fd(fd->context);
		sysio()->dirent_in.index = curr_offset / sizeof(struct dirent);

		struct dirent *dirent = (struct dirent *)buf;
		Genode::memset(dirent, 0, sizeof(struct dirent));

		if (!noux()->syscall(Noux::Session::SYSCALL_DIRENT)) {
			switch (sysio()->error.general) {

			case Noux::Sysio::ERR_FD_INVALID:
				errno = EBADF;
				PERR("dirent: ERR_FD_INVALID");
				return -1;

			case Noux::Sysio::NUM_GENERAL_ERRORS: return -1;
			}
		}

		switch (sysio()->dirent_out.entry.type) {
		case Noux::Sysio::DIRENT_TYPE_DIRECTORY: dirent->d_type = DT_DIR;  break;
		case Noux::Sysio::DIRENT_TYPE_FILE:      dirent->d_type = DT_REG;  break;
		case Noux::Sysio::DIRENT_TYPE_SYMLINK:   dirent->d_type = DT_LNK;  break;
		case Noux::Sysio::DIRENT_TYPE_FIFO:      dirent->d_type = DT_FIFO; break;
		case Noux::Sysio::DIRENT_TYPE_END:       return 0;
		}

		dirent->d_fileno = sysio()->dirent_out.entry.fileno;
		dirent->d_reclen = sizeof(struct dirent);

		Genode::strncpy(dirent->d_name, sysio()->dirent_out.entry.name,
		                sizeof(dirent->d_name));

		dirent->d_namlen = Genode::strlen(dirent->d_name);

		*basep += sizeof(struct dirent);
		return sizeof(struct dirent);
	}


	::off_t Plugin::lseek(Libc::File_descriptor *fd,
	                      ::off_t offset, int whence)
	{
		PWRN("lseek - not implemented: fd=%d, offset=%ld, whence=%d",
		     noux_fd(fd->context), (long)offset, whence);

		if (whence == SEEK_SET)
			return offset;

		return 0;
	}


	int Plugin::fchdir(Libc::File_descriptor *fd)
	{
		sysio()->fchdir_in.fd = noux_fd(fd->context);
		if (!noux()->syscall(Noux::Session::SYSCALL_FCHDIR)) {
			PERR("fchdir error");
			/* XXX set errno */
			return -1;
		}

		return 0;
	}

} /* unnamed namespace */


/**************************************
 ** Obtaining command-line arguments **
 **************************************/

/* external symbols provided by Genode's startup code */
extern char **genode_argv;
extern int    genode_argc;

/* pointer to environment, provided by libc */
extern char **environ;

__attribute__((constructor))
void init_libc_noux(void)
{
	/* copy command-line arguments from 'args' ROM dataspace */
	Genode::Rom_connection args_rom("args");
	char *args = (char *)Genode::env()->rm_session()->
		attach(args_rom.dataspace());

	enum { MAX_ARGS = 256, ARG_BUF_SIZE = 4096 };
	static char *argv[MAX_ARGS];
	static char  arg_buf[ARG_BUF_SIZE];
	Genode::memcpy(arg_buf, args, ARG_BUF_SIZE);

	int argc = 0;
	for (unsigned i = 0; arg_buf[i] && (i < ARG_BUF_SIZE - 2); ) {

		if (i >= ARG_BUF_SIZE - 2) {
			PWRN("command-line argument buffer exceeded\n");
			break;
		}

		if (argc >= MAX_ARGS - 1) {
			PWRN("number of command-line arguments exceeded\n");
			break;
		}

		argv[argc] = &arg_buf[i];
		i += Genode::strlen(&args[i]) + 1; /* skip null-termination */
		argc++;
	}

	/* register command-line arguments at Genode's startup code */
	genode_argv = argv;
	genode_argc = argc;

	/*
	 * Make environment variables from 'env' ROM dataspace available to libc's
	 * 'environ'.
	 */
	Genode::Rom_connection env_rom("env");
	Genode::Dataspace_capability env_ds = env_rom.dataspace();
	char *env_string = (char *)Genode::env()->rm_session()->attach(env_ds);

	enum { ENV_MAX_SIZE       = 4096,
	       ENV_MAX_ENTRIES    =  128,
	       ENV_KEY_MAX_SIZE   =  256,
	       ENV_VALUE_MAX_SIZE = 1024 };

	static char  env_buf[ENV_MAX_SIZE];
	static char *env_array[ENV_MAX_ENTRIES];

	unsigned num_entries = 0;  /* index within 'env_array' */
	Genode::size_t i     = 0;  /* index within 'env_buf' */

	while ((num_entries < ENV_MAX_ENTRIES - 2) && (i < ENV_MAX_SIZE)) {

		Genode::Arg arg = Genode::Arg_string::first_arg(env_string);
		if (!arg.valid())
			break;

		char key_buf[ENV_KEY_MAX_SIZE],
		     value_buf[ENV_VALUE_MAX_SIZE];

		arg.key(key_buf, sizeof(key_buf));
		arg.string(value_buf, sizeof(value_buf), "");

		env_array[num_entries++] = env_buf + i;
		Genode::snprintf(env_buf + i, ENV_MAX_SIZE - i,
		                 "%s=%s", key_buf, value_buf);

		i += Genode::strlen(env_buf + i) + 1;

		/* remove processed arg from 'env_string' */
		Genode::Arg_string::remove_arg(env_string, key_buf);
	}

	/* register list of environment variables at libc 'environ' pointer */
	environ = env_array;

	/* initialize noux libc plugin */
	static Plugin noux_plugin;
}


