/*
 * NET3:	Implementation of BSD Unix domain sockets.
 *
 * Authors:	Alan Cox, <alan@cymru.net>
 *
 *		Currently this contains all but the file descriptor passing code.
 *		Before that goes in the odd bugs in the iovec handlers need 
 *		fixing, and this bit testing. BSD fd passing is not a trivial part
 *		of the exercise it turns out. Anyone like writing garbage collectors.
 *
 *		This program is free software; you can redistribute it and/or
 *		modify it under the terms of the GNU General Public License
 *		as published by the Free Software Foundation; either version
 *		2 of the License, or (at your option) any later version.
 *
 * Fixes:
 *		Linus Torvalds	:	Assorted bug cures.
 *		Niibe Yutaka	:	async I/O support.
 *		Carsten Paeth	:	PF_UNIX check, address fixes.
 *		Alan Cox	:	Limit size of allocated blocks.
 *		Alan Cox	:	Fixed the stupid socketpair bug.
 *		Alan Cox	:	BSD compatibility fine tuning.
 *		Alan Cox	:	Fixed a bug in connect when interrupted.
 *		Alan Cox	:	Sorted out a proper draft version of
 *					file descriptor passing hacked up from
 *					Mike Shaver's work.
 *		Marty Leisner	:	Fixes to fd passing
 *		Nick Nevin	:	recvmsg bugfix.
 *		Alan Cox	:	Started proper garbage collector
 *		Heiko EiBfeldt	:	Missing verify_area check
 *
 * Known differences from reference BSD that was tested:
 *
 *	[TO FIX]
 *	ECONNREFUSED is not returned from one end of a connected() socket to the
 *		other the moment one end closes.
 *	fstat() doesn't return st_dev=NODEV, and give the blksize as high water mark
 *		and a fake inode identifier (nor the BSD first socket fstat twice bug).
 *	[NOT TO FIX]
 *	accept() returns a path name even if the connecting socket has closed
 *		in the meantime (BSD loses the path and gives up).
 *	accept() returns 0 length path for an unbound connector. BSD returns 16
 *		and a null first byte in the path (but not for gethost/peername - BSD bug ??)
 *	socketpair(...SOCK_RAW..) doesn't panic the kernel.
 *	BSD af_unix apparently has connect forgetting to block properly.
 */

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/signal.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/socket.h>
#include <linux/un.h>
#include <linux/fcntl.h>
#include <linux/termios.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/net.h>
#include <linux/in.h>
#include <linux/fs.h>
#include <linux/malloc.h>
#include <asm/segment.h>
#include <linux/skbuff.h>
#include <linux/netdevice.h>
#include <net/sock.h>
#include <net/tcp.h>
#include <net/af_unix.h>
#include <linux/proc_fs.h>

unix_socket *unix_socket_list=NULL;

#define min(a,b)	(((a)<(b))?(a):(b))

/*
 *	Make sure the unix name is null-terminated.
 */
 
static inline void unix_mkname(struct sockaddr_un * sunaddr, unsigned long len)
{
	if (len >= sizeof(*sunaddr))
		len = sizeof(*sunaddr)-1;
	((char *)sunaddr)[len]=0;
}

/*
 *	Note: Sockets may not be removed _during_ an interrupt or net_bh
 *	handler using this technique. They can be added although we do not
 *	use this facility.
 */
 
static void unix_remove_socket(unix_socket *sk)
{
	unix_socket **s;
	
	cli();
	s=&unix_socket_list;

	while(*s!=NULL)
	{
		if(*s==sk)
		{
			*s=sk->next;
			sti();
			return;
		}
		s=&((*s)->next);
	}
	sti();
}

static void unix_insert_socket(unix_socket *sk)
{
	cli();
	sk->next=unix_socket_list;
	unix_socket_list=sk;
	sti();
}

static unix_socket *unix_find_socket(struct inode *i)
{
	unix_socket *s;
	cli();
	s=unix_socket_list;
	while(s)
	{
		if(s->protinfo.af_unix.inode==i)
		{
			sti();
			return(s);
		}
		s=s->next;
	}
	sti();
	return(NULL);
}

/*
 *	Delete a unix socket. We have to allow for deferring this on a timer.
 */

static void unix_destroy_timer(unsigned long data)
{
	unix_socket *sk=(unix_socket *)data;
	if(sk->protinfo.af_unix.locks==0 && sk->wmem_alloc==0)
	{
		if(sk->protinfo.af_unix.name)
			kfree(sk->protinfo.af_unix.name);
		sk_free(sk);
		return;
	}
	
	/*
	 *	Retry;
	 */
	 
	sk->timer.expires=jiffies+10*HZ;	/* No real hurry try it every 10 seconds or so */
	add_timer(&sk->timer);
}
	 
	 
static void unix_delayed_delete(unix_socket *sk)
{
	sk->timer.data=(unsigned long)sk;
	sk->timer.expires=jiffies+HZ;		/* Normally 1 second after will clean up. After that we try every 10 */
	sk->timer.function=unix_destroy_timer;
	add_timer(&sk->timer);
}
	
