/*
 * \brief  Facility for passing system-call arguments
 * \author Norman Feske
 * \date   2011-02-15
 *
 * The 'Sysio' data structure is shared between the noux environment
 * and the child. It is used to pass system-call arguments that would
 * traditionally be transferred via 'copy_from_user' and 'copy_to_user'.
 */

/*
 * 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.
 */

#ifndef _INCLUDE__NOUX_SESSION__SYSIO_H_
#define _INCLUDE__NOUX_SESSION__SYSIO_H_

/* Genode includes */
#include <util/misc_math.h>


#define SYSIO_DECL(syscall_name, args, results) \
	struct args syscall_name##_in; \
	struct results syscall_name##_out;


namespace Noux {

	using namespace Genode;

	struct Sysio
	{
		/*
		 * Information about pending signals
		 */
		enum { SIG_MAX = 32 };
		bool sig_mask[SIG_MAX];

		/*
		 * Number of pending signals
		 */
		int sig_cnt;

		enum { MAX_PATH_LEN = 512 };
		typedef char Path[MAX_PATH_LEN];

		enum { CHUNK_SIZE = 7*1024 };
		typedef char Chunk[CHUNK_SIZE];

		enum { ARGS_MAX_LEN = 3*1024 };
		typedef char Args[ARGS_MAX_LEN];

		enum { ENV_MAX_LEN  = 3*1024 };
		typedef char Env[ENV_MAX_LEN];

		typedef __SIZE_TYPE__ size_t;

		enum {
			STAT_MODE_SYMLINK   = 0120000,
			STAT_MODE_FILE      = 0100000,
			STAT_MODE_DIRECTORY = 0040000,
			STAT_MODE_CHARDEV   = 0020000,
		};

		struct Stat
		{
			size_t         size;
			unsigned       mode;
			unsigned       uid;
			unsigned       gid;
			unsigned long  inode;
			unsigned       device;
		};

		/**
		 * Argument structure used for ioctl syscall
		 */
		struct Ioctl_in
		{
			enum Opcode { OP_UNDEFINED, OP_TIOCGWINSZ };

			Opcode request;
		};

		/**
		 * Structure carrying the result values of 'ioctl' syscalls
		 */
		struct Ioctl_out
		{
			union
			{
				/* if request was 'OP_TIOCGSIZE' */
				struct {
					int rows;
					int columns;
				} tiocgwinsz;
			};
		};

		enum { DIRENT_MAX_NAME_LEN = 128 };

		enum Dirent_type {
			DIRENT_TYPE_FILE,
			DIRENT_TYPE_DIRECTORY,
			DIRENT_TYPE_FIFO,
			DIRENT_TYPE_SYMLINK,
			DIRENT_TYPE_END
		};

		struct Dirent
		{
			int         fileno;
			Dirent_type type;
			char        name[DIRENT_MAX_NAME_LEN];
		};

		enum Fcntl_cmd {
			FCNTL_CMD_GET_FILE_STATUS_FLAGS,
			FCNTL_CMD_SET_FD_FLAGS
		};

		/**
		 * Input and output argument type of select syscall
		 */
		struct Select_fds
		{
			/**
			 * Maximum number of file descriptors supported
			 */
			enum { MAX_FDS = 32U };

			/**
			 * Number of file descriptors to watch for read operations (rd),
			 * write operations (wr), or exceptions (ex).
			 */
			size_t num_rd,
			       num_wr,
			       num_ex;

			/**
			 * Array containing the file descriptors, starting with those
			 * referring to rd, followed by wr, and finally ex
			 */
			int array[MAX_FDS];

			/**
			 * Return total number of file descriptors contained in the array
			 */
			size_t total_fds() const {
				return min(num_rd + num_wr + num_ex, (size_t)MAX_FDS); }

			/**
			 * Check for maximum population of fds array
			 */
			bool max_fds_exceeded() const
			{
				/*
				 * Note that even though the corner case of num_rd + num_wr +
				 * num_ex == MAX_FDS is technically valid, this condition hints
				 * at a possible attempt to over popupate the array (see the
				 * implementation of 'select' in the Noux libc plugin). Hence,
				 * we regard this case as an error, too.
				 */
				return total_fds() >= MAX_FDS;
			}

			/**
			 * Return true of fd set index should be watched for reading
			 */
			bool watch_for_rd(unsigned i) const { return i < num_rd; }

			/**
			 * Return true if fd set index should be watched for writing
			 */
			bool watch_for_wr(unsigned i) const {
				return (i >= num_rd) && (i < num_rd + num_wr); }

			/**
			 * Return true if fd set index should be watched for exceptions
			 */
			bool watch_for_ex(unsigned i) const {
				return (i >= num_rd + num_wr) && (i < total_fds()); }
		};

		struct Select_timeout
		{
			long sec, usec;

			/**
			 * Set timeout to infinity
			 */
			void set_infinite() { sec = -1; usec = -1; }

			/**
			 * Return true if the timeout is infinite
			 */
			bool infinite() const { return (sec == -1) && (usec == -1); }

			/**
			 * Return true if the timeout is zero
			 */
			bool zero() const { return (sec == 0) && (usec == 0); }
		};

		enum General_error { ERR_FD_INVALID, NUM_GENERAL_ERRORS };
		enum Stat_error    { STAT_ERR_NO_ENTRY     = NUM_GENERAL_ERRORS };
		enum Fcntl_error   { FCNTL_ERR_CMD_INVALID = NUM_GENERAL_ERRORS };
		enum Open_error    { OPEN_ERR_UNACCESSIBLE = NUM_GENERAL_ERRORS };
		enum Execve_error  { EXECVE_NONEXISTENT    = NUM_GENERAL_ERRORS };

		union {
			General_error general;
			Stat_error    stat;
			Fcntl_error   fcntl;
			Open_error    open;
			Execve_error  execve;
		} error;

		union {

			SYSIO_DECL(getcwd, { }, { Path path; });

			SYSIO_DECL(write,  { int fd; size_t count; Chunk chunk; }, { });

			SYSIO_DECL(stat,   { Path path; }, { Stat st; });

			SYSIO_DECL(fstat,  { int fd; }, { Stat st; });

			SYSIO_DECL(fcntl,  { int fd; long long_arg; Fcntl_cmd cmd; },
			                   { int result; });

			SYSIO_DECL(open,   { Path path; int mode; }, { int fd; });

			SYSIO_DECL(close,  { int fd; }, { });

			SYSIO_DECL(ioctl,  : Ioctl_in { int fd; }, : Ioctl_out { });

			SYSIO_DECL(dirent, { int fd; int index; }, { Dirent entry; });

			SYSIO_DECL(fchdir, { int fd; }, { });

			SYSIO_DECL(read,   { int fd; size_t count; },
			                   { Chunk chunk; size_t count; });

			SYSIO_DECL(execve, { Path filename; Args args; Env env; }, { });

			SYSIO_DECL(select, { Select_fds fds; Select_timeout timeout; },
			                   { Select_fds fds; });

			SYSIO_DECL(fork,   { addr_t ip; addr_t sp;
			                     addr_t parent_cap_addr; },
			                   { int pid; });

			SYSIO_DECL(getpid, { }, { int pid; });

			SYSIO_DECL(wait4,  { int pid; bool nohang; },
			                   { int pid; int status; });
			SYSIO_DECL(pipe,   { }, { int fd[2]; });

			SYSIO_DECL(dup2,   { int fd; int to_fd; }, { });
		};
	};
};

#undef SYSIO_DECL

#endif /* _INCLUDE__NOUX_SESSION__SYSIO_H_ */
