/*
 *	This module implements the (SPP-derived) Sequenced Packet eXchange
 *	(SPX) protocol for Linux 2.1.X as specified in
 *		NetWare SPX Services Specification, Semantics and API
 *		 Revision:       1.00
 *		 Revision Date:  February 9, 1993
 *
 *	Developers:
 *      Jay Schulist    <jschlst@turbolinux.com>
 *	Jim Freeman	<jfree@caldera.com>
 *
 *	Changes:
 *	Alan Cox	:	Fixed an skb_unshare check for NULL
 *				that crashed it under load. Renamed and
 *				made static the ipx ops. Removed the hack
 *				ipx methods interface. Dropped AF_SPX - its
 *				the wrong abstraction.
 *	Eduardo Trapani	:	Added a check for the return value of
 *				ipx_if_offset that crashed sock_alloc_send_skb.
 *				Added spx_datagram_poll() so that select()
 *				works now on SPX sockets.  Added updating
 *				of the alloc count to follow rmt_seq.
 *
 *	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.
 *
 *	None of the authors or maintainers or their employers admit
 *	liability nor provide warranty for any of this software.
 *	This material is provided "as is" and at no charge.
 */

#include <linux/config.h>
#if defined(CONFIG_SPX) || defined(CONFIG_SPX_MODULE)
#include <linux/module.h>
#include <net/ipx.h>
#include <net/spx.h>
#include <net/sock.h>
#include <asm/byteorder.h>
#include <asm/uaccess.h>
#include <linux/uio.h>
#include <linux/unistd.h>
#include <linux/poll.h>

static struct proto_ops *ipx_operations;
static struct proto_ops spx_ops;
static __u16  connids;

/* Functions needed for SPX connection start up */
static int spx_transmit(struct sock *sk,struct sk_buff *skb,int type,int len);
static void spx_retransmit(unsigned long data);
static void spx_watchdog(unsigned long data);
void spx_rcv(struct sock *sk, int bytes);

extern void ipx_remove_socket(struct sock *sk);

/* Datagram poll:	the same code as datagram_poll() in net/core
			but the right spx buffers are looked at and
			there is no question on the type of the socket
			*/
static unsigned int spx_datagram_poll(struct file * file, struct socket *sock, poll_table *wait)
{
	struct sock *sk = sock->sk;
	struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
	unsigned int mask;

	poll_wait(file, sk->sleep, wait);
	mask = 0;

	/* exceptional events? */
	if (sk->err || !skb_queue_empty(&sk->error_queue))
		mask |= POLLERR;
	if (sk->shutdown & RCV_SHUTDOWN)
		mask |= POLLHUP;

	/* readable? */
	if (!skb_queue_empty(&pdata->rcv_queue))
		mask |= POLLIN | POLLRDNORM;

	/* Need to check for termination and startup */
	if (sk->state==TCP_CLOSE)
		mask |= POLLHUP;
	/* connection hasn't started yet? */
	if (sk->state == TCP_SYN_SENT)
		return mask;

	/* writable? */
	if (sock_writeable(sk))
		mask |= POLLOUT | POLLWRNORM | POLLWRBAND;
	else
		sk->socket->flags |= SO_NOSPACE;

	return mask;
}

/* Create the SPX specific data */
static int spx_sock_init(struct sock *sk)
{
        struct spx_opt *pdata = &sk->tp_pinfo.af_spx;

        pdata->state            = SPX_CLOSED;
        pdata->sequence         = 0;
	pdata->acknowledge	= 0;
        pdata->source_connid    = htons(connids);
	pdata->rmt_seq		= 0;
        connids++;

        pdata->owner            = (void *)sk;
        pdata->sndbuf           = sk->sndbuf;

        pdata->watchdog.function = spx_watchdog;
        pdata->watchdog.data    = (unsigned long)sk;
        pdata->wd_interval      = VERIFY_TIMEOUT;
	pdata->retransmit.function = spx_retransmit;
	pdata->retransmit.data	= (unsigned long)sk;
	pdata->retransmits	= 0;
        pdata->retries          = 0;
        pdata->max_retries      = RETRY_COUNT;

	skb_queue_head_init(&pdata->rcv_queue);
	skb_queue_head_init(&pdata->transmit_queue);
	skb_queue_head_init(&pdata->retransmit_queue);

        return (0);
}

