/*
 *  linux/ibcs/socket.c
 *
 *  Copyright (C) 1994  Mike Jagdis (jaggy@purplet.demon.co.uk)
 *
 * $Id$
 * $Source$
 */

#include <linux/types.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/ptrace.h>
#include <linux/net.h>
#include <linux/socket.h>
#include <linux/sockfunc.h>
#include <linux/sys.h>
#include <asm/segment.h>

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



int
ibcs_setsockopt(unsigned long *sp)
{
	int error;
	int fd, level, optname;
	char *optval;
	int optlen;
	struct file *file;
	struct inode *inode;

	error = verify_area(VERIFY_READ,
			((unsigned long *)sp),
			5*sizeof(long));
	if (error)
		return error;

	fd = (int)get_fs_long(((unsigned long *)sp)+0);
	if (fd < 0 || fd >= NR_OPEN
	|| !(file = current->filp[fd])
	|| !(inode = file->f_inode))
		return -EBADF;
	if (!inode->i_socket)
		return -ENOTSOCK;

	/* As far as I can see SCO doesn't support anything other than
	 * socket level options and uses 0xffff for these.
	 * XXXXX This may need mapping.
	 */
	level = (int)get_fs_long(((unsigned long *)sp)+1);
	if (level != 0xffff)
		return -ENOPROTOOPT;

	optname = (int)get_fs_long(((unsigned long *)sp)+2);
	optval = (char *)get_fs_long(((unsigned long *)sp)+3);
	optlen = (int)get_fs_long(((unsigned long *)sp)+4);

	optname = map_value(sopt_map, optname, 0);
	switch (optname) {
/* The following are all supported by Linux and seem to take compatible
 * arguments.
 */
		case SO_DEBUG:
		case SO_REUSEADDR:
		case SO_TYPE:
		case SO_ERROR:
		case SO_DONTROUTE:
		case SO_BROADCAST:
		case SO_SNDBUF:
		case SO_RCVBUF:
		case SO_KEEPALIVE:
		case SO_OOBINLINE:
		case SO_NO_CHECK:
		case SO_PRIORITY:
		case SO_LINGER:
		case SO_ACCEPTCONN:
			return sock_setsockopt(fd, SOL_SOCKET, optname,
					optval, optlen);

/* The following are not currently implemented under Linux so we
 * must fake them in reasonable ways. (Only SO_PROTOTYPE is documented
 * in SCO's man page).
 */

		case SO_PROTOTYPE:
		case SO_ORDREL:
		case SO_SNDTIMEO:
		case SO_RCVTIMEO:
			return -ENOPROTOOPT;

		case SO_USELOOPBACK:
		case SO_SNDLOWAT:
		case SO_RCVLOWAT:
			return 0;

/* The following are not currenty implemented under Linux and probably
 * aren't settable anyway.
 */
		case SO_IMASOCKET:
			return -ENOPROTOOPT;
	}

	return -ENOPROTOOPT;
}


int
ibcs_getsockopt(unsigned long *sp)
{
	int error;
	int fd, level, optname;
	char *optval;
	int optlen;
	struct file *file;
	struct inode *inode;

	error = verify_area(VERIFY_READ,
			((unsigned long *)sp),
			5*sizeof(long));
	if (error)
		return error;

	fd = (int)get_fs_long(((unsigned long *)sp)+0);
	if (fd < 0 || fd >= NR_OPEN
	|| !(file = current->filp[fd])
	|| !(inode = file->f_inode))
		return -EBADF;
	if (!inode->i_socket)
		return -ENOTSOCK;

	/* As far as I can see SCO doesn't support anything other than
	 * socket level options and uses 0xffff for these.
	 * XXXXX This may need mapping.
	 */
	level = (int)get_fs_long(((unsigned long *)sp)+1);
	if (level != 0xffff)
		return -ENOPROTOOPT;

	optname = (int)get_fs_long(((unsigned long *)sp)+2);
	optval = (char *)get_fs_long(((unsigned long *)sp)+3);
	optlen = (int)get_fs_long(((unsigned long *)sp)+4);

	optname = map_value(sopt_map, optname, 0);
	switch (optname) {
/* The following are all supported by Linux and seem to take compatible
 * arguments.
 */
		case SO_DEBUG:
		case SO_REUSEADDR:
		case SO_TYPE:
		case SO_ERROR:
		case SO_DONTROUTE:
		case SO_BROADCAST:
		case SO_SNDBUF:
		case SO_RCVBUF:
		case SO_KEEPALIVE:
		case SO_OOBINLINE:
		case SO_NO_CHECK:
		case SO_PRIORITY:
		case SO_LINGER:
		case SO_ACCEPTCONN:
			return sock_getsockopt(fd, SOL_SOCKET, optname,
					optval, optlen);

/* The following are not currently implemented under Linux so we
 * must fake them in reasonable ways. (Only SO_PROTOTYPE is documented
 * in SCO's man page).
 */

		case SO_PROTOTYPE:
			error = verify_area(VERIFY_WRITE, (char *)optval,
					sizeof(long));
			if (!error)
				put_fs_long(0, optval);
			return error;

		case SO_ORDREL:
		case SO_SNDTIMEO:
		case SO_RCVTIMEO:
			return -ENOPROTOOPT;

		case SO_USELOOPBACK:
		case SO_SNDLOWAT:
		case SO_RCVLOWAT:
		case SO_IMASOCKET:
			error = verify_area(VERIFY_WRITE, (char *)optval,
					sizeof(long));
			if (!error)
				put_fs_long(1, optval);
			return error;
	}

	return -ENOPROTOOPT;
}
