/*
 *  linux/ibcs/socksys.h
 *
 *  Copyright (C) 1994  Mike Jagdis (jaggy@purplet.demon.co.uk)
 *
 * $Id: socksys.c,v 1.4 1994/02/23 16:44:50 mike Exp $
 * $Source: /var/CVS/ibcs/socksys.c,v $
 */

/* This is designed to work with Net2Debugged. It requires patches to
 * Net2Debugged. It will almost certainly not work with Net2E without
 * a fair amount of work.
 */

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/fcntl.h>
#include <linux/major.h>
#include <asm/segment.h>
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/sched.h>
#include <linux/signal.h>
#include <linux/socket.h>
#include <linux/sockfunc.h>
#include <linux/utsname.h>
#include <linux/time.h>
#include <linux/termios.h>
#include <linux/sys.h>
#include <linux/config.h>

#include <ibcs/ibcs.h>
#include <ibcs/map.h>

#ifndef CONFIG_BINFMT_IBCS
#include <linux/module.h>
#endif

#ifdef IBCS_TRACE
#include <ibcs/trace.h>
#endif

#include "socksys.h"


/* socksys_fops defines the file operations that can be applied to the
 * /dev/socksys device. socksys_socket_fops defines the file operations
 * that can be applied to sockets themselves.
 */

static int socksys_ioctl(struct inode *inode, struct file *file,
		unsigned int cmd, unsigned long arg);
static int socksys_open(struct inode *ino, struct file *filep);
static void socksys_close(struct inode *ino, struct file *filep);

static struct file_operations socksys_fops = {
	NULL,			/* lseek */
	NULL,			/* read */
	NULL,			/* write */
	NULL,			/* readdir */
	NULL,			/* select */
	socksys_ioctl,		/* ioctl */
	NULL,			/* mmap */
	socksys_open,		/* open */
	socksys_close,		/* close */
	NULL			/* fsync */
};

static struct file_operations socksys_socket_fops = {
	sock_lseek,		/* lseek */
	sock_read,		/* read */
	sock_write,		/* write */
	sock_readdir,		/* readdir */
	sock_select,		/* select */
	socksys_ioctl,		/* ioctl */
	NULL,			/* mmap */
	NULL,			/* open */
	socksys_close,		/* close */
	NULL			/* fsync */
};


