#include <signal.h>
#include <errno.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/stat.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <linux/fcntl.h>
#include <termios.h>
#include "kern_sock.h"

static struct unix_proto_data {
	int refcnt;			/* cnt of reference 0=free */
	struct socket *socket;		/* socket we're bound to */
	int protocol;
	struct sockaddr_un sockaddr_un;
	short sockaddr_len;		/* >0 if name bound */
	char *buf;
	int bp_head, bp_tail;
	struct inode *inode;
	struct unix_proto_data *peerupd;
} unix_datas[NSOCKETS];
#define last_unix_data (unix_datas + NSOCKETS - 1)

#define UN_DATA(SOCK) ((struct unix_proto_data *)(SOCK)->data)
#define UN_PATH_OFFSET ((unsigned long)((struct sockaddr_un *)0)->sun_path)

/*
 * buffer size must be power of 2. buffer mgmt inspired by pipe code.
 * note that buffer contents can wraparound, and we can write one byte less
 * than full size to discern full vs empty.
 */
#define BUF_SIZE PAGE_SIZE
#define UN_BUF_AVAIL(UPD) (((UPD)->bp_head - (UPD)->bp_tail) & (BUF_SIZE-1))
#define UN_BUF_SPACE(UPD) ((BUF_SIZE-1) - UN_BUF_AVAIL(UPD))

static int unix_proto_init(void);
static int unix_proto_create(struct socket *sock, int protocol);
static int unix_proto_dup(struct socket *newsock, struct socket *oldsock);
static int unix_proto_release(struct socket *sock, struct socket *peer);
static int unix_proto_bind(struct socket *sock, struct sockaddr *umyaddr,
			   int sockaddr_len);
static int unix_proto_connect(struct socket *sock, struct sockaddr *uservaddr,
			      int sockaddr_len);
static int unix_proto_socketpair(struct socket *sock1, struct socket *sock2);
static int unix_proto_accept(struct socket *sock, struct socket *newsock);
static int unix_proto_getname(struct socket *sock, struct sockaddr *usockaddr,
			      int *usockaddr_len, int peer);
static int unix_proto_read(struct socket *sock, char *ubuf, int size,
			   int nonblock);
static int unix_proto_write(struct socket *sock, char *ubuf, int size,
			    int nonblock);
static int unix_proto_select(struct socket *sock, int which);
static int unix_proto_ioctl(struct socket *sock, unsigned int cmd,
			    unsigned long arg);

struct proto_ops unix_proto_ops = {
	unix_proto_init,
	unix_proto_create,
	unix_proto_dup,
	unix_proto_release,
	unix_proto_bind,
	unix_proto_connect,
	unix_proto_socketpair,
	unix_proto_accept,
	unix_proto_getname,
	unix_proto_read,
	unix_proto_write,
	unix_proto_select,
	unix_proto_ioctl
};

#ifdef SOCK_DEBUG
void
sockaddr_un_printk(struct sockaddr_un *sockun, int sockaddr_len)
{
	char buf[sizeof(sockun->sun_path) + 1];

	sockaddr_len -= UN_PATH_OFFSET;
	if (sockun->sun_family != AF_UNIX)
		printk("sockaddr_un: <BAD FAMILY: %d>\n", sockun->sun_family);
	else if (sockaddr_len <= 0 || sockaddr_len >= sizeof(buf)-1)
		printk("sockaddr_un: <BAD LENGTH: %d>\n", sockaddr_len);
	else {
		memcpy(buf, sockun->sun_path, sockaddr_len);
		buf[sockaddr_len] = '\0';
		printk("sockaddr_un: '%s'[%d]\n", buf,
		       sockaddr_len + UN_PATH_OFFSET);
	}
}
#endif

static struct unix_proto_data *
unix_data_lookup(struct sockaddr_un *sockun, int sockaddr_len)
{
	struct unix_proto_data *upd;

	for (upd = unix_datas; upd <= last_unix_data; ++upd) {
		if (upd->refcnt && upd->socket &&
		    upd->sockaddr_len == sockaddr_len &&
		    memcmp(&upd->sockaddr_un, sockun, sockaddr_len) == 0)
			return upd;
	}
	return NULL;
}