static void unix_destroy_socket(unix_socket *sk)
{
	struct sk_buff *skb;

	unix_remove_socket(sk);
	
	while((skb=skb_dequeue(&sk->receive_queue))!=NULL)
	{
		if(sk->state==TCP_LISTEN)
		{
			unix_socket *osk=skb->sk;
			osk->state=TCP_CLOSE;
			kfree_skb(skb, FREE_WRITE);	/* Now surplus - free the skb first before the socket */
			osk->state_change(osk);		/* So the connect wakes and cleans up (if any) */
			/* osk will be destroyed when it gets to close or the timer fires */			
		}
		else
		{
			/* passed fds are erased in the kfree_skb hook */
			kfree_skb(skb,FREE_WRITE);
		}
	}
	
	if(sk->protinfo.af_unix.inode!=NULL)
	{
		iput(sk->protinfo.af_unix.inode);
		sk->protinfo.af_unix.inode=NULL;
	}
	
	if(--sk->protinfo.af_unix.locks==0 && sk->wmem_alloc==0)
	{
		if(sk->protinfo.af_unix.name)
			kfree(sk->protinfo.af_unix.name);
		sk_free(sk);
	}
	else
	{
		sk->dead=1;
		unix_delayed_delete(sk);	/* Try every so often until buffers are all freed */
	}
}

/*
 *	Fixme: We need async I/O on AF_UNIX doing next.
 */
 
static int unix_fcntl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
	return -EINVAL;
}

/*
 *	Yes socket options work with the new unix domain socketry!!!!!!!
 */
 
static int unix_setsockopt(struct socket *sock, int level, int optname, char *optval, int optlen)
{
	unix_socket *sk=sock->data;
	if(level!=SOL_SOCKET)
		return -EOPNOTSUPP;
	return sock_setsockopt(sk,level,optname,optval,optlen);	
}

static int unix_getsockopt(struct socket *sock, int level, int optname, char *optval, int *optlen)
{
	unix_socket *sk=sock->data;
	if(level!=SOL_SOCKET)
		return -EOPNOTSUPP;
	return sock_getsockopt(sk,level,optname,optval,optlen);
}

static int unix_listen(struct socket *sock, int backlog)
{
	unix_socket *sk=sock->data;
	if(sk->type!=SOCK_STREAM)
		return -EOPNOTSUPP;		/* Only stream sockets accept */
	if(sk->protinfo.af_unix.name==NULL)
		return -EINVAL;			/* No listens on an unbound socket */
	sk->max_ack_backlog=backlog;
	sk->state=TCP_LISTEN;
	return 0;
}

static void def_callback1(struct sock *sk)
{
	if(!sk->dead)
		wake_up_interruptible(sk->sleep);
}

static void def_callback2(struct sock *sk, int len)
{
	if(!sk->dead)
	{
		wake_up_interruptible(sk->sleep);
		sock_wake_async(sk->socket, 1);
	}
}

static void def_callback3(struct sock *sk)
{
	if(!sk->dead)
	{
		wake_up_interruptible(sk->sleep);
		sock_wake_async(sk->socket, 2);
	}
}

static int unix_create(struct socket *sock, int protocol)
{
	unix_socket *sk;
	if(protocol && protocol != PF_UNIX)
		return -EPROTONOSUPPORT;
	sk=(unix_socket *)sk_alloc(GFP_KERNEL);
	if(sk==NULL)
		return -ENOMEM;
	switch(sock->type)
	{
		case SOCK_STREAM:
			break;
		/*
		 *	Believe it or not BSD has AF_UNIX, SOCK_RAW though
		 *	nothing uses it.
		 */
		case SOCK_RAW:
			sock->type=SOCK_DGRAM;
		case SOCK_DGRAM:
			break;
		default:
			sk_free(sk);
			return -ESOCKTNOSUPPORT;
	}
	sk->type=sock->type;
	init_timer(&sk->timer);
	skb_queue_head_init(&sk->write_queue);
	skb_queue_head_init(&sk->receive_queue);
	skb_queue_head_init(&sk->back_log);
	sk->protinfo.af_unix.family=AF_UNIX;
	sk->protinfo.af_unix.inode=NULL;
	sk->protinfo.af_unix.locks=1;		/* Us */
	sk->protinfo.af_unix.readsem=MUTEX;	/* single task reading lock */
	sk->rcvbuf=SK_RMEM_MAX;
	sk->sndbuf=SK_WMEM_MAX;
	sk->allocation=GFP_KERNEL;
	sk->state=TCP_CLOSE;
	sk->priority=SOPRI_NORMAL;
	sk->state_change=def_callback1;
	sk->data_ready=def_callback2;
	sk->write_space=def_callback3;
	sk->error_report=def_callback1;
	sk->mtu=4096;
	sk->socket=sock;
	sock->data=(void *)sk;
	sk->sleep=sock->wait;
	unix_insert_socket(sk);
	return 0;
}

