summaryrefslogtreecommitdiff
path: root/pfinet/linux-inet/datagram.c
blob: cd248cfb1321a815d0edb6a63e8cfd0f75c487cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/*
 *	SUCS NET3:
 *
 *	Generic datagram handling routines. These are generic for all protocols. Possibly a generic IP version on top
 *	of these would make sense. Not tonight however 8-).
 *	This is used because UDP, RAW, PACKET and the to be released IPX layer all have identical select code and mostly
 *	identical recvfrom() code. So we share it here. The select was shared before but buried in udp.c so I moved it.
 *
 *	Authors:	Alan Cox <iiitac@pyr.swan.ac.uk>. (datagram_select() from old udp.c code)
 *
 *	Fixes:
 *		Alan Cox	:	NULL return from skb_peek_copy() understood
 *		Alan Cox	:	Rewrote skb_read_datagram to avoid the skb_peek_copy stuff.
 *		Alan Cox	:	Added support for SOCK_SEQPACKET. IPX can no longer use the SO_TYPE hack but
 *					AX.25 now works right, and SPX is feasible.
 *		Alan Cox	:	Fixed write select of non IP protocol crash.
 *		Florian  La Roche:	Changed for my new skbuff handling.
 *
 *	Note:
 *		A lot of this will change when the protocol/socket separation
 *	occurs. Using this will make things reasonably clean.
 */

#include <linux/types.h>
#include <linux/kernel.h>
#include <asm/segment.h>
#include <asm/system.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/in.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/inet.h>
#include <linux/netdevice.h>
#include "ip.h"
#include "protocol.h"
#include "route.h"
#include "tcp.h"
#include "udp.h"
#include <linux/skbuff.h>
#include "sock.h"


/*
 *	Get a datagram skbuff, understands the peeking, nonblocking wakeups and possible
 *	races. This replaces identical code in packet,raw and udp, as well as the yet to
 *	be released IPX support. It also finally fixes the long standing peek and read
 *	race for datagram sockets. If you alter this routine remember it must be
 *	re-entrant.
 */

struct sk_buff *skb_recv_datagram(struct sock *sk, unsigned flags, int noblock, int *err)
{
	struct sk_buff *skb;
	unsigned long intflags;

	/* Socket is inuse - so the timer doesn't attack it */
	save_flags(intflags);
restart:
	sk->inuse = 1;
	while(skb_peek(&sk->receive_queue) == NULL)	/* No data */
	{
		/* If we are shutdown then no more data is going to appear. We are done */
		if (sk->shutdown & RCV_SHUTDOWN)
		{
			release_sock(sk);
			*err=0;
			return NULL;
		}

		if(sk->err)
		{
			release_sock(sk);
			*err=-sk->err;
			sk->err=0;
			return NULL;
		}

		/* Sequenced packets can come disconnected. If so we report the problem */
		if(sk->type==SOCK_SEQPACKET && sk->state!=TCP_ESTABLISHED)
		{
			release_sock(sk);
			*err=-ENOTCONN;
			return NULL;
		}

		/* User doesn't want to wait */
		if (noblock)
		{
			release_sock(sk);
			*err=-EAGAIN;
			return NULL;
		}
		release_sock(sk);

		/* Interrupts off so that no packet arrives before we begin sleeping.
		   Otherwise we might miss our wake up */
		cli();
		if (skb_peek(&sk->receive_queue) == NULL)
		{
			interruptible_sleep_on(sk->sleep);
			/* Signals may need a restart of the syscall */
			if (current->signal & ~current->blocked)
			{
				restore_flags(intflags);;
				*err=-ERESTARTSYS;
				return(NULL);
			}
			if(sk->err != 0)	/* Error while waiting for packet
						   eg an icmp sent earlier by the
						   peer has finally turned up now */
			{
				*err = -sk->err;
				sk->err=0;
				restore_flags(intflags);
				return NULL;
			}
		}
		sk->inuse = 1;
		restore_flags(intflags);
	  }
	  /* Again only user level code calls this function, so nothing interrupt level
	     will suddenly eat the receive_queue */
	  if (!(flags & MSG_PEEK))
	  {
		skb=skb_dequeue(&sk->receive_queue);
		if(skb!=NULL)
			skb->users++;
		else
			goto restart;	/* Avoid race if someone beats us to the data */
	  }
	  else
	  {
		cli();
		skb=skb_peek(&sk->receive_queue);
		if(skb!=NULL)
			skb->users++;
		restore_flags(intflags);
		if(skb==NULL)	/* shouldn't happen but .. */
			*err=-EAGAIN;
	  }
	  return skb;
}

void skb_free_datagram(struct sk_buff *skb)
{
	unsigned long flags;

	save_flags(flags);
	cli();
	skb->users--;
	if(skb->users>0)
	{
		restore_flags(flags);
		return;
	}
	/* See if it needs destroying */
	if(!skb->next && !skb->prev)	/* Been dequeued by someone - ie it's read */
		kfree_skb(skb,FREE_READ);
	restore_flags(flags);
}

void skb_copy_datagram(struct sk_buff *skb, int offset, char *to, int size)
{
	/* We will know all about the fraglist options to allow >4K receives
	   but not this release */
	memcpy_tofs(to,skb->h.raw+offset,size);
}

/*
 *	Datagram select: Again totally generic. Moved from udp.c
 *	Now does seqpacket.
 */

int datagram_select(struct sock *sk, int sel_type, select_table *wait)
{
	select_wait(sk->sleep, wait);
	switch(sel_type)
	{
		case SEL_IN:
			if (sk->type==SOCK_SEQPACKET && sk->state==TCP_CLOSE)
			{
				/* Connection closed: Wake up */
				return(1);
			}
			if (skb_peek(&sk->receive_queue) != NULL || sk->err != 0)
			{	/* This appears to be consistent
				   with other stacks */
				return(1);
			}
			return(0);

		case SEL_OUT:
			if (sk->prot && sk->prot->wspace(sk) >= MIN_WRITE_SPACE)
			{
				return(1);
			}
			if (sk->prot==NULL && sk->sndbuf-sk->wmem_alloc >= MIN_WRITE_SPACE)
			{
				return(1);
			}
			return(0);

		case SEL_EX:
			if (sk->err)
				return(1); /* Socket has gone into error state (eg icmp error) */
			return(0);
	}
	return(0);
}