static inline int
socksys_syscall(struct inode *inode, struct file *file, int *sp)
{
	int cmd;

	cmd = (int)get_fs_long(sp);
	sp++;

#ifdef SOCKSYS_DEBUG
	{
		int a0, a1, a2, a3, a4, a5;
		static char *cmd_map[] = {
			"", "accept", "bind", "connect", "getpeername",
			"getsockname", "getsockopt", "listen", "recv",
			"recvfrom", "send", "sendto", "setsockopt", "shutdown",
			"socket", "select", "getipdomain", "setipdomain",
			"adjtime", "setreuid", "setregid", "gettimeofday",
			"settimeofday", "getitimer", "setitimer"
		};

		a0 = (int)get_fs_long(sp+0);
		a1 = (int)get_fs_long(sp+1);
		a2 = (int)get_fs_long(sp+2);
		a3 = (int)get_fs_long(sp+3);
		a4 = (int)get_fs_long(sp+4);
		a5 = (int)get_fs_long(sp+5);
		printk(KERN_DEBUG "socksys: [%d] %lx: %s (%d) <%d,%d,%d,%d,%d,%d>\n",
			current->pid, (unsigned long)file,
			cmd_map[cmd], cmd, a0, a1, a2, a3, a4, a5);
	}
#endif

	switch (cmd) {
		case SSYS_SO_SOCKET: {
			/* Get a socket but replace the socket file
			 * operations with our own so we can do the
			 * right thing for ioctls.
			 */
			int fd;

			put_fs_long(
				map_value(dom_map,
					get_fs_long(((unsigned long *)sp)+0),
					0),
				((unsigned long *)sp)+0);
			put_fs_long(
				map_value(af_map,
					get_fs_long(((unsigned long *)sp)+1),
					0),
				((unsigned long *)sp)+1);
			if ((fd = sys_socketcall(SYS_SOCKET, sp)) < 0)
				return fd;
			current->filp[fd]->f_op = &socksys_socket_fops;
#ifndef CONFIG_BINFMT_IBCS
			MOD_INC_USE_COUNT;
#endif
			return fd;
		}

		case SSYS_SO_ACCEPT:
			return sys_socketcall(SYS_ACCEPT, sp);
		case SSYS_SO_BIND:
			return sys_socketcall(SYS_BIND, sp);
		case SSYS_SO_CONNECT:
			return sys_socketcall(SYS_CONNECT, sp);
		case SSYS_SO_GETPEERNAME:
			return sys_socketcall(SYS_GETPEERNAME, sp);
		case SSYS_SO_GETSOCKNAME:
			return sys_socketcall(SYS_GETSOCKNAME, sp);
		case SSYS_SO_GETSOCKOPT:
			return ibcs_getsockopt((unsigned long *)sp);
		case SSYS_SO_LISTEN:
			return sys_socketcall(SYS_LISTEN, sp);
		case SSYS_SO_RECV:
			return sys_socketcall(SYS_RECV, sp);
		case SSYS_SO_RECVFROM:
			return sys_socketcall(SYS_RECVFROM, sp);
		case SSYS_SO_SEND:
			return sys_socketcall(SYS_SEND, sp);
		case SSYS_SO_SENDTO:
			return sys_socketcall(SYS_SENDTO, sp);
		case SSYS_SO_SETSOCKOPT:
			return ibcs_setsockopt((unsigned long *)sp);
		case SSYS_SO_SHUTDOWN:
			return sys_socketcall(SYS_SHUTDOWN, sp);

		case SSYS_SO_GETIPDOMAIN: {
			int error, len;
			char *name, *p;

			if ((error = verify_area(VERIFY_READ, sp, 2*sizeof(long))))
				return error;
			name = (char *)get_fs_long(sp+0);
			len = (int)get_fs_long(sp+1);
			if ((error = verify_area(VERIFY_WRITE, name, len)))
				return error;

			--len;
			for (p=system_utsname.domainname; *p && len; p++,len--)
				put_fs_byte(*p, name++);
			*name = '\0';

			return (0);
		}
		case SSYS_SO_SETIPDOMAIN: {
			int error, len;
			char *name;

			if ((error = verify_area(VERIFY_READ, sp, 2*sizeof(long))))
				return error;
			name = (char *)get_fs_long(sp+0);
			len = (int)get_fs_long(sp+1);
			return sys_setdomainname(name, len);
		}

		case SSYS_SO_SETREUID:
		case SSYS_SO_SETREGID: {
			int error;
			uid_t ruid, euid;

			if ((error = verify_area(VERIFY_READ, sp, 2*sizeof(long))))
				return error;
			ruid = (uid_t)get_fs_long(sp+0);
			euid = (uid_t)get_fs_long(sp+1);
			return (cmd == SSYS_SO_SETREUID)
				? sys_setreuid(ruid, euid)
				: sys_setregid(ruid, euid);
		}

		case SSYS_SO_GETTIME:
		case SSYS_SO_SETTIME: {
			int error;
			struct timeval *tv;
			struct timezone *tz;

			if ((error = verify_area(VERIFY_READ, sp, 2*sizeof(long))))
				return error;
			tv = (struct timeval *)get_fs_long(sp+0);
			tz = (struct timezone *)get_fs_long(sp+1);
			return (cmd == SSYS_SO_GETTIME)
				? sys_gettimeofday(tv, tz)
				: sys_settimeofday(tv, tz);
		}

		case SSYS_SO_GETITIMER: {
			int error, which;
			struct itimerval *value;

			if ((error = verify_area(VERIFY_READ, sp, 2*sizeof(long))))
				return error;
			which = (int)get_fs_long(sp+0);
			value = (struct itimerval *)get_fs_long(sp+1);
			return sys_getitimer(which, value);
		}
		case SSYS_SO_SETITIMER: {
			int error, which;
			struct itimerval *value, *ovalue;

			if ((error = verify_area(VERIFY_READ, sp, 3*sizeof(long))))
				return error;
			which = (int)get_fs_long(sp+0);
			value = (struct itimerval *)get_fs_long(sp+1);
			ovalue = (struct itimerval *)get_fs_long(sp+2);
			return sys_setitimer(which, value, ovalue);
		}

		case SSYS_SO_SELECT:
			/* This may be wrong? I don't know how to trigger
			 * this case. Select seems to go via the Xenix
			 * select entry point.
			 */
			return sys_select(sp);

		case SSYS_SO_ADJTIME:
			return -EINVAL;
	}
}