static int unix_dup(struct socket *newsock, struct socket *oldsock)
{
	return unix_create(newsock,0);
}

static int unix_release(struct socket *sock, struct socket *peer)
{
	unix_socket *sk=sock->data;
	unix_socket *skpair;
	
	/* May not have data attached */
	
	if(sk==NULL)
		return 0;
		
	sk->state_change(sk);
	sk->dead=1;
	skpair=(unix_socket *)sk->protinfo.af_unix.other;	/* Person we send to (default) */
	if(sk->type==SOCK_STREAM && skpair!=NULL && skpair->state!=TCP_LISTEN)
	{
		skpair->shutdown=SHUTDOWN_MASK;		/* No more writes */
		skpair->state_change(skpair);		/* Wake any blocked writes */
	}
	if(skpair!=NULL)
		skpair->protinfo.af_unix.locks--;	/* It may now die */
	sk->protinfo.af_unix.other=NULL;		/* No pair */
	unix_destroy_socket(sk);			/* Try to flush out this socket. Throw out buffers at least */
	unix_gc();					/* Garbage collect fds */	

	/*
	 *	FIXME: BSD difference: In BSD all sockets connected to use get ECONNRESET and we die on the spot. In
	 *	Linux we behave like files and pipes do and wait for the last dereference.
	 */
	 
	sock->data = NULL;
	sk->socket = NULL;
	
	return 0;
}


static unix_socket *unix_find_other(char *path, int *error)
{
	int old_fs;
	int err;
	struct inode *inode;
	unix_socket *u;
	
	old_fs=get_fs();
	set_fs(get_ds());
	err = open_namei(path, 2, S_IFSOCK, &inode, NULL);
	set_fs(old_fs);
	if(err<0)
	{
		*error=err;
		return NULL;
	}
	u=unix_find_socket(inode);
	iput(inode);
	if(u==NULL)
	{
		*error=-ECONNREFUSED;
		return NULL;
	}
	return u;
}


static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
	struct sockaddr_un *sunaddr=(struct sockaddr_un *)uaddr;
	unix_socket *sk=sock->data;
	int old_fs;
	int err;
	
	if(sk->protinfo.af_unix.name)
		return -EINVAL;		/* Already bound */
	
	if(addr_len>sizeof(struct sockaddr_un) || addr_len<3 || sunaddr->sun_family!=AF_UNIX)
		return -EINVAL;
	unix_mkname(sunaddr, addr_len);
	/*
	 *	Put ourselves in the filesystem
	 */
	if(sk->protinfo.af_unix.inode!=NULL)
		return -EINVAL;
	
	sk->protinfo.af_unix.name=kmalloc(addr_len+1, GFP_KERNEL);
	if(sk->protinfo.af_unix.name==NULL)
		return -ENOMEM;
	memcpy(sk->protinfo.af_unix.name, sunaddr->sun_path, addr_len+1);
	
	old_fs=get_fs();
	set_fs(get_ds());
	
	err=do_mknod(sk->protinfo.af_unix.name,S_IFSOCK|S_IRWXUGO,0);
	if(err==0)
		err=open_namei(sk->protinfo.af_unix.name, 2, S_IFSOCK, &sk->protinfo.af_unix.inode, NULL);
	
	set_fs(old_fs);
	
	if(err<0)
	{
		kfree_s(sk->protinfo.af_unix.name,addr_len+1);
		sk->protinfo.af_unix.name=NULL;
		if(err==-EEXIST)
			return -EADDRINUSE;
		else
			return err;
	}
	
	return 0;
	
}