static int spx_create(struct socket *sock, int protocol)
{
	struct sock *sk;

	/*
	 *	Called on connection receive so cannot be GFP_KERNEL
	 */
	 
	sk = sk_alloc(PF_IPX, GFP_ATOMIC, 1);
	if(sk == NULL)
                return (-ENOMEM);

	switch(sock->type)
        {
                case SOCK_SEQPACKET:
			sock->ops = &spx_ops;
			break;
		default:
			sk_free(sk);
                        return (-ESOCKTNOSUPPORT);
        }

	sock_init_data(sock, sk);
	spx_sock_init(sk);
	sk->data_ready  = spx_rcv;
	sk->destruct 	= NULL;
        sk->no_check 	= 1;

        MOD_INC_USE_COUNT;

	return (0);
}


void spx_close_socket(struct sock *sk)
{
	struct spx_opt *pdata = &sk->tp_pinfo.af_spx;

	pdata->state	= SPX_CLOSED;
	sk->state 	= TCP_CLOSE;
	del_timer(&pdata->retransmit);
	del_timer(&pdata->watchdog);
}

void spx_destroy_socket(struct sock *sk)
{
	struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
	struct sk_buff *skb;

        ipx_remove_socket(sk);
        while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
                kfree_skb(skb);
	while((skb = skb_dequeue(&pdata->transmit_queue)) != NULL)
                kfree_skb(skb);
	while((skb = skb_dequeue(&pdata->retransmit_queue)) != NULL)
		kfree_skb(skb);
	while((skb = skb_dequeue(&pdata->rcv_queue)) != NULL)
                kfree_skb(skb);

        sk_free(sk);
	MOD_DEC_USE_COUNT;
}

/* Release an SPX socket */
static int spx_release(struct socket *sock)
{
 	struct sock *sk = sock->sk;
	struct spx_opt *pdata = &sk->tp_pinfo.af_spx;

	if(sk == NULL)
		return (0);
	if(!sk->dead)
                sk->state_change(sk);
        sk->dead = 1;

	if(pdata->state != SPX_CLOSED)
	{
		spx_transmit(sk, NULL, DISCON, 0);
		spx_close_socket(sk);
	}

	sock->sk	= NULL;
	sk->socket 	= NULL;
	spx_destroy_socket(sk);

        return (0);
}

/* Move a socket into listening state. */
static int spx_listen(struct socket *sock, int backlog)
{
        struct sock *sk = sock->sk;

        if(sock->state != SS_UNCONNECTED)
                return (-EINVAL);
	if(sock->type != SOCK_SEQPACKET)
		return (-EOPNOTSUPP);
        if(sk->zapped != 0)
                return (-EAGAIN);

        sk->max_ack_backlog = backlog;
        if(sk->state != TCP_LISTEN)
        {
                sk->ack_backlog = 0;
                sk->state = TCP_LISTEN;
        }
        sk->socket->flags |= SO_ACCEPTCON;

        return (0);
}

/* Accept a pending SPX connection */
static int spx_accept(struct socket *sock, struct socket *newsock, int flags)
{
        struct sock *sk;
        struct sock *newsk;
        struct sk_buff *skb;
	int err;

	if(sock->sk == NULL)
		return (-EINVAL);
	sk = sock->sk;

        if((sock->state != SS_UNCONNECTED) || !(sock->flags & SO_ACCEPTCON))
                return (-EINVAL);
        if(sock->type != SOCK_SEQPACKET)
		return (-EOPNOTSUPP);
	if(sk->state != TCP_LISTEN)
                return (-EINVAL);

	cli();
	do {
		skb = skb_dequeue(&sk->receive_queue);
		if(skb == NULL)
		{
                	if(flags & O_NONBLOCK)
			{
                                sti();
                                return (-EWOULDBLOCK);
                        }
                	interruptible_sleep_on(sk->sleep);
                	if(signal_pending(current))
                	{
                        	sti();
                        	return (-ERESTARTSYS);
                	}
		}
	} while (skb == NULL);

	newsk 		= skb->sk;
        newsk->pair 	= NULL;
	sti();

	err = spx_transmit(newsk, skb, CONACK, 0);   /* Connection ACK */
	if(err)
		return (err);

	/* Now attach up the new socket */
	sock->sk 	= NULL;
        sk->ack_backlog--;
        newsock->sk 	= newsk;
	newsk->state 	= TCP_ESTABLISHED;
	newsk->protinfo.af_ipx.dest_addr = newsk->tp_pinfo.af_spx.dest_addr;

	return (0);
}