static int
socksys_ioctl(struct inode *inode, struct file *file,
		unsigned int cmd, unsigned long arg)
{
	switch (cmd) {
		case SSYS_SIOCSOCKSYS:		/* Pseudo socket syscall */
			return socksys_syscall(inode, file, (int *)arg);

		case SSYS_SIOCSHIWAT:		/* set high watermark */
		case SSYS_SIOCGHIWAT:		/* get high watermark */
		case SSYS_SIOCSLOWAT:		/* set low watermark */
		case SSYS_SIOCGLOWAT:		/* get low watermark */
			return -EPERM;

		case SSYS_SIOCATMARK:		/* at oob mark? */
			return sock_ioctl(inode, file, SIOCATMARK, arg);

		case SSYS_SIOCSPGRP:		/* set process group */
			return sock_ioctl(inode, file, SIOCSPGRP, arg);
		case SSYS_SIOCGPGRP:		/* get process group */
			return sock_ioctl(inode, file, SIOCGPGRP, arg);

		case SSYS_FIONREAD: {		/* BSD compatibilty */
			int error;

			error = sock_ioctl(inode, file, TIOCINQ, arg);
#ifdef IBCS_TRACE
			if (!error)
				printk(KERN_DEBUG
					"iBCS: socksys FIONREAD found %lu bytes ready\n",
					(unsigned long)get_fs_long(arg));
#endif
			return error;
		}

		case SSYS_FIONBIO: {		/* BSD compatibilty */
			int on = get_fs_long((unsigned long *)arg);
			if (on)
				file->f_flags |= O_NONBLOCK;
			else
				file->f_flags &= ~O_NONBLOCK;
			return sock_ioctl(inode, file, FIONBIO, arg);
		}

		case SSYS_FIOASYNC: {		/* BSD compatibilty */
			int on = get_fs_long((unsigned long *)arg);
			if (on)
				file->f_flags |= O_SYNC;
			else
				file->f_flags &= ~O_SYNC;
			return sock_ioctl(inode, file, FIOASYNC, arg);
		}

		case SSYS_SIOCADDRT:		/* add route */
			return sock_ioctl(inode, file, SIOCADDRT, arg);
		case SSYS_SIOCDELRT:		/* delete route */
			return sock_ioctl(inode, file, SIOCDELRT, arg);

		case SSYS_SIOCSIFADDR:		/* set ifnet address */
			return sock_ioctl(inode, file, SIOCSIFADDR, arg);
		case SSYS_SIOCGIFADDR:		/* get ifnet address */
			return sock_ioctl(inode, file, SIOCGIFADDR, arg);

		case SSYS_SIOCSIFDSTADDR:	/* set p-p address */
			return sock_ioctl(inode, file, SIOCSIFDSTADDR, arg);
		case SSYS_SIOCGIFDSTADDR:	/* get p-p address */
			return sock_ioctl(inode, file, SIOCGIFDSTADDR, arg);

		case SSYS_SIOCSIFFLAGS:		/* set ifnet flags */
			return sock_ioctl(inode, file, SIOCSIFFLAGS, arg);
		case SSYS_SIOCGIFFLAGS:		/* get ifnet flags */
			return sock_ioctl(inode, file, SIOCGIFFLAGS, arg);

		case SSYS_SIOCGIFCONF:		/* get ifnet list */
			return sock_ioctl(inode, file, SIOCGIFCONF, arg);

		case SSYS_SIOCGIFBRDADDR:	/* get broadcast addr */
			return sock_ioctl(inode, file, SIOCGIFBRDADDR, arg);
		case SSYS_SIOCSIFBRDADDR:	/* set broadcast addr */
			return sock_ioctl(inode, file, SIOCSIFBRDADDR, arg);

		case SSYS_SIOCGIFNETMASK:	/* get net addr mask */
			return sock_ioctl(inode, file, SIOCGIFNETMASK, arg);
		case SSYS_SIOCSIFNETMASK:	/* set net addr mask */
			return sock_ioctl(inode, file, SIOCSIFNETMASK, arg);

		case SSYS_SIOCGIFMETRIC:	/* get IF metric */
			return sock_ioctl(inode, file, SIOCGIFMETRIC, arg);
		case SSYS_SIOCSIFMETRIC:	/* set IF metric */
			return sock_ioctl(inode, file, SIOCSIFMETRIC, arg);

		case SSYS_SIOCSARP:		/* set arp entry */
			return sock_ioctl(inode, file, SIOCSARP, arg);
		case SSYS_SIOCGARP:		/* get arp entry */
			return sock_ioctl(inode, file, SIOCGARP, arg);
		case SSYS_SIOCDARP:		/* delete arp entry */
			return sock_ioctl(inode, file, SIOCDARP, arg);

		case SSYS_SIOCGENADDR:		/* Get ethernet addr */
			return sock_ioctl(inode, file, SIOCGIFHWADDR, arg);

		case SSYS_SIOCSIFMTU:		/* get if_mtu */
			return sock_ioctl(inode, file, SIOCSIFMTU, arg);
		case SSYS_SIOCGIFMTU:		/* set if_mtu */
			return sock_ioctl(inode, file, SIOCGIFMTU, arg);

		case SSYS_SIOCGETNAME:		/* getsockname */
			return sock_ioctl(inode, file, SIOCGIFNAME, arg);

		case SSYS_SIOCGETPEER: {	/* getpeername */
			struct sockaddr uaddr;
			int uaddr_len, error, old_fs;
			struct socket *sock;

			if ((error = verify_area(VERIFY_WRITE, (char *)arg, sizeof(struct sockaddr))))
				return error;
			if (!(sock = socki_lookup(inode)))
				return -ENOTSOCK;
			old_fs = get_fs();
			set_fs (get_ds());
			error = sock->ops->getname(sock, &uaddr, &uaddr_len, 1);
			set_fs(old_fs);
			if (error >= 0)
				memcpy_tofs(&uaddr, (char *)arg, sizeof(struct sockaddr));
			return error;
		}

		case SSYS_IF_UNITSEL:		/* set unit number */
		case SSYS_SIOCXPROTO:		/* empty proto table */

		case SSYS_SIOCIFDETACH:		/* detach interface */
		case SSYS_SIOCGENPSTATS:	/* get ENP stats */

		case SSYS_SIOCSIFNAME:		/* set interface name */
		case SSYS_SIOCGIFONEP:		/* get one-packet params */
		case SSYS_SIOCSIFONEP:		/* set one-packet params */

		case SSYS_SIOCPROTO:		/* link proto */
		case SSYS_SIOCX25XMT:
		case SSYS_SIOCX25RCV:
		case SSYS_SIOCX25TBL:

		default:
			printk(KERN_DEBUG "socksys: [%d] %lx: ioctl 0x%x with argument 0x%lx requested\n",
				current->pid, (unsigned long)file,
				cmd, (unsigned long)arg);
			break;
	}

	return -EINVAL;
}