static int unix_connect(struct socket *sock, struct sockaddr *uaddr, int addr_len, int flags)
{
	unix_socket *sk=sock->data;
	struct sockaddr_un *sunaddr=(struct sockaddr_un *)uaddr;
	unix_socket *other;
	struct sk_buff *skb;
	int err;

	if(sk->type==SOCK_STREAM && sk->protinfo.af_unix.other)
	{
		if(sock->state==SS_CONNECTING && sk->state==TCP_ESTABLISHED)
		{
			sock->state=SS_CONNECTED;
			return 0;
		}
		if(sock->state==SS_CONNECTING && sk->state == TCP_CLOSE)
		{
			sock->state=SS_UNCONNECTED;
			return -ECONNREFUSED;
		}
		if(sock->state!=SS_CONNECTING)
			return -EISCONN;
		if(flags&O_NONBLOCK)
			return -EALREADY;
		/*
		 *	Drop through the connect up logic to the wait.
		 */
	}
	
	if(addr_len < sizeof(sunaddr->sun_family)+1 || sunaddr->sun_family!=AF_UNIX)
		return -EINVAL;
		
	unix_mkname(sunaddr, addr_len);
		
	if(sk->type==SOCK_DGRAM)
	{
		if(sk->protinfo.af_unix.other)
		{
			sk->protinfo.af_unix.other->protinfo.af_unix.locks--;
			sk->protinfo.af_unix.other=NULL;
			sock->state=SS_UNCONNECTED;
		}
		other=unix_find_other(sunaddr->sun_path, &err);
		if(other==NULL)
			return err;
		if(other->type!=sk->type)
			return -EPROTOTYPE;
		other->protinfo.af_unix.locks++;
		sk->protinfo.af_unix.other=other;
		sock->state=SS_CONNECTED;
		sk->state=TCP_ESTABLISHED;
		return 0;			/* Done */
	}
	

	if(sock->state==SS_UNCONNECTED)
	{
		/*
		 *	Now ready to connect
		 */
	 
		skb=sock_alloc_send_skb(sk, 0, 0, 0, &err); /* Marker object */
		if(skb==NULL)
			return err;
		skb->sk=sk;				/* So they know it is us */
		skb->free=1;
		skb->h.filp=NULL;
		sk->state=TCP_CLOSE;
		unix_mkname(sunaddr, addr_len);
		other=unix_find_other(sunaddr->sun_path, &err);
		if(other==NULL)
		{
			kfree_skb(skb, FREE_WRITE);
			return err;
		}
		if(other->type!=sk->type)
		{
			kfree_skb(skb, FREE_WRITE);
			return -EPROTOTYPE;
		}
		other->protinfo.af_unix.locks++;		/* Lock the other socket so it doesn't run off for a moment */
		other->ack_backlog++;
		sk->protinfo.af_unix.other=other;
		skb_queue_tail(&other->receive_queue,skb);
		sk->state=TCP_SYN_SENT;
		sock->state=SS_CONNECTING;
		sti();
		other->data_ready(other,0);		/* Wake up ! */		
	}
			
	
	/* Wait for an accept */
	
	cli();
	while(sk->state==TCP_SYN_SENT)
	{
		if(flags&O_NONBLOCK)
		{
			sti();
			return -EINPROGRESS;
		}
		interruptible_sleep_on(sk->sleep);
		if(current->signal & ~current->blocked)
		{
			sti();
			return -ERESTARTSYS;
		}
	}
	
	/*
	 *	Has the other end closed on us ?
	 */
	 
	if(sk->state==TCP_CLOSE)
	{
		sk->protinfo.af_unix.other->protinfo.af_unix.locks--;
		sk->protinfo.af_unix.other=NULL;
		sock->state=SS_UNCONNECTED;
		sti();
		return -ECONNREFUSED;
	}
	
	/*
	 *	Amazingly it has worked
	 */
	 
	sock->state=SS_CONNECTED;
	sti();
	return 0;
	
}

static int unix_socketpair(struct socket *a, struct socket *b)
{
	unix_socket *ska,*skb;	
	
	ska=a->data;
	skb=b->data;

	/* Join our sockets back to back */
	ska->protinfo.af_unix.locks++;
	skb->protinfo.af_unix.locks++;
	ska->protinfo.af_unix.other=skb;
	skb->protinfo.af_unix.other=ska;
	ska->state=TCP_ESTABLISHED;
	skb->state=TCP_ESTABLISHED;
	return 0;
}