static struct unix_proto_data *
unix_data_alloc(void)
{
	struct unix_proto_data *upd;

	cli();
	for (upd = unix_datas; upd <= last_unix_data; ++upd) {
		if (!upd->refcnt) {
			upd->refcnt = 1;
			sti();
			upd->socket = NULL;
			upd->sockaddr_len = 0;
			upd->buf = NULL;
			upd->bp_head = upd->bp_tail = 0;
			upd->inode = NULL;
			upd->peerupd = NULL;
			return upd;
		}
	}
	sti();
	return NULL;
}

static inline void
unix_data_ref(struct unix_proto_data *upd)
{
	++upd->refcnt;
	PRINTK("unix_data_ref: refing data 0x%x (%d)\n", upd, upd->refcnt);
}

static void
unix_data_deref(struct unix_proto_data *upd)
{
	if (upd->refcnt == 1) {
		PRINTK("unix_data_deref: releasing data 0x%x\n", upd);
		if (upd->buf) {
			free_page((unsigned long)upd->buf);
			upd->buf = NULL;
			upd->bp_head = upd->bp_tail = 0;
		}
	}
	--upd->refcnt;
}

/*
 * upon a create, we allocate an empty protocol data, and grab a page to
 * buffer writes
 */
static int
unix_proto_create(struct socket *sock, int protocol)
{
	struct unix_proto_data *upd;

	PRINTK("unix_proto_create: socket 0x%x, proto %d\n", sock, protocol);
	if (protocol != 0) {
		PRINTK("unix_proto_create: protocol != 0\n");
		return -EINVAL;
	}
	if (!(upd = unix_data_alloc())) {
		printk("unix_proto_create: can't allocate buffer\n");
		return -ENOMEM;
	}
	if (!(upd->buf = (char *)get_free_page())) {
		printk("unix_proto_create: can't get page!\n");
		unix_data_deref(upd);
		return -ENOMEM;
	}
	upd->protocol = protocol;
	upd->socket = sock;
	UN_DATA(sock) = upd;
	PRINTK("unix_proto_create: allocated data 0x%x\n", upd);
	return 0;
}

static int
unix_proto_dup(struct socket *newsock, struct socket *oldsock)
{
	struct unix_proto_data *upd = UN_DATA(oldsock);

	return unix_proto_create(newsock, upd->protocol);
}

static int
unix_proto_release(struct socket *sock, struct socket *peer)
{
	struct unix_proto_data *upd = UN_DATA(sock);

	PRINTK("unix_proto_release: socket 0x%x, unix_data 0x%x\n",
	       sock, upd);
	if (!upd)
		return 0;
	if (upd->socket != sock) {
		printk("unix_proto_release: socket link mismatch!\n");
		return -EINVAL;
	}
	if (upd->inode) {
		PRINTK("unix_proto_release: releasing inode 0x%x\n",
		       upd->inode);
		iput(upd->inode);
		upd->inode = NULL;
	}
	UN_DATA(sock) = NULL;
	upd->socket = NULL;
	if (upd->peerupd)
		unix_data_deref(upd->peerupd);
	unix_data_deref(upd);
	return 0;
}

/*
 * bind a name to a socket. this is where much of the work is done. we
 * allocate a fresh page for the buffer, grab the appropriate inode and
 * set things up.
 *
 * XXX what should we do if an address is already bound? here we return
 * EINVAL, but it may be necessary to re-bind. i think thats what bsd does
 * in the case of datagram sockets
 */