/* Build a connection to an SPX socket */
static int spx_connect(struct socket *sock, struct sockaddr *uaddr,
                int addr_len, int flags)
{
	struct sock *sk = sock->sk;
        struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
        struct sockaddr_ipx src;
	struct sk_buff *skb;
	int size, err;

	size = sizeof(src);
	err  = ipx_operations->getname(sock, (struct sockaddr *)&src, &size, 0);
	if(err)
		return (err);

        pdata->source_addr.net	= src.sipx_network;
        memcpy(pdata->source_addr.node, src.sipx_node, IPX_NODE_LEN);
        pdata->source_addr.sock = (unsigned short)src.sipx_port;

	err = ipx_operations->connect(sock, uaddr, addr_len, flags);
        if(err)
                return (err);

        pdata->dest_addr = sk->protinfo.af_ipx.dest_addr;
	pdata->state	 = SPX_CONNECTING;
	sock->state	 = SS_CONNECTING;
        sk->state	 = TCP_SYN_SENT;

        /* Send Connection request */
	err = spx_transmit(sk, NULL, CONREQ, 0);
        if(err)
                return (err);

	cli();
        do {
                skb = skb_dequeue(&sk->receive_queue);
                if(skb == NULL)
                {
                        if(flags & O_NONBLOCK)
                        {
                                sti();
                                return (-EWOULDBLOCK);
                        }
                        interruptible_sleep_on(sk->sleep);
                        if(signal_pending(current))
                        {
                                sti();
                                return (-ERESTARTSYS);
                        }
                }
        } while (skb == NULL);

        if(pdata->state == SPX_CLOSED)
        {
		sti();
                del_timer(&pdata->watchdog);
                return (-ETIMEDOUT);
        }

	sock->state	= SS_CONNECTED;
	sk->state 	= TCP_ESTABLISHED;
	kfree_skb(skb);
	sti();

        return (0);
}

/*
 * Calculate the timeout for a packet. Thankfully SPX has a large
 * fudge factor (3/4 secs) and does not pay much attention to RTT.
 * As we simply have a default retry time of 1*HZ and a max retry
 * time of 5*HZ. Between those values we increase the timeout based
 * on the number of retransmit tries.
 *
 * FixMe: This is quite fake, but will work for now. (JS)
 */
static inline unsigned long spx_calc_rtt(int tries)
{
        if(tries < 1)
                return (RETRY_TIME);
        if(tries > 5)
                return (MAX_RETRY_DELAY);
        return (tries * HZ);
}

static int spx_route_skb(struct spx_opt *pdata, struct sk_buff *skb, int type)
{
	struct sk_buff *skb2;
	int err = 0;

	skb = skb_unshare(skb, GFP_ATOMIC);
	if(skb == NULL)
		return (-ENOBUFS);

	switch(type)
	{
		case (CONREQ):
		case (DATA):
			if(!skb_queue_empty(&pdata->retransmit_queue))
			{
				skb_queue_tail(&pdata->transmit_queue, skb);
				return 0;
			}

		case (TQUEUE):
			pdata->retransmit.expires = jiffies + spx_calc_rtt(0);
			add_timer(&pdata->retransmit);

			skb2 = skb_clone(skb, GFP_BUFFER);
	                if(skb2 == NULL)
        	                return -ENOBUFS;
        	        skb_queue_tail(&pdata->retransmit_queue, skb2);

		case (ACK):
		case (CONACK):
		case (WDREQ):
		case (WDACK):
		case (DISCON):
		case (DISACK):
		case (RETRAN):
		default:
			/* Send data */
        		err = ipxrtr_route_skb(skb);
        		if(err)
                		kfree_skb(skb);
	}

	return (err);
}