static int unix_accept(struct socket *sock, struct socket *newsock, int flags)
{
	unix_socket *sk=sock->data;
	unix_socket *newsk, *tsk;
	struct sk_buff *skb;
	
	if(sk->type!=SOCK_STREAM)
	{
		return -EOPNOTSUPP;
	}
	if(sk->state!=TCP_LISTEN)
	{
		return -EINVAL;
	}
		
	newsk=newsock->data;
	if(sk->protinfo.af_unix.name!=NULL)
	{
		newsk->protinfo.af_unix.name=kmalloc(strlen(sk->protinfo.af_unix.name)+1, GFP_KERNEL);
		if(newsk->protinfo.af_unix.name==NULL)
			return -ENOMEM;
		strcpy(newsk->protinfo.af_unix.name, sk->protinfo.af_unix.name);
	}
		
	do
	{
		cli();
		skb=skb_dequeue(&sk->receive_queue);
		if(skb==NULL)
		{
			if(flags&O_NONBLOCK)
			{
				sti();
				return -EAGAIN;
			}
			interruptible_sleep_on(sk->sleep);
			if(current->signal & ~current->blocked)
			{
				sti();
				return -ERESTARTSYS;
			}
			sti();
		}
	}
	while(skb==NULL);
	tsk=skb->sk;
	kfree_skb(skb, FREE_WRITE);	/* The buffer is just used as a tag */
	sk->ack_backlog--;
	newsk->protinfo.af_unix.other=tsk;
	tsk->protinfo.af_unix.other=newsk;
	tsk->state=TCP_ESTABLISHED;
	newsk->state=TCP_ESTABLISHED;
	newsk->protinfo.af_unix.locks++;	/* Swap lock over */
	sk->protinfo.af_unix.locks--;	/* Locked to child socket not master */
	tsk->protinfo.af_unix.locks++;	/* Back lock */
	sti();
	tsk->state_change(tsk);		/* Wake up any sleeping connect */
	sock_wake_async(tsk->socket, 0);
	return 0;
}

static int unix_getname(struct socket *sock, struct sockaddr *uaddr, int *uaddr_len, int peer)
{
	unix_socket *sk=sock->data;
	struct sockaddr_un *sunaddr=(struct sockaddr_un *)uaddr;
	
	if(peer)
	{
		if(sk->protinfo.af_unix.other==NULL)
			return -ENOTCONN;
		sk=sk->protinfo.af_unix.other;
	}
	sunaddr->sun_family=AF_UNIX;
	if(sk->protinfo.af_unix.name==NULL)
	{
		*sunaddr->sun_path=0;
		*uaddr_len=sizeof(sunaddr->sun_family)+1;
		return 0;		/* Not bound */
	}
	*uaddr_len=sizeof(sunaddr->sun_family)+strlen(sk->protinfo.af_unix.name)+1;
	strcpy(sunaddr->sun_path,sk->protinfo.af_unix.name);		/* 108 byte limited */
	return 0;
}

/*
 *	Support routines for struct cmsghdr handling
 */
 
static struct cmsghdr *unix_copyrights(void *userp, int len)
{
	struct cmsghdr *cm;

	if(len>256|| len <=0)
		return NULL;
	cm=kmalloc(len, GFP_KERNEL);
	memcpy_fromfs(cm, userp, len);
	return cm;
}

/*
 *	Return a header block
 */
 
static void unix_returnrights(void *userp, int len, struct cmsghdr *cm)
{
	memcpy_tofs(userp, cm, len);
	kfree(cm);
}

/*
 *	Copy file descriptors into system space.
 *	Return number copied or negative error code
 */
 
static int unix_fd_copy(struct sock *sk, struct cmsghdr *cmsg, struct file **fp)
{
	int num=cmsg->cmsg_len-sizeof(struct cmsghdr);
	int i;
	int *fdp=(int *)cmsg->cmsg_data;

	num /= sizeof(int);	/* Odd bytes are forgotten in BSD not errored */
	if (num >= UNIX_MAX_FD)
		return -EINVAL;
	
	/*
	 *	Verify the descriptors.
	 */
	 
	for(i=0; i< num; i++)
	{
		int fd;
		
		fd = fdp[i];	
#if 0
		printk("testing  fd %d\n", fd);
#endif
		if (fd < 0 || fd >= NR_OPEN)
			return -EBADF;
		if (current->files->fd[fd]==NULL)
			return -EBADF;
	}
	
        /* add another reference to these files */
	for(i=0; i< num; i++)
	{
		fp[i]=current->files->fd[fdp[i]];
		fp[i]->f_count++;
		unix_inflight(fp[i]);
	}
	
	return num;
}

/*
 *	Free the descriptors in the array
 */

static void unix_fd_free(struct sock *sk, struct file **fp, int num)
{
	int i;
	for(i=0;i<num;i++)
	{
		close_fp(fp[i]);
		unix_notinflight(fp[i]);
	}
}


/*
 *	Perform the AF_UNIX file descriptor pass out functionality. This
 *	is nasty and messy as is the whole design of BSD file passing.
 */