static int
unix_proto_bind(struct socket *sock, struct sockaddr *umyaddr,
		int sockaddr_len)
{
	struct unix_proto_data *upd = UN_DATA(sock);
	char fname[sizeof(((struct sockaddr_un *)0)->sun_path) + 1];
	int i;
	unsigned long old_fs;

	PRINTK("unix_proto_bind: socket 0x%x, len=%d\n", sock,
	       sockaddr_len);
	if (sockaddr_len <= UN_PATH_OFFSET ||
	    sockaddr_len >= sizeof(struct sockaddr_un)) {
		PRINTK("unix_proto_bind: bad length %d\n", sockaddr_len);
		return -EINVAL;
	}
	if (upd->sockaddr_len || upd->inode) {
		printk("unix_proto_bind: already bound!\n");
		return -EINVAL;
	}
	verify_area(umyaddr, sockaddr_len);
	memcpy_fromfs(&upd->sockaddr_un, umyaddr, sockaddr_len);
	if (upd->sockaddr_un.sun_family != AF_UNIX) {
		PRINTK("unix_proto_bind: family is %d, not AF_UNIX (%d)\n",
		       upd->sockaddr_un.sun_family, AF_UNIX);
		return -EINVAL;
	}

	memcpy(fname, upd->sockaddr_un.sun_path, sockaddr_len-UN_PATH_OFFSET);
	fname[sockaddr_len-UN_PATH_OFFSET] = '\0';
	old_fs = get_fs();
	set_fs(get_ds());
	i = do_mknod(fname, S_IFSOCK | 0777, 0);
	if (i == 0)
		i = open_namei(fname, 0, S_IFSOCK, &upd->inode);
	set_fs(old_fs);
	if (i < 0) {
		printk("unix_proto_bind: can't open socket %s\n", fname);
		return i;
	}

	upd->sockaddr_len = sockaddr_len;	/* now its legal */
	PRINTK("unix_proto_bind: bound socket address: ");
#ifdef SOCK_DEBUG
	sockaddr_un_printk(&upd->sockaddr_un, upd->sockaddr_len);
#endif
	return 0;
}

/*
 * perform a connection. we can only connect to unix sockets (i can't for
 * the life of me find an application where that wouldn't be the case!)
 */
static int
unix_proto_connect(struct socket *sock, struct sockaddr *uservaddr,
		   int sockaddr_len)
{
	int i;
	struct unix_proto_data *serv_upd;
	struct sockaddr_un sockun;

	PRINTK("unix_proto_connect: socket 0x%x, servlen=%d\n", sock,
	       sockaddr_len);
	if (sockaddr_len <= UN_PATH_OFFSET ||
	    sockaddr_len >= sizeof(struct sockaddr_un)) {
		PRINTK("unix_proto_connect: bad length %d\n", sockaddr_len);
		return -EINVAL;
	}
	verify_area(uservaddr, sockaddr_len);
	memcpy_fromfs(&sockun, uservaddr, sockaddr_len);
	if (sockun.sun_family != AF_UNIX) {
		PRINTK("unix_proto_connect: family is %d, not AF_UNIX (%d)\n",
		       sockun.sun_family, AF_UNIX);
		return -EINVAL;
	}
	if (!(serv_upd = unix_data_lookup(&sockun, sockaddr_len))) {
		PRINTK("unix_proto_connect: can't locate peer\n");
		return -EINVAL;
	}
	if ((i = sock_awaitconn(sock, serv_upd->socket)) < 0) {
		PRINTK("unix_proto_connect: can't await connection\n");
		return i;
	}
	unix_data_ref(UN_DATA(sock->conn));
	UN_DATA(sock)->peerupd = UN_DATA(sock->conn); /* ref server */
	return 0;
}

/*
 * to do a socketpair, we make just connect the two datas, easy! since we
 * always wait on the socket inode, they're no contention for a wait area,
 * and deadlock prevention in the case of a process writing to itself is,
 * ignored, in true unix fashion!
 */
static int
unix_proto_socketpair(struct socket *sock1, struct socket *sock2)
{
	struct unix_proto_data *upd1 = UN_DATA(sock1), *upd2 = UN_DATA(sock2);

	unix_data_ref(upd1);
	unix_data_ref(upd2);
	upd1->peerupd = upd2;
	upd2->peerupd = upd1;
	return 0;
}

/*
 * on accept, we ref the peer's data for safe writes
 */
static int
unix_proto_accept(struct socket *sock, struct socket *newsock)
{
	PRINTK("unix_proto_accept: socket 0x%x accepted via socket 0x%x\n",
	       sock, newsock);
	unix_data_ref(UN_DATA(newsock->conn));
	UN_DATA(newsock)->peerupd = UN_DATA(newsock->conn);
	return 0;
}

