[LLC]: fix llc_ui_recvmsg, making it behave like tcp_recvmsg
Arnaldo Carvalho de Melo [Thu, 22 Sep 2005 11:29:08 +0000 (08:29 -0300)]
In fact it is an exact copy of the parts that makes sense to LLC :-)

Signed-off-by: Arnaldo Carvalho de Melo <acme@mandriva.com>

include/net/llc_conn.h
net/llc/af_llc.c
net/llc/llc_conn.c
net/llc/llc_proc.c
net/llc/llc_sap.c

index e44f494..54852ff 100644 (file)
@@ -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;
index 7aa51eb..59d02cb 100644 (file)
@@ -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;
 }
 
 /**
index e10ce5a..042b24a 100644 (file)
@@ -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);
index 36e8db3..bd531cb 100644 (file)
@@ -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);
index e6d5389..4029cee 100644 (file)
@@ -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;