static void unix_detach_fds(struct sk_buff *skb, struct cmsghdr *cmsg)
{
	int i;
	/* count of space in parent for fds */
	int cmnum;
	struct file **fp;
	int *cmfptr;
	int fdnum;

	cmfptr = NULL;
	cmnum = 0;
	if (cmsg)
	{
		cmnum = (cmsg->cmsg_len-sizeof(struct cmsghdr)) / sizeof(int);
		cmfptr = (int *)&cmsg->cmsg_data;
	}
	
	fdnum = *(int *)skb->h.filp;
	fp = (struct file **)(skb->h.filp+sizeof(long));

	if (cmnum > fdnum)
		cmnum = fdnum;

	/*
	 *	Copy those that fit
	 */
	for (i = 0 ; i < cmnum ; i++)
	{
		int new_fd = get_unused_fd();
		if (new_fd < 0)
			break;
		current->files->fd[new_fd]=fp[i];
		*cmfptr++ = new_fd;
		unix_notinflight(fp[i]);
	}
	/*
	 *	Dump those that don't
	 */
	for( ; i < fdnum ; i++)
	{
		close_fp(fp[i]);
		unix_notinflight(fp[i]);
	}
	kfree(skb->h.filp);
	skb->h.filp=NULL;

	/* no need to use destructor */
	skb->destructor = NULL;
}

static void unix_destruct_fds(struct sk_buff *skb)
{
	unix_detach_fds(skb,NULL);
}
	
/*
 *	Attach the file descriptor array to an sk_buff
 */
static void unix_attach_fds(int fpnum,struct file **fp,struct sk_buff *skb)
{

	skb->h.filp = kmalloc(sizeof(long)+fpnum*sizeof(struct file *), 
							GFP_KERNEL);
	/* number of descriptors starts block */
	*(int *)skb->h.filp = fpnum;
	/* actual  descriptors */
	memcpy(skb->h.filp+sizeof(long),fp,fpnum*sizeof(struct file *));
	skb->destructor = unix_destruct_fds;
}

/*
 *	Send AF_UNIX data.
 */
		
static int unix_sendmsg(struct socket *sock, struct msghdr *msg, int len, int nonblock, int flags)
{
	unix_socket *sk=sock->data;
	unix_socket *other;
	struct sockaddr_un *sunaddr=msg->msg_name;
	int err,size;
	struct sk_buff *skb;
	int limit=0;
	int sent=0;
	struct file *fp[UNIX_MAX_FD];
	/* number of fds waiting to be passed, 0 means either
	 * no fds to pass or they've already been passed 
	 */
	int fpnum=0;

	if(sk->err)
		return sock_error(sk);

	if(flags&MSG_OOB)
		return -EOPNOTSUPP;
			
	if(flags)	/* For now */ {
		return -EINVAL;
	}
		
	if(sunaddr!=NULL)
	{
		if(sock->type==SOCK_STREAM)
		{
			if(sk->state==TCP_ESTABLISHED)
				return -EISCONN;
			else
				return -EOPNOTSUPP;
		}
	}

	if(sunaddr==NULL)
	{
		if(sk->protinfo.af_unix.other==NULL)
			return -ENOTCONN;
	}

	/*
	 *	A control message has been attached.
	 */
	if(msg->msg_control) 
	{
		struct cmsghdr *cm=unix_copyrights(msg->msg_control, 
						msg->msg_controllen);
		if(cm==NULL || msg->msg_controllen<sizeof(struct cmsghdr) ||
		   cm->cmsg_type!=SCM_RIGHTS ||
		   cm->cmsg_level!=SOL_SOCKET ||
		   msg->msg_controllen!=cm->cmsg_len)
		{
			kfree(cm);
		   	return -EINVAL;
		}
		fpnum=unix_fd_copy(sk,cm,fp);
		kfree(cm);
		if(fpnum<0) {
			return fpnum;
		}
	}

	while(sent < len)
	{
		/*
		 *	Optimisation for the fact that under 0.01% of X messages typically
		 *	need breaking up.
		 */
		 
		size=len-sent;

		if(size>(sk->sndbuf-sizeof(struct sk_buff))/2)	/* Keep two messages in the pipe so it schedules better */
		{
			if(sock->type==SOCK_DGRAM)
			{
				unix_fd_free(sk,fp,fpnum);
				return -EMSGSIZE;
			}
			size=(sk->sndbuf-sizeof(struct sk_buff))/2;
		}
		/*
		 *	Keep to page sized kmalloc()'s as various people
		 *	have suggested. Big mallocs stress the vm too
		 *	much.
		 */

		if(size > 4000 && sock->type!=SOCK_DGRAM)
			limit = 4000;	/* Fall back to 4K if we can't grab a big buffer this instant */
		else
			limit = 0;	/* Otherwise just grab and wait */

		/*
		 *	Grab a buffer
		 */
		 
		skb=sock_alloc_send_skb(sk,size,limit,nonblock, &err);
		
		if(skb==NULL)
		{
			unix_fd_free(sk,fp,fpnum);
			if(sent)
			{
				sk->err=-err;
				return sent;
			}
			return err;
		}
		size=skb_tailroom(skb);		/* If we dropped back on a limit then our skb is smaller */

		skb->sk=sk;
		skb->free=1;
		
		if(fpnum)
		{
			unix_attach_fds(fpnum,fp,skb);
			fpnum=0;
		}
		else
			skb->h.filp=NULL;

		memcpy_fromiovec(skb_put(skb,size),msg->msg_iov, size);

		cli();
		if(sunaddr==NULL)
		{
			other=sk->protinfo.af_unix.other;
			if(sock->type==SOCK_DGRAM && other->dead)
			{
				other->protinfo.af_unix.locks--;
				sk->protinfo.af_unix.other=NULL;
				sock->state=SS_UNCONNECTED;
				sti();
				kfree_skb(skb, FREE_WRITE);
				if(!sent)
					return -ECONNRESET;
				else
					return sent;
			}
		}
		else
		{
			unix_mkname(sunaddr, msg->msg_namelen);
			other=unix_find_other(sunaddr->sun_path, &err);
			if(other==NULL)
			{
				sti();
				kfree_skb(skb, FREE_WRITE);
				if(sent)
					return sent;
				else
					return err;
			}
		}
		skb_queue_tail(&other->receive_queue, skb);
		sti();
		/* if we sent an fd, only do it once */
		other->data_ready(other,size);
		sent+=size;
	}
	return sent;
}

