diff options
Diffstat (limited to 'net/llc/af_llc.c')
-rw-r--r-- | net/llc/af_llc.c | 180 |
1 files changed, 147 insertions, 33 deletions
diff --git a/net/llc/af_llc.c b/net/llc/af_llc.c index 7aa51eb79b13..59d02cbbeb9e 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; } /** |