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

#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 = 4*1024 };
		typedef char Args[ARGS_MAX_LEN];

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

		typedef __SIZE_TYPE__ size_t;
		typedef long int ssize_t;

		/**
		 * Flags of 'mode' argument of open syscall
		 */
		enum {
			OPEN_MODE_RDONLY  = 0,
			OPEN_MODE_WRONLY  = 1,
			OPEN_MODE_RDWR    = 2,
			OPEN_MODE_ACCMODE = 3,
			OPEN_MODE_CREATE  = 0x0800, /* libc O_EXCL */
		};

		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, OP_TIOCSETAF,
			              OP_TIOCSETAW, OP_FIONBIO };

			enum Val    { VAL_NULL, VAL_ECHO, VAL_ECHONL };

			Opcode request;
			int argp;
		};

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

		enum Lseek_whence { LSEEK_SET, LSEEK_CUR, LSEEK_END };

		enum { DIRENT_MAX_NAME_LEN = 128 };

		enum Dirent_type {
			DIRENT_TYPE_FILE,
			DIRENT_TYPE_DIRECTORY,
			DIRENT_TYPE_FIFO,
			DIRENT_TYPE_CHARDEV,
			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_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); }
		};

		/**
		 * Socket related structures
		 */
		enum { MAX_HOSTNAME_LEN = 255 };
		typedef char Hostname[MAX_HOSTNAME_LEN];

		enum { MAX_SERVNAME_LEN = 255 };
		typedef char Servname[MAX_SERVNAME_LEN];

		enum { MAX_ADDRINFO_RESULTS = 4 };

		struct in_addr
		{
			unsigned int    s_addr;
		};

		struct sockaddr
		{
			unsigned char   sa_len;
			unsigned char   sa_family;
			char            sa_data[14];
		};

		struct sockaddr_in {
			unsigned char   sin_len;
			unsigned char   sin_family;
			unsigned short  sin_port;
			struct          in_addr sin_addr;
			char            sin_zero[8];
		};

		typedef unsigned        socklen_t;

		struct addrinfo {
			int              ai_flags;
			int              ai_family;
			int              ai_socktype;
			int              ai_protocol;
			socklen_t        ai_addrlen;
			struct sockaddr *ai_addr;
			char            *ai_canonname;
			struct addrinfo *ai_next;
		};

		struct Addrinfo {
			struct addrinfo  addrinfo;
			struct sockaddr  ai_addr;
			char             ai_canonname[255];
		};

		/**
		 * user info defintions
		 */
		enum { USERINFO_GET_ALL = 0, USERINFO_GET_UID, USERINFO_GET_GID };
		enum { MAX_USERNAME_LEN = 32 };
		typedef char User[MAX_USERNAME_LEN];
		enum { MAX_SHELL_LEN = 16 };
		typedef char Shell[MAX_SHELL_LEN];
		enum { MAX_HOME_LEN = 128 };
		typedef char Home[MAX_HOME_LEN];
		typedef unsigned int Uid;

		/**
		 * time/clock definitions
		 */
		enum Clock_Id        { CLOCK_ID_SECOND };

		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 Ftruncate_error { FTRUNCATE_ERR_NO_PERM = NUM_GENERAL_ERRORS };
		enum Open_error      { OPEN_ERR_UNACCESSIBLE, OPEN_ERR_NO_PERM,
		                       OPEN_ERR_EXISTS };
		enum Execve_error    { EXECVE_NONEXISTENT    = NUM_GENERAL_ERRORS };
		enum Unlink_error    { UNLINK_ERR_NO_ENTRY, UNLINK_ERR_NO_PERM };
		enum Readlink_error  { READLINK_ERR_NO_ENTRY };
		enum Rename_error    { RENAME_ERR_NO_ENTRY, RENAME_ERR_CROSS_FS,
		                       RENAME_ERR_NO_PERM };
		enum Mkdir_error     { MKDIR_ERR_EXISTS,   MKDIR_ERR_NO_ENTRY,
		                       MKDIR_ERR_NO_SPACE, MKDIR_ERR_NO_PERM,
		                       MKDIR_ERR_NAME_TOO_LONG};
		enum Symlink_error   { SYMLINK_ERR_EXISTS, SYMLINK_ERR_NO_ENTRY,
		                       SYMLINK_ERR_NAME_TOO_LONG};

		enum Read_error      { READ_ERR_AGAIN, READ_ERR_WOULD_BLOCK,
		                       READ_ERR_INVALID, READ_ERR_IO };

		enum Write_error     { WRITE_ERR_AGAIN, WRITE_ERR_WOULD_BLOCK,
		                       WRITE_ERR_INVALID, WRITE_ERR_IO };

		enum Ioctl_error     { IOCTL_ERR_INVALID, IOCTL_ERR_NOTTY };

		/**
		 * Socket related errors
		 */
		enum Accept_error    { ACCEPT_ERR_AGAIN, ACCEPT_ERR_WOULD_BLOCK,
		                       ACCEPT_ERR_INVALID, ACCEPT_ERR_NO_MEMORY,
		                       ACCEPT_ERR_NOT_SUPPORTED };

		enum Bind_error      { BIND_ERR_ACCESS, BIND_ERR_ADDR_IN_USE,
		                       BIND_ERR_INVALID, BIND_ERR_NO_MEMORY };

		enum Connect_error   { CONNECT_ERR_ACCESS, CONNECT_ERR_AGAIN,
		                       CONNECT_ERR_ALREADY, CONNECT_ERR_CONN_REFUSED,
		                       CONNECT_ERR_NO_PERM, CONNECT_ERR_ADDR_IN_USE,
		                       CONNECT_ERR_IN_PROGRESS, CONNECT_ERR_IS_CONNECTED };

		enum Listen_error    { LISTEN_ERR_ADDR_IN_USE, LISTEN_ERR_NOT_SUPPORTED };

		enum Recv_error      { RECV_ERR_AGAIN, RECV_ERR_WOULD_BLOCK,
		                       RECV_ERR_CONN_REFUSED, RECV_ERR_INVALID,
		                       RECV_ERR_NOT_CONNECTED, RECV_ERR_NO_MEMORY };

		enum Send_error      { SEND_ERR_AGAIN, SEND_ERR_WOULD_BLOCK,
		                       SEND_ERR_CONNECTION_RESET, SEND_ERR_INVALID,
		                       SEND_ERR_IS_CONNECTED, SEND_ERR_NO_MEMORY };

		enum Shutdown_error  { SHUTDOWN_ERR_NOT_CONNECTED };

		enum Socket_error    { SOCKET_ERR_ACCESS, SOCKET_ERR_NO_AF_SUPPORT,
		                       SOCKET_ERR_INVALID, SOCKET_ERR_NO_MEMORY };

		enum Clock_error     { CLOCK_ERR_INVALID, CLOCK_ERR_FAULT, CLOCK_ERR_NO_PERM };

		enum Utimes_error    { UTIMES_ERR_ACCESS, UTIMES_ERR_FAUL, UTIMES_ERR_EIO,
		                       UTIMES_ERR_NAME_TOO_LONG, UTIMES_ERR_NO_ENTRY,
		                       UTIMES_ERR_NOT_DIRECTORY, UTIMES_ERR_NO_PERM,
		                       UTIMES_ERR_READ_ONLY };

		union {
			General_error   general;
			Stat_error      stat;
			Fcntl_error     fcntl;
			Ftruncate_error ftruncate;
			Open_error      open;
			Execve_error    execve;
			Unlink_error    unlink;
			Readlink_error  readlink;
			Rename_error    rename;
			Mkdir_error     mkdir;
			Symlink_error   symlink;
			Read_error      read;
			Write_error     write;
			Ioctl_error     ioctl;
			Accept_error    accept;
			Bind_error      bind;
			Connect_error   connect;
			Listen_error    listen;
			Recv_error      recv;
			Send_error      send;
			Shutdown_error  shutdown;
			Socket_error    socket;
			Clock_error     clock;
			Utimes_error    utimes;
		} error;

		union {

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

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

			SYSIO_DECL(symlink,     { Path oldpath; Path newpath; }, { });

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

			SYSIO_DECL(ftruncate,   { int fd; off_t length; }, { });

			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(lseek,       { int fd; off_t offset; Lseek_whence whence; },
			                        { off_t offset; });

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

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

			SYSIO_DECL(readlink,    { Path path; size_t bufsiz; },
			                        { Chunk chunk; ssize_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; }, { int fd; });

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

			SYSIO_DECL(rename,      { Path from_path; Path to_path; }, { });

			SYSIO_DECL(mkdir,       { Path path; int mode; }, { });

			SYSIO_DECL(socket,      { int domain; int type; int protocol; },
			                        { int fd; });

			 /* XXX for now abuse Chunk for passing optval */
			SYSIO_DECL(getsockopt,  { int fd; int level; int optname; Chunk optval;
			                          socklen_t optlen; }, { int result; });

			SYSIO_DECL(setsockopt,  { int fd; int level;
			                          int optname; Chunk optval;
			                          socklen_t optlen; }, { });

			SYSIO_DECL(accept,      { int fd; struct sockaddr addr; socklen_t addrlen; },
			                        { int fd; });

			SYSIO_DECL(bind,        { int fd; struct sockaddr addr;
			                          socklen_t addrlen; }, { int result; });

			SYSIO_DECL(getpeername, { int fd; struct sockaddr addr;
			                          socklen_t addrlen; }, { });

			SYSIO_DECL(listen,      { int fd; int type; int backlog; },
			                        { int result; });

			SYSIO_DECL(send,        { int fd; Chunk buf; size_t len; int flags; },
			                        { ssize_t len; });

			SYSIO_DECL(sendto,      { int fd; Chunk buf; size_t len; int flags;
			                          struct sockaddr dest_addr; socklen_t addrlen; },
			                        { ssize_t len; });

			SYSIO_DECL(recv,        { int fd; Chunk buf; size_t len; int flags; },
			                        { size_t len; });

			SYSIO_DECL(recvfrom,    { int fd; Chunk buf; size_t len; int flags;
			                          struct sockaddr src_addr; socklen_t addrlen; },
			                        { size_t len; });

			SYSIO_DECL(shutdown,    { int fd; int how; }, { });

			SYSIO_DECL(connect,     { int fd; struct sockaddr addr; socklen_t addrlen; },
			                        { int result; });

			SYSIO_DECL(userinfo, { int request; Uid uid; },
			                     { User name; Uid uid; Uid gid; Shell shell;
			                       Home home; });

			SYSIO_DECL(gettimeofday, { }, { unsigned long sec; unsigned int usec; });

			SYSIO_DECL(clock_gettime, { Clock_Id clock_id; },
			                          { unsigned long sec; unsigned long nsec; });

			SYSIO_DECL(utimes,      { Path path; unsigned long sec; unsigned long usec; },
			                        { });
		};
	};
};

#undef SYSIO_DECL

#endif /* _INCLUDE__NOUX_SESSION__SYSIO_H_ */