/*
 *	Sleep until data has arrive. But check for races..
 */
 
static void unix_data_wait(unix_socket * sk)
{
	cli();
	if (!skb_peek(&sk->receive_queue)) {
		sk->socket->flags |= SO_WAITDATA;
		interruptible_sleep_on(sk->sleep);
		sk->socket->flags &= ~SO_WAITDATA;
	}
	sti();
}

static int unix_recvmsg(struct socket *sock, struct msghdr *msg, int size, int noblock, int flags, int *addr_len)
{
	unix_socket *sk=sock->data;
	struct sockaddr_un *sunaddr=msg->msg_name;
	struct sk_buff *skb;
	int copied=0;
	unsigned char *sp;
	int len;
	int num;
	struct iovec *iov=msg->msg_iov;
	struct cmsghdr *cm=NULL;
	int ct=msg->msg_iovlen;

	if(flags&MSG_OOB)
		return -EOPNOTSUPP;
		
	if(addr_len)
		*addr_len=0;
		
	if(sk->err)
		return sock_error(sk);

	if(msg->msg_control) 
	{
		cm=unix_copyrights(msg->msg_control, 
			msg->msg_controllen);
		if(msg->msg_controllen<sizeof(struct cmsghdr)
#if 0 
/*		investigate this further -- Stevens example doesn't seem to care */
		||
		   cm->cmsg_type!=SCM_RIGHTS ||
		   cm->cmsg_level!=SOL_SOCKET ||
		   msg->msg_controllen!=cm->cmsg_len
#endif
		)
		{
			kfree(cm);
/*			printk("recvmsg: Bad msg_control\n");*/
		   	return -EINVAL;
		}
	}
	
	down(&sk->protinfo.af_unix.readsem);		/* Lock the socket */
	while(ct--)
	{
		int done=0;
		sp=iov->iov_base;
		len=iov->iov_len;
		iov++;
		
		while(done<len)
		{
			if (copied && (flags & MSG_PEEK))
				goto out;
			if (copied == size)
				goto out;
			skb=skb_dequeue(&sk->receive_queue);
			if(skb==NULL)
			{
				up(&sk->protinfo.af_unix.readsem);
				if(sk->shutdown & RCV_SHUTDOWN)
					return copied;
				if(copied)
					return copied;
				if(noblock)
					return -EAGAIN;
				if(current->signal & ~current->blocked)
					return -ERESTARTSYS;
				unix_data_wait(sk);
				down(&sk->protinfo.af_unix.readsem);
				continue;
			}
			if(msg->msg_name!=NULL)
			{
				sunaddr->sun_family=AF_UNIX;
				if(skb->sk->protinfo.af_unix.name)
				{
					memcpy(sunaddr->sun_path, skb->sk->protinfo.af_unix.name, 108);
					if(addr_len)
						*addr_len=strlen(sunaddr->sun_path)+sizeof(short);
				}
				else
					if(addr_len)
						*addr_len=sizeof(short);
			}

			num=min(skb->len,len-done);
			memcpy_tofs(sp, skb->data, num);

			if (skb->h.filp!=NULL)
				unix_detach_fds(skb,cm);

			copied+=num;
			done+=num;
			sp+=num;
			if (!(flags & MSG_PEEK))
				skb_pull(skb, num);
			/* put the skb back if we didn't use it up.. */
			if (skb->len) {
				skb_queue_head(&sk->receive_queue, skb);
				continue;
			}
			kfree_skb(skb, FREE_WRITE);
			if(sock->type==SOCK_DGRAM || cm)
				goto out;
		}
	}
out:
	up(&sk->protinfo.af_unix.readsem);
	if(cm)
		unix_returnrights(msg->msg_control,msg->msg_controllen,cm);
	return copied;
}