/* SPX packet transmit engine */
static int spx_transmit(struct sock *sk, struct sk_buff *skb, int type, int len)
{
        struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
        struct ipxspxhdr *ipxh;
	unsigned long flags;
	int err;

	if(skb == NULL)
	{
		int offset  = ipx_if_offset(pdata->dest_addr.net);
        	int size    = offset + sizeof(struct ipxspxhdr);

        	if (offset < 0) /* ENETUNREACH */
        		return(-ENETUNREACH);

		save_flags(flags);
		cli();
        	skb = sock_alloc_send_skb(sk, size, 1, 0, &err);
        	if(skb == NULL)
                	return (-ENOMEM);
        	skb_reserve(skb, offset);
        	skb->h.raw = skb->nh.raw = skb_put(skb,sizeof(struct ipxspxhdr));
		restore_flags(flags);
	}

	/* IPX header */
	ipxh = (struct ipxspxhdr *)skb->nh.raw;
	ipxh->ipx.ipx_checksum  = 0xFFFF;
	ipxh->ipx.ipx_pktsize   = htons(SPX_SYS_PKT_LEN);
        ipxh->ipx.ipx_tctrl     = 0;
	ipxh->ipx.ipx_type 	= IPX_TYPE_SPX;
        ipxh->ipx.ipx_dest      = pdata->dest_addr;
        ipxh->ipx.ipx_source    = pdata->source_addr;

	/* SPX header */
        ipxh->spx.dtype         = 0;
	ipxh->spx.sequence      = htons(pdata->sequence);
	ipxh->spx.ackseq        = htons(pdata->rmt_seq);
	ipxh->spx.sconn         = pdata->source_connid;
        ipxh->spx.dconn         = pdata->dest_connid;
        ipxh->spx.allocseq      = htons(pdata->alloc);

	/* Reset/Set WD timer */
        del_timer(&pdata->watchdog);
        pdata->watchdog.expires = jiffies + VERIFY_TIMEOUT;
        add_timer(&pdata->watchdog);

	switch(type)
	{
		case (DATA):	/* Data */
			ipxh->ipx.ipx_pktsize 	= htons(SPX_SYS_PKT_LEN + len);
			ipxh->spx.cctl 		= (CCTL_ACK | CCTL_EOM);
                	pdata->sequence++;
			break;

		case (ACK):	/* ACK */
			pdata->rmt_seq++;
		case (WDACK):	/* WD ACK */
		case (CONACK):	/* Connection ACK */
			ipxh->spx.cctl 		= CCTL_SYS;
			ipxh->spx.ackseq 	= htons(pdata->rmt_seq);
			break;

		case (CONREQ):	/* Connection Request */
			del_timer(&pdata->watchdog);
		case (WDREQ):	/* WD Request */
			pdata->source_connid    = htons(connids++);
                	pdata->dest_connid      = 0xFFFF;
                	pdata->alloc 		= 3 + pdata->rmt_seq;
			ipxh->spx.cctl          = (CCTL_ACK | CCTL_SYS);
			ipxh->spx.sconn         = pdata->source_connid;
		        ipxh->spx.dconn         = pdata->dest_connid;
		        ipxh->spx.allocseq      = htons(pdata->alloc);
			break;

		case (DISCON):	/* Informed Disconnect */
			ipxh->spx.cctl 		= CCTL_ACK;
			ipxh->spx.dtype 	= SPX_DTYPE_ECONN;
			break;

		case (DISACK):	/* Informed Disconnect ACK */
			ipxh->spx.cctl  	= 0;
			ipxh->spx.dtype 	= SPX_DTYPE_ECACK;
			ipxh->spx.sequence 	= 0;
			ipxh->spx.ackseq 	= htons(pdata->rmt_seq++);
			break;

		default:
			return (-EOPNOTSUPP);
	}

	/* Send data */
        return (spx_route_skb(pdata, skb, type));
}

/* Check the state of the connection and send a WD request if needed. */
static void spx_watchdog(unsigned long data)
{
	struct sock *sk = (struct sock*)data;
        struct spx_opt *pdata = &sk->tp_pinfo.af_spx;

        del_timer(&pdata->watchdog);
	if(pdata->state == SPX_CLOSED)
                return;
	if(pdata->retries > pdata->max_retries)
        {
		spx_close_socket(sk);	/* Unilateral Abort */
                return;
        }

        /* Send WD request */
	spx_transmit(sk, NULL, WDREQ, 0);
	pdata->retries++;

        return;
}