static int
socksys_open(struct inode *ino, struct file *filep)
{
#ifndef CONFIG_BINFMT_IBCS
	MOD_INC_USE_COUNT;
#endif
#ifdef SOCKSYS_DEBUG
	printk(KERN_DEBUG "socksys: [%d] %lx opening\n",
		current->pid, (unsigned long)filep);
#endif
	return 0;
}


static void
socksys_close(struct inode *ino, struct file *filep)
{
#ifndef CONFIG_BINFMT_IBCS
	MOD_DEC_USE_COUNT;
#endif
	if (ino->i_socket)
		sock_close(ino, filep);

#ifdef SOCKSYS_DEBUG
	printk(KERN_DEBUG "socksys: [%d] %lx closed\n",
		current->pid, (unsigned long)filep);
#endif
}


static int socksys_major;


void
init_socksys(void)
{
	/* N.B. this is coded to allow for the possibility of auto
	 * assignment of major number by passing 0 and seeing what we
	 * get back. This isn't possible since I haven't reworked the
	 * device subsystems yet :-).
	 */
	socksys_major = register_chrdev(SOCKSYS_MAJOR, "socksys", &socksys_fops);
	if (socksys_major < 0) {
		printk(KERN_INFO "iBCS: couldn't register socksys on character major %d\n",
			SOCKSYS_MAJOR);
		if (socksys_major == 0 && (socksys_major = SOCKSYS_MAJOR) == 0)
			printk(KERN_NOTICE "iBCS: auto allocation of major numbers is not supported\n");
	} else {
		if (!socksys_major)
			socksys_major = SOCKSYS_MAJOR;
		printk(KERN_INFO "iBCS: socksys registered on character major %d\n", socksys_major);
	}
}


#ifndef CONFIG_BINFMT_IBCS
void
cleanup_socksys(void)
{
	/* Remove the socksys socket interface to streams based TCP/IP */
	if (socksys_major > 0 && unregister_chrdev(socksys_major, "socksys") != 0)
		printk(KERN_NOTICE "iBCS: couldn't unregister socksys device!\n");
}
#endif