static int unix_shutdown(struct socket *sock, int mode)
{
	unix_socket *sk=(unix_socket *)sock->data;
	unix_socket *other=sk->protinfo.af_unix.other;
	if(mode&SEND_SHUTDOWN)
	{
		sk->shutdown|=SEND_SHUTDOWN;
		sk->state_change(sk);
		if(other)
		{
			other->shutdown|=RCV_SHUTDOWN;
			other->state_change(other);
		}
	}
	other=sk->protinfo.af_unix.other;
	if(mode&RCV_SHUTDOWN)
	{
		sk->shutdown|=RCV_SHUTDOWN;
		sk->state_change(sk);
		if(other)
		{
			other->shutdown|=SEND_SHUTDOWN;
			other->state_change(other);
		}
	}
	return 0;
}

		
static int unix_select(struct socket *sock,  int sel_type, select_table *wait)
{
	return datagram_select(sock->data,sel_type,wait);
}

static int unix_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
	unix_socket *sk=sock->data;
	int err;
	long amount=0;
			
	switch(cmd)
	{
	
		case TIOCOUTQ:
			err=verify_area(VERIFY_WRITE,(void *)arg,sizeof(unsigned long));
			if(err)
				return err;
			amount=sk->sndbuf-sk->wmem_alloc;
			if(amount<0)
				amount=0;
			put_fs_long(amount,(unsigned long *)arg);
			return 0;
		case TIOCINQ:
		{
			struct sk_buff *skb;
			if(sk->state==TCP_LISTEN)
				return -EINVAL;
			/* These two are safe on a single CPU system as only user tasks fiddle here */
			if((skb=skb_peek(&sk->receive_queue))!=NULL)
				amount=skb->len;
			err=verify_area(VERIFY_WRITE,(void *)arg,sizeof(unsigned long));
			if(err)
				return err;
			put_fs_long(amount,(unsigned long *)arg);
			return 0;
		}

		default:
			return -EINVAL;
	}
	/*NOTREACHED*/
	return(0);
}

#ifdef CONFIG_PROC_FS
static int unix_get_info(char *buffer, char **start, off_t offset, int length, int dummy)
{
	off_t pos=0;
	off_t begin=0;
	int len=0;
	unix_socket *s=unix_socket_list;
	
	len+= sprintf(buffer,"Num       RefCount Protocol Flags    Type St "
	    "Inode Path\n");
	
	while(s!=NULL)
	{
		len+=sprintf(buffer+len,"%p: %08X %08X %08lX %04X %02X %5ld",
			s,
			s->protinfo.af_unix.locks,
			0,
			s->socket->flags,
			s->socket->type,
			s->socket->state,
			s->socket->inode ? s->socket->inode->i_ino : 0);
		if(s->protinfo.af_unix.name!=NULL)
			len+=sprintf(buffer+len, " %s\n", s->protinfo.af_unix.name);
		else
			buffer[len++]='\n';
		
		pos=begin+len;
		if(pos<offset)
		{
			len=0;
			begin=pos;
		}
		if(pos>offset+length)
			break;
		s=s->next;
	}
	*start=buffer+(offset-begin);
	len-=(offset-begin);
	if(len>length)
		len=length;
	return len;
}
#endif

struct proto_ops unix_proto_ops = {
	AF_UNIX,
	
	unix_create,
	unix_dup,
	unix_release,
	unix_bind,
	unix_connect,
	unix_socketpair,
	unix_accept,
	unix_getname,
	unix_select,
	unix_ioctl,
	unix_listen,
	unix_shutdown,
	unix_setsockopt,
	unix_getsockopt,
	unix_fcntl,
	unix_sendmsg,
	unix_recvmsg
};


void unix_proto_init(struct net_proto *pro)
{
	printk(KERN_INFO "NET3: Unix domain sockets 0.12 for Linux NET3.035.\n");
	sock_register(unix_proto_ops.family, &unix_proto_ops);
#ifdef CONFIG_PROC_FS
	proc_net_register(&(struct proc_dir_entry) {
		PROC_NET_UNIX,  4, "unix",
		S_IFREG | S_IRUGO, 1, 0, 0,
		0, &proc_net_inode_operations,
		unix_get_info
	});
#endif
}
/*
 * Local variables:
 *  compile-command: "gcc -g -D__KERNEL__ -Wall -O6 -I/usr/src/linux/include -c af_unix.c"
 * End:
 */