static void spx_retransmit(unsigned long data)
{
	struct sock *sk = (struct sock*)data;
        struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
	struct sk_buff *skb;
	unsigned long flags;
	int err;

	del_timer(&pdata->retransmit);
	if(pdata->state == SPX_CLOSED)
		return;
	if(pdata->retransmits > RETRY_COUNT)
	{
		spx_close_socket(sk);   /* Unilateral Abort */
                return;
        }

	/* Need to leave skb on the queue, aye the fear */
	save_flags(flags);
	cli();
	skb = skb_peek(&pdata->retransmit_queue);
	if(skb_cloned(skb))
                skb = skb_copy(skb, GFP_ATOMIC);
        else
                skb = skb_clone(skb, GFP_ATOMIC);
	restore_flags(flags);

	pdata->retransmit.expires = jiffies + spx_calc_rtt(pdata->retransmits);
	add_timer(&pdata->retransmit);

	err = spx_route_skb(pdata, skb, RETRAN);
	pdata->retransmits++;

	return;
}

/* Check packet for retransmission, ConReqAck aware */
static int spx_retransmit_chk(struct spx_opt *pdata, int ackseq, int type)
{
	struct ipxspxhdr *ipxh;
	struct sk_buff *skb;

	skb = skb_dequeue(&pdata->retransmit_queue);
	if(!skb)
		return (-ENOENT);

	/* Check Data/ACK seq */
	switch(type)
	{
		case ACK:	/* Check Sequence, Should == 1 */
			ipxh = (struct ipxspxhdr *)skb->nh.raw;
			if(!(ntohs(ipxh->spx.sequence) - htons(ackseq)))
				break;

		case CONACK:
			del_timer(&pdata->retransmit);
			pdata->retransmits = 0;
			kfree_skb(skb);
			if(skb_queue_empty(&pdata->retransmit_queue))
			{
				skb = skb_dequeue(&pdata->transmit_queue);
				if(skb != NULL)
					spx_route_skb(pdata, skb, TQUEUE);
			}
			return (0);
	}

	skb_queue_head(&pdata->retransmit_queue, skb);
	return (-1);
}

/* SPX packet receive engine */
void spx_rcv(struct sock *sk, int bytes)
{
	struct sk_buff *skb;
	struct ipxspxhdr *ipxh;
	struct spx_opt *pdata = &sk->tp_pinfo.af_spx;

	skb = skb_dequeue(&sk->receive_queue);
	if(skb == NULL)
		return;
	ipxh = (struct ipxspxhdr *)skb->nh.raw;

	/* Can't receive on a closed connection */
        if((pdata->state == SPX_CLOSED) && (ipxh->spx.sequence != 0))
		goto toss_skb;
	if(ntohs(ipxh->ipx.ipx_pktsize) < SPX_SYS_PKT_LEN)
		goto toss_skb;
        if(ipxh->ipx.ipx_type != IPX_TYPE_SPX)
		goto toss_skb;
        if(ntohs(ipxh->spx.ackseq) > pdata->sequence)
		goto toss_skb;

	/* Reset WD timer on any received packet */
	del_timer(&pdata->watchdog);
	pdata->retries = 0;
	pdata->watchdog.expires = jiffies + ABORT_TIMEOUT;
        add_timer(&pdata->watchdog);

	switch(ipxh->spx.cctl)
	{
		case (CCTL_SYS | CCTL_ACK):
			if((ipxh->spx.sequence == 0)	/* ConReq */
				&& (ipxh->spx.ackseq == 0)
				&& (ipxh->spx.dconn == 0xFFFF))
			{
				pdata->state		= SPX_CONNECTED;
				pdata->dest_addr        = ipxh->ipx.ipx_source;
				pdata->source_addr      = ipxh->ipx.ipx_dest;
				pdata->dest_connid      = ipxh->spx.sconn;
				pdata->alloc = 3 + ntohs(ipxh->spx.sequence);

				skb_queue_tail(&sk->receive_queue, skb);
				wake_up_interruptible(sk->sleep);
			}
			else	/* WD Request */
				spx_transmit(sk, skb, WDACK, 0);
			goto finish;

		case CCTL_SYS:	/* ACK */
			if((ipxh->spx.dtype == 0)       /* ConReq ACK */
                                && (ipxh->spx.sconn != 0xFFFF)
                                && (ipxh->spx.dconn != 0xFFFF)
                                && (ipxh->spx.sequence == 0)
                                && (ipxh->spx.ackseq == 0)
                                && (pdata->state != SPX_CONNECTED))
                        {
                                pdata->state = SPX_CONNECTED;
				pdata->dest_connid = ipxh->spx.sconn;

				if(spx_retransmit_chk(pdata, 0, CONACK) < 0)
					goto toss_skb;

                                skb_queue_tail(&sk->receive_queue, skb);
                                wake_up_interruptible(sk->sleep);
                                goto finish;
                        }

			spx_retransmit_chk(pdata, ipxh->spx.ackseq, ACK);
			goto toss_skb;

		case (CCTL_ACK):
			/* Informed Disconnect */
			if(ipxh->spx.dtype == SPX_DTYPE_ECONN)
			{
				
				spx_transmit(sk, skb, DISACK, 0);
				spx_close_socket(sk);
				goto finish;
			}
			/* Fall through */

		default:
			if(ntohs(ipxh->spx.sequence) == pdata->rmt_seq)
			{
				pdata->rmt_seq = ntohs(ipxh->spx.sequence);
				pdata->rmt_ack = ntohs(ipxh->spx.ackseq);
				pdata->alloc   = pdata->rmt_seq + 3;
				if(pdata->rmt_ack > 0 || pdata->rmt_ack == 0)
					spx_retransmit_chk(pdata,pdata->rmt_ack, ACK);

				skb_queue_tail(&pdata->rcv_queue, skb);
				wake_up_interruptible(sk->sleep);
				if(ipxh->spx.cctl&CCTL_ACK)
					spx_transmit(sk, NULL, ACK, 0);
				goto finish;
			}

			if(ipxh->spx.dtype == SPX_DTYPE_ECACK)
			{
				if(pdata->state != SPX_CLOSED)
					spx_close_socket(sk);
				goto toss_skb;
			}
	}

toss_skb:	/* Catch All */
	kfree_skb(skb);
finish:
        return;
}