/*
 * gets the current name or the name of the connected socket.
 */
static int
unix_proto_getname(struct socket *sock, struct sockaddr *usockaddr,
		   int *usockaddr_len, int peer)
{
	struct unix_proto_data *upd;
	int len;

	PRINTK("unix_proto_getname: socket 0x%x for %s\n", sock,
	       peer ? "peer" : "self");
	if (peer) {
		if (sock->state != SS_CONNECTED) {
			PRINTK("unix_proto_getname: socket not connected\n");
			return -EINVAL;
		}
		upd = UN_DATA(sock->conn);
	}
	else
		upd = UN_DATA(sock);
	verify_area(usockaddr_len, sizeof(*usockaddr_len));
	if ((len = get_fs_long(usockaddr_len)) <= 0)
		return -EINVAL;
	if (len > upd->sockaddr_len)
		len = upd->sockaddr_len;
	if (len) {
		verify_area(usockaddr, len);
		memcpy_tofs(usockaddr, &upd->sockaddr_un, len);
	}
	put_fs_long(len, usockaddr_len);
	return 0;
}

/*
 * we read from our own buf.
 */
static int
unix_proto_read(struct socket *sock, char *ubuf, int size, int nonblock)
{
	struct unix_proto_data *upd;
	int todo, avail;

	if ((todo = size) <= 0)
		return 0;
	upd = UN_DATA(sock);
	while (!(avail = UN_BUF_AVAIL(upd))) {
		if (sock->state != SS_CONNECTED) {
			PRINTK("unix_proto_read: socket not connected\n");
			return (sock->state == SS_DISCONNECTING) ? 0 : -EINVAL;
		}
		PRINTK("unix_proto_read: no data available...\n");
		if (nonblock)
			return -EAGAIN;
		interruptible_sleep_on(sock->wait);
		if (current->signal & ~current->blocked) {
			PRINTK("unix_proto_read: interrupted\n");
			return -ERESTARTSYS;
		}
		if (sock->state == SS_DISCONNECTING) {
			PRINTK("unix_proto_read: disconnected\n");
			return 0;
		}
	}

	/*
	 * copy from the read buffer into the user's buffer, watching for
	 * wraparound. then we wake up the writer
	 */
	do {
		int part, cando;

		if (avail <= 0) {
			PRINTK("unix_proto_read: AVAIL IS NEGATIVE!!!\n");
			send_sig(SIGKILL,current,1);
			return -EINTR;
		}

		if ((cando = todo) > avail)
			cando = avail;
		if (cando > (part = BUF_SIZE - upd->bp_tail))
			cando = part;
		PRINTK("unix_proto_read: avail=%d, todo=%d, cando=%d\n",
		       avail, todo, cando);
		verify_area(ubuf, cando);
		memcpy_tofs(ubuf, upd->buf + upd->bp_tail, cando);
		upd->bp_tail = (upd->bp_tail + cando) & (BUF_SIZE-1);
		ubuf += cando;
		todo -= cando;
		if (sock->state == SS_CONNECTED)
			wake_up(sock->conn->wait);
		avail = UN_BUF_AVAIL(upd);
	} while (todo && avail);
	return size - todo;
}

/*
 * we write to our peer's buf. when we connected we ref'd this peer so we
 * are safe that the buffer remains, even after the peer has disconnected,
 * which we check other ways.
 */
