summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/net/llc_conn.h1
-rw-r--r--net/llc/af_llc.c180
-rw-r--r--net/llc/llc_conn.c2
-rw-r--r--net/llc/llc_proc.c2
-rw-r--r--net/llc/llc_sap.c5
5 files changed, 154 insertions, 36 deletions
diff --git a/include/net/llc_conn.h b/include/net/llc_conn.h
index e44f494bbef..54852ff6033 100644
--- a/include/net/llc_conn.h
+++ b/include/net/llc_conn.h
@@ -38,6 +38,7 @@ struct llc_sock {
struct llc_addr laddr; /* lsap/mac pair */
struct llc_addr daddr; /* dsap/mac pair */
struct net_device *dev; /* device to send to remote */
+ u32 copied_seq; /* head of yet unread data */
u8 retry_count; /* number of retries */
u8 ack_must_be_send;
u8 first_pdu_Ns;
diff --git a/net/llc/af_llc.c b/net/llc/af_llc.c
index 7aa51eb79b1..59d02cbbeb9 100644
--- a/net/llc/af_llc.c
+++ b/net/llc/af_llc.c
@@ -648,53 +648,167 @@ out:
* llc_ui_recvmsg - copy received data to the socket user.
* @sock: Socket to copy data from.
* @msg: Various user space related information.
- * @size: Size of user buffer.
+ * @len: Size of user buffer.
* @flags: User specified flags.
*
* Copy received data to the socket user.
* Returns non-negative upon success, negative otherwise.
*/
static int llc_ui_recvmsg(struct kiocb *iocb, struct socket *sock,
- struct msghdr *msg, size_t size, int flags)
+ struct msghdr *msg, size_t len, int flags)
{
- struct sock *sk = sock->sk;
struct sockaddr_llc *uaddr = (struct sockaddr_llc *)msg->msg_name;
- struct sk_buff *skb;
+ const int nonblock = flags & MSG_DONTWAIT;
+ struct sk_buff *skb = NULL;
+ struct sock *sk = sock->sk;
+ struct llc_sock *llc = llc_sk(sk);
size_t copied = 0;
- int rc = -ENOMEM;
- int noblock = flags & MSG_DONTWAIT;
+ u32 peek_seq = 0;
+ u32 *seq;
+ unsigned long used;
+ int target; /* Read at least this many bytes */
+ long timeo;
- dprintk("%s: receiving in %02X from %02X\n", __FUNCTION__,
- llc_sk(sk)->laddr.lsap, llc_sk(sk)->daddr.lsap);
lock_sock(sk);
- if (skb_queue_empty(&sk->sk_receive_queue)) {
- rc = llc_wait_data(sk, sock_rcvtimeo(sk, noblock));
- if (rc)
- goto out;
- }
- skb = skb_dequeue(&sk->sk_receive_queue);
- if (!skb) /* shutdown */
+ copied = -ENOTCONN;
+ if (sk->sk_state == TCP_LISTEN)
goto out;
- copied = skb->len;
- if (copied > size)
- copied = size;
- rc = skb_copy_datagram_iovec(skb, 0, msg->msg_iov, copied);
- if (rc)
- goto dgram_free;
- if (skb->len > copied) {
- skb_pull(skb, copied);
- skb_queue_head(&sk->sk_receive_queue, skb);
- }
- if (uaddr)
- memcpy(uaddr, llc_ui_skb_cb(skb), sizeof(*uaddr));
- msg->msg_namelen = sizeof(*uaddr);
- if (!skb->next) {
-dgram_free:
- kfree_skb(skb);
- }
+
+ timeo = sock_rcvtimeo(sk, nonblock);
+
+ seq = &llc->copied_seq;
+ if (flags & MSG_PEEK) {
+ peek_seq = llc->copied_seq;
+ seq = &peek_seq;
+ }
+
+ target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);
+ copied = 0;
+
+ do {
+ u32 offset;
+
+ /*
+ * We need to check signals first, to get correct SIGURG
+ * handling. FIXME: Need to check this doesn't impact 1003.1g
+ * and move it down to the bottom of the loop
+ */
+ if (signal_pending(current)) {
+ if (copied)
+ break;
+ copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;
+ break;
+ }
+
+ /* Next get a buffer. */
+
+ skb = skb_peek(&sk->sk_receive_queue);
+ if (skb) {
+ offset = *seq;
+ goto found_ok_skb;
+ }
+ /* Well, if we have backlog, try to process it now yet. */
+
+ if (copied >= target && !sk->sk_backlog.tail)
+ break;
+
+ if (copied) {
+ if (sk->sk_err ||
+ sk->sk_state == TCP_CLOSE ||
+ (sk->sk_shutdown & RCV_SHUTDOWN) ||
+ !timeo ||
+ (flags & MSG_PEEK))
+ break;
+ } else {
+ if (sock_flag(sk, SOCK_DONE))
+ break;
+
+ if (sk->sk_err) {
+ copied = sock_error(sk);
+ break;
+ }
+ if (sk->sk_shutdown & RCV_SHUTDOWN)
+ break;
+
+ if (sk->sk_state == TCP_CLOSE) {
+ if (!sock_flag(sk, SOCK_DONE)) {
+ /*
+ * This occurs when user tries to read
+ * from never connected socket.
+ */
+ copied = -ENOTCONN;
+ break;
+ }
+ break;
+ }
+ if (!timeo) {
+ copied = -EAGAIN;
+ break;
+ }
+ }
+
+ if (copied >= target) { /* Do not sleep, just process backlog. */
+ release_sock(sk);
+ lock_sock(sk);
+ } else
+ sk_wait_data(sk, &timeo);
+
+ if ((flags & MSG_PEEK) && peek_seq != llc->copied_seq) {
+ if (net_ratelimit())
+ printk(KERN_DEBUG "LLC(%s:%d): Application "
+ "bug, race in MSG_PEEK.\n",
+ current->comm, current->pid);
+ peek_seq = llc->copied_seq;
+ }
+ continue;
+ found_ok_skb:
+ /* Ok so how much can we use? */
+ used = skb->len - offset;
+ if (len < used)
+ used = len;
+
+ if (!(flags & MSG_TRUNC)) {
+ int rc = skb_copy_datagram_iovec(skb, offset,
+ msg->msg_iov, used);
+ if (rc) {
+ /* Exception. Bailout! */
+ if (!copied)
+ copied = -EFAULT;
+ break;
+ }
+ }
+
+ *seq += used;
+ copied += used;
+ len -= used;
+
+ if (used + offset < skb->len)
+ continue;
+
+ if (!(flags & MSG_PEEK)) {
+ sk_eat_skb(sk, skb);
+ *seq = 0;
+ }
+ } while (len > 0);
+
+ /*
+ * According to UNIX98, msg_name/msg_namelen are ignored
+ * on connected socket. -ANK
+ * But... af_llc still doesn't have separate sets of methods for
+ * SOCK_DGRAM and SOCK_STREAM :-( So we have to do this test, will
+ * eventually fix this tho :-) -acme
+ */
+ if (sk->sk_type == SOCK_DGRAM)
+ goto copy_uaddr;
out:
release_sock(sk);
- return rc ? : copied;
+ return copied;
+copy_uaddr:
+ if (uaddr != NULL && skb != NULL) {
+ memcpy(uaddr, llc_ui_skb_cb(skb), sizeof(*uaddr));
+ msg->msg_namelen = sizeof(*uaddr);
+ }
+ goto out;
}
/**
diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c
index e10ce5adb10..042b24a8ca4 100644
--- a/net/llc/llc_conn.c
+++ b/net/llc/llc_conn.c
@@ -120,8 +120,8 @@ int llc_conn_state_process(struct sock *sk, struct sk_buff *skb)
sk->sk_socket->state = SS_UNCONNECTED;
sk->sk_state = TCP_CLOSE;
if (!sock_flag(sk, SOCK_DEAD)) {
- sk->sk_state_change(sk);
sock_set_flag(sk, SOCK_DEAD);
+ sk->sk_state_change(sk);
}
}
kfree_skb(skb);
diff --git a/net/llc/llc_proc.c b/net/llc/llc_proc.c
index 36e8db3fa1a..bd531cb235a 100644
--- a/net/llc/llc_proc.c
+++ b/net/llc/llc_proc.c
@@ -134,7 +134,7 @@ static int llc_seq_socket_show(struct seq_file *seq, void *v)
llc_ui_format_mac(seq, llc->daddr.mac);
seq_printf(seq, "@%02X %8d %8d %2d %3d %4d\n", llc->daddr.lsap,
atomic_read(&sk->sk_wmem_alloc),
- atomic_read(&sk->sk_rmem_alloc),
+ atomic_read(&sk->sk_rmem_alloc) - llc->copied_seq,
sk->sk_state,
sk->sk_socket ? SOCK_INODE(sk->sk_socket)->i_uid : -1,
llc->link);
diff --git a/net/llc/llc_sap.c b/net/llc/llc_sap.c
index e6d538937f9..4029ceee9b9 100644
--- a/net/llc/llc_sap.c
+++ b/net/llc/llc_sap.c
@@ -49,9 +49,12 @@ struct sk_buff *llc_alloc_frame(struct sock *sk, struct net_device *dev)
void llc_save_primitive(struct sock *sk, struct sk_buff* skb, u8 prim)
{
- struct sockaddr_llc *addr = llc_ui_skb_cb(skb);
+ struct sockaddr_llc *addr;
+ if (skb->sk->sk_type == SOCK_STREAM) /* See UNIX98 */
+ return;
/* save primitive for use by the user. */
+ addr = llc_ui_skb_cb(skb);
addr->sllc_family = sk->sk_family;
addr->sllc_arphrd = skb->dev->type;
addr->sllc_test = prim == LLC_TEST_PRIM;