/* Get message/packet data from user-land */
static int spx_sendmsg(struct socket *sock, struct msghdr *msg, int len,
			struct scm_cookie *scm)
{
	struct sock *sk = sock->sk;
	int flags = msg->msg_flags;
	struct sk_buff *skb;
	int err, offset, size;

	if(len > 534)
                return (-EMSGSIZE);
        if(sk->zapped)
                return (-ENOTCONN); /* Socket not bound */
	if(flags&~MSG_DONTWAIT)
                return (-EINVAL);

	offset	= ipx_if_offset(sk->tp_pinfo.af_spx.dest_addr.net);
        size 	= offset + sizeof(struct ipxspxhdr) + len;

	cli();
        skb  	= sock_alloc_send_skb(sk, size, 0, flags&MSG_DONTWAIT, &err);
        if(skb == NULL)
                return (err);
	sti();

	skb->sk = sk;
        skb_reserve(skb, offset);
	skb->h.raw = skb->nh.raw = skb_put(skb, sizeof(struct ipxspxhdr));

	err = memcpy_fromiovec(skb_put(skb, len), msg->msg_iov, len);
	if(err)
        {
                kfree_skb(skb);
                return (-EFAULT);
        }

	err = spx_transmit(sk, skb, DATA, len);
	if(err)
		return (-EAGAIN);

        return (len);
}

