/*
 *  linux/ibcs/socksys.h
 *
 *  Copyright (C) 1994  Mike Jagdis (jaggy@purplet.demon.co.uk)
 *
 * $Id: socksys.c,v 1.19 1994/05/26 16:20:02 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/in.h>
#include <linux/net.h>
#include <linux/sched.h>
#include <linux/signal.h>
#include <linux/socket.h>
#include <linux/un.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 <ibcs/socksys.h>

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);


/* socksys_fops defines the file operations that can be applied to the
 * /dev/socksys device.
 */
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 */
};

/* socksys_socket_fops defines the file operations that can be applied to
 * sockets themselves. This gets initialised when the first socket is
 * created.
 */
static struct file_operations socksys_socket_fops = {
	NULL,			/* lseek */
	NULL,			/* read */
	NULL,			/* write */
	NULL,			/* readdir */
	NULL,			/* select */
	NULL,			/* ioctl */
	NULL,			/* mmap */
	NULL,			/* open */
	NULL,			/* close */
	NULL			/* fsync */
};
static void (*sock_close)(struct inode *inode, struct file *file);
static int (*sock_ioctl)(struct inode *inode, struct file *file,
			unsigned int cmd, unsigned long arg);


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

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

#ifdef IBCS_TRACE
	if (ibcs_trace & TRACE_SOCKSYS) {
		unsigned long 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 = (unsigned long)get_fs_long(sp+0);
		a1 = (unsigned long)get_fs_long(sp+1);
		a2 = (unsigned long)get_fs_long(sp+2);
		a3 = (unsigned long)get_fs_long(sp+3);
		a4 = (unsigned long)get_fs_long(sp+4);
		a5 = (unsigned long)get_fs_long(sp+5);
		printk(KERN_DEBUG "socksys: [%d] %lx: %s (%d) <0x%lx,0x%lx,0x%lx,0x%lx,0x%lx,0x%lx>\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(af_map,
					get_fs_long(((unsigned long *)sp)+0),
					0),
 				((unsigned long *)sp)+0);
			put_fs_long(
				map_value(type_map,
					get_fs_long(((unsigned long *)sp)+1),
					0),
 				((unsigned long *)sp)+1);

			if ((fd = SYS(socketcall)(SYS_SOCKET, sp)) < 0)
				return fd;

			/* If our file operations don't appear to match
			 * what the socket system is advertising it may
			 * be because we haven't initialised ours at all
			 * yet or it may be because the old socket system
			 * module was unloaded and reloaded. This isn't
			 * entirely safe because we may still have open
			 * sockets which *should* use the old routines
			 * until they close - tough, for now.
			 */
			if (socksys_socket_fops.read != current->FD[fd]->f_op->read) {
				memcpy(&socksys_socket_fops,
					current->FD[fd]->f_op,
					sizeof(struct file_operations));
				sock_ioctl = socksys_socket_fops.ioctl;
				sock_close = socksys_socket_fops.release;
				socksys_socket_fops.ioctl = socksys_ioctl;
				socksys_socket_fops.release = socksys_close;
			}
			current->FD[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: {
#ifndef INIT_MM
			struct sockaddr *sa;

			/* Trap attack... Some things (notably SVR
			 * syslog code) seems to think 0.0.0.0 is
			 * a valid address for localhost.
			 */
			sa = (struct sockaddr *)get_fs_long(sp+1);
			if (get_fs_word(&sa->sa_family) == AF_INET) {
				unsigned long *addr;

				addr = &(((struct sockaddr_in *)sa)->sin_addr.s_addr);
				if (get_fs_long(addr) == INADDR_ANY) {
					put_fs_long(0x0100007f, addr);
#ifdef IBCS_TRACE
					if (ibcs_trace & TRACE_SOCKSYS)
						printk(KERN_DEBUG "iBCS: socksys: remapped INADDR_ANY to localhost\n");
#endif
				}
			}
#endif
			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 0x5301: /* also used sometimes? == SIOCSHIWAT */
		case SSYS_SIOCSHIWAT:		/* set high watermark */
		case SSYS_SIOCSLOWAT:		/* set low watermark */
		case SSYS_SIOCGHIWAT:		/* get high watermark */
		case SSYS_SIOCGLOWAT:		/* get low watermark */
			return -EINVAL;

		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 && (ibcs_trace & TRACE_SOCKSYS))
				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;

			if ((error = verify_area(VERIFY_WRITE, (char *)arg, sizeof(struct sockaddr))))
				return error;
			if (!inode->i_socket)
				return -ENOTSOCK;
			old_fs = get_fs();
			set_fs (get_ds());
			error = inode->i_socket->ops->getname(inode->i_socket,
							&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 IBCS_TRACE
	if (ibcs_trace & TRACE_SOCKSYS)
		printk(KERN_DEBUG "socksys: [%d] %lx opening\n",
			current->pid, (unsigned long)filep);
#endif
	/* Minor = 0 is the socksys device itself. No special handling
	 *           will be needed as it is controlled by the application
	 *           via ioctls.
	 */
	if (MINOR(ino->i_rdev) == 0)
		return 0;

	/* Minor = 1 is the spx device. This is the client side of a
	 *           streams pipe to the X server. Under SCO and friends
	 *           the library code messes around setting the connection
	 *           up itself. We do it ourselves - this means we don't
	 *           need to worry about the implementation of the server
	 *           side (/dev/X0R - which must exist but can be a link
	 *           to /dev/null) nor do we need to actually implement
	 *           getmsg/putmsg.
	 */
{ /* SPX */
	int fd, err, args[3];
	struct sockaddr_un Xaddr = {
		AF_UNIX, "/tmp/.X11-unix/X0"
	};
	int old_fs = get_fs();

#ifdef IBCS_TRACE
	if (ibcs_trace & TRACE_SOCKSYS)
		printk(KERN_DEBUG "SPX: [%d] %lx opening\n",
			current->pid, (unsigned long)filep);
#endif

	/* Grab a socket. */
#ifdef IBCS_TRACE
	if (ibcs_trace & TRACE_SOCKSYS)
		printk(KERN_DEBUG "SPX: [%d] %lx get a Unix domain socket\n",
			current->pid, (unsigned long)filep);
#endif
	args[0] = AF_UNIX;
	args[1] = SOCK_STREAM;
	args[2] = 0;
	set_fs(get_ds());
	fd = SYS(socketcall)(SYS_SOCKET, args);
	set_fs(old_fs);
	if (fd < 0) {
#ifndef CONFIG_BINFMT_IBCS
		MOD_DEC_USE_COUNT;
#endif
		return fd;
	}

	/* Connect the socket to X. */
#ifdef IBCS_TRACE
	if (ibcs_trace & TRACE_SOCKSYS)
		printk(KERN_DEBUG "SPX: [%d] %lx connect to /tmp/X11-unix/X0\n",
			current->pid, (unsigned long)filep);
#endif
	set_fs (get_ds());
	args[0] = fd;
	args[1] = (int)&Xaddr;
	args[2] = sizeof(struct sockaddr_un);
	err = SYS(socketcall)(SYS_CONNECT, args);
	set_fs(old_fs);
	if (err) {
		SYS(close)(fd);
#ifndef CONFIG_BINFMT_IBCS
		MOD_DEC_USE_COUNT;
#endif
		return err;
	}

	/* If our file operations don't appear to match
	 * what the socket system is advertising it may
	 * be because we haven't initialised ours at all
	 * yet or it may be because the old socket system
	 * module was unloaded and reloaded. This isn't
	 * entirely safe because we may still have open
	 * sockets which *should* use the old routines
	 * until they close - tough, for now.
	 */
	if (socksys_socket_fops.read != current->FD[fd]->f_op->read) {
		memcpy(&socksys_socket_fops,
			current->FD[fd]->f_op,
			sizeof(struct file_operations));
		sock_ioctl = socksys_socket_fops.ioctl;
		sock_close = socksys_socket_fops.release;
		socksys_socket_fops.ioctl = socksys_ioctl;
		socksys_socket_fops.release = socksys_close;
	}

	/* Release the inode we were given, replace with the socket
	 * inode, redirect operations to our emulation handlers then
	 * clear down the descriptor used for the socket.
	 */
#ifdef IBCS_TRACE
	if (ibcs_trace & TRACE_SOCKSYS)
		printk(KERN_DEBUG "SPX: [%d] %lx swap inodes\n",
			current->pid, (unsigned long)filep);
#endif
	iput(ino);
	filep->f_inode = current->FD[fd]->f_inode;
	filep->f_op = &socksys_socket_fops;
	if (--current->FD[fd]->f_count == 0)
		current->FD[fd] = NULL;

	return 0;
} /* SPX */
}


static void
socksys_close(struct inode *ino, struct file *filep)
{
	if (ino && ino->i_socket)
		sock_close(ino, filep);

#ifdef IBCS_TRACE
	if (ibcs_trace & TRACE_SOCKSYS)
		printk(KERN_DEBUG "socksys: [%d] %lx closed\n",
			current->pid, (unsigned long)filep);
#endif
#ifndef CONFIG_BINFMT_IBCS
	MOD_DEC_USE_COUNT;
#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_ERR "iBCS: couldn't register socksys on character major %d\n",
			SOCKSYS_MAJOR);
		if (socksys_major == 0 && (socksys_major = SOCKSYS_MAJOR) == 0)
			printk(KERN_ERR "iBCS: auto allocation of major numbers is not supported\n");
	} else {
		if (!socksys_major)
			socksys_major = SOCKSYS_MAJOR;
#ifdef IBCS_TRACE
		printk(KERN_INFO "iBCS: socksys registered on character major %d\n", socksys_major);
#endif
	}
}


#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_ERR "iBCS: couldn't unregister socksys device!\n");
}
#endif