static int
unix_proto_write(struct socket *sock, char *ubuf, int size, int nonblock)
{
	struct unix_proto_data *pupd;
	int todo, space;

	if ((todo = size) <= 0)
		return 0;
	if (sock->state != SS_CONNECTED) {
		PRINTK("unix_proto_write: socket not connected\n");
		if (sock->state == SS_DISCONNECTING) {
			send_sig(SIGPIPE,current,1);
			return -EINTR;
		}
		return -EINVAL;
	}
	pupd = UN_DATA(sock)->peerupd;	/* safer than sock->conn */

	while (!(space = UN_BUF_SPACE(pupd))) {
		PRINTK("unix_proto_write: no space left...\n");
		if (nonblock)
			return -EAGAIN;
		interruptible_sleep_on(sock->wait);
		if (current->signal & ~current->blocked) {
			PRINTK("unix_proto_write: interrupted\n");
			return -ERESTARTSYS;
		}
		if (sock->state == SS_DISCONNECTING) {
			PRINTK("unix_proto_write: disconnected (SIGPIPE)\n");
			send_sig(SIGPIPE,current,1);
			return -EINTR;
		}
	}

	/*
	 * copy from the user's buffer to the write buffer, watching for
	 * wraparound. then we wake up the reader
	 */
	do {
		int part, cando;

		if (space <= 0) {
			PRINTK("unix_proto_write: SPACE IS NEGATIVE!!!\n");
			send_sig(SIGKILL,current,1);
			return -EINTR;
		}

		/*
		 * we may become disconnected inside this loop, so watch
		 * for it (peerupd is safe until we close)
		 */
		if (sock->state == SS_DISCONNECTING) {
			send_sig(SIGPIPE,current,1);
			return -EINTR;
		}
		if ((cando = todo) > space)
			cando = space;
		if (cando > (part = BUF_SIZE - pupd->bp_head))
			cando = part;
		PRINTK("unix_proto_write: space=%d, todo=%d, cando=%d\n",
		       space, todo, cando);
		verify_area(ubuf, cando);
		memcpy_fromfs(pupd->buf + pupd->bp_head, ubuf, cando);
		pupd->bp_head = (pupd->bp_head + cando) & (BUF_SIZE-1);
		ubuf += cando;
		todo -= cando;
		if (sock->state == SS_CONNECTED)
			wake_up(sock->conn->wait);
		space = UN_BUF_SPACE(pupd);
	} while (todo && space);
	return size - todo;
}

static int
unix_proto_select(struct socket *sock, int which)
{
	struct unix_proto_data *upd, *peerupd;

	if (which == SEL_IN) {
		upd = UN_DATA(sock);
		PRINTK("unix_proto_select: there is%s data available\n",
		       UN_BUF_AVAIL(upd) ? "" : " no");
		if (UN_BUF_AVAIL(upd))	/* even if disconnected */
			return 1;
		else if (sock->state != SS_CONNECTED) {
			PRINTK("unix_proto_select: socket not connected (read EOF)\n");
			return 1;
		}
		else
			return 0;
	}
	if (which == SEL_OUT) {
		if (sock->state != SS_CONNECTED) {
			PRINTK("unix_proto_select: socket not connected (write EOF)\n");
			return 1;
		}
		peerupd = UN_DATA(sock->conn);
		PRINTK("unix_proto_select: there is%s space available\n",
		       UN_BUF_SPACE(peerupd) ? "" : " no");
		return (UN_BUF_SPACE(peerupd) > 0);
	}
	/* SEL_EX */
	PRINTK("unix_proto_select: there are no exceptions here?!\n");
	return 0;
}

static int
unix_proto_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
	struct unix_proto_data *upd, *peerupd;

	upd = UN_DATA(sock);
	peerupd = (sock->state == SS_CONNECTED) ? UN_DATA(sock->conn) : NULL;

	switch (cmd) {
	case TIOCINQ:
		verify_area((void *)arg, sizeof(unsigned long));
		if (UN_BUF_AVAIL(upd) || peerupd)
			put_fs_long(UN_BUF_AVAIL(upd), (unsigned long *)arg);
		else
			put_fs_long(1, (unsigned long *)arg); /* read EOF */
		break;

	case TIOCOUTQ:
		verify_area((void *)arg, sizeof(unsigned long));
		if (peerupd)
			put_fs_long(UN_BUF_SPACE(peerupd),
				    (unsigned long *)arg);
		else
			put_fs_long(0, (unsigned long *)arg);
		break;

	default:
		return -EINVAL;
	}
	return 0;
}

static int
unix_proto_init(void)
{
	struct unix_proto_data *upd;

	PRINTK("unix_proto_init: initializing...\n");
	for (upd = unix_datas; upd <= last_unix_data; ++upd)
		upd->refcnt = 0;
	return 0;
}