/* Send message/packet data to user-land */
static int spx_recvmsg(struct socket *sock, struct msghdr *msg, int size,
			int flags, struct scm_cookie *scm)
{
	struct sk_buff *skb;
	struct ipxspxhdr *ispxh;
	struct sock *sk = sock->sk;
	struct spx_opt *pdata = &sk->tp_pinfo.af_spx;
	struct sockaddr_ipx *sipx = (struct sockaddr_ipx *)msg->msg_name;
	int copied, err;

        if(sk->zapped)
                return (-ENOTCONN); /* Socket not bound */

	lock_sock(sk);
restart:
        while(skb_queue_empty(&pdata->rcv_queue))      /* No data */
        {
                /* Socket errors? */
                err = sock_error(sk);
                if(err)
			return (err);

                /* Socket shut down? */
                if(sk->shutdown & RCV_SHUTDOWN)
			return (-ESHUTDOWN);

                /* handle signals */
                if(signal_pending(current))
			return (-ERESTARTSYS);

                /* User doesn't want to wait */
                if(flags&MSG_DONTWAIT)
			return (-EAGAIN);

		release_sock(sk);
        	save_flags(flags);
        	cli();
        	if(skb_peek(&pdata->rcv_queue) == NULL)
                	interruptible_sleep_on(sk->sleep);
        	restore_flags(flags);
        	lock_sock(sk);
        }

        skb = skb_dequeue(&pdata->rcv_queue);
        if(skb == NULL) 
		goto restart;

	ispxh 	= (struct ipxspxhdr *)skb->nh.raw;
	copied 	= ntohs(ispxh->ipx.ipx_pktsize) - SPX_SYS_PKT_LEN;
        if(copied > size)
	{
                copied = size;
                msg->msg_flags |= MSG_TRUNC;
        }

	err = memcpy_toiovec(msg->msg_iov, skb->nh.raw+SPX_SYS_PKT_LEN, copied);
        if(err)
                return (-EFAULT);

	msg->msg_namelen = sizeof(*sipx);
	if(sipx)
	{
		sipx->sipx_family	= AF_IPX;
                sipx->sipx_port		= ispxh->ipx.ipx_source.sock;
                memcpy(sipx->sipx_node,ispxh->ipx.ipx_source.node,IPX_NODE_LEN);
                sipx->sipx_network	= ispxh->ipx.ipx_source.net;
                sipx->sipx_type 	= ispxh->ipx.ipx_type;
        }
	kfree_skb(skb);
        release_sock(sk);

	return (copied);
}

/*
 * Functions which just wrap their IPX cousins
 */

static int spx_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{
        int err;
        err = ipx_operations->bind(sock, uaddr, addr_len);
        return (err);
}

static int spx_getname (struct socket *sock, struct sockaddr *uaddr,
                         int *usockaddr_len, int peer)
{
	int err;
	err = ipx_operations->getname(sock, uaddr, usockaddr_len, peer);
	return (err);
}

static int spx_ioctl (struct socket *sock, unsigned int cmd,
                         unsigned long arg)
{
	int err;
	err = ipx_operations->ioctl(sock, cmd, arg);
	return (err);
}

static int spx_setsockopt(struct socket *sock, int level, int optname,
                         char *optval, int optlen)
{
	int err;
	err = ipx_operations->setsockopt(sock, level, optname, optval, optlen);
	return (err);
}

static int spx_getsockopt(struct socket *sock, int level, int optname,
                         char *optval, int *optlen)
{
	int err;
	err = ipx_operations->getsockopt(sock, level, optname, optval, optlen);
	return (err);
}

static struct proto_ops SOCKOPS_WRAPPED(spx_ops) = {
        PF_IPX,
        spx_release,
        spx_bind,
        spx_connect,
        sock_no_socketpair,
        spx_accept,
	spx_getname,
        spx_datagram_poll,
	spx_ioctl,
        spx_listen,
        sock_no_shutdown,
	spx_setsockopt,
	spx_getsockopt,
        sock_no_fcntl,
        spx_sendmsg,
        spx_recvmsg,
	sock_no_mmap
};

#include <linux/smp_lock.h>
SOCKOPS_WRAP(spx, PF_IPX);


static struct net_proto_family spx_family_ops=
{
	PF_IPX,
	spx_create
};


void spx_proto_init(void)
{
	int error;

	connids = (__u16)jiffies;	/* initalize random */

	error = ipx_register_spx(&ipx_operations, &spx_family_ops);
        if (error)
                printk(KERN_ERR "SPX: unable to register with IPX.\n");

	/* route socket(PF_IPX, SOCK_SEQPACKET) calls through spx_create() */

	printk(KERN_INFO "NET4: Sequenced Packet eXchange (SPX) 0.02 for Linux NET4.0\n");
	return;
}

void spx_proto_finito(void)
{
	ipx_unregister_spx();
	return;
}

#ifdef MODULE

int init_module(void)
{
        spx_proto_init();
        return 0;
}

void cleanup_module(void)
{
        spx_proto_finito();
        return;
}

#endif /* MODULE */
#endif /* CONFIG_SPX || CONFIG_SPX_MODULE */
