kcm: Sendpage support
authorTom Herbert <tom@herbertland.com>
Mon, 7 Mar 2016 22:11:09 +0000 (14:11 -0800)
committerDavid S. Miller <davem@davemloft.net>
Wed, 9 Mar 2016 21:36:15 +0000 (16:36 -0500)
Implement kcm_sendpage. Set in sendpage to kcm_sendpage in both
dgram and seqpacket ops.

Signed-off-by: Tom Herbert <tom@herbertland.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/kcm/kcmsock.c

index 982ea5f..9ac2499 100644 (file)
@@ -990,6 +990,149 @@ static void kcm_push(struct kcm_sock *kcm)
                kcm_write_msgs(kcm);
 }
 
+static ssize_t kcm_sendpage(struct socket *sock, struct page *page,
+                           int offset, size_t size, int flags)
+
+{
+       struct sock *sk = sock->sk;
+       struct kcm_sock *kcm = kcm_sk(sk);
+       struct sk_buff *skb = NULL, *head = NULL;
+       long timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
+       bool eor;
+       int err = 0;
+       int i;
+
+       if (flags & MSG_SENDPAGE_NOTLAST)
+               flags |= MSG_MORE;
+
+       /* No MSG_EOR from splice, only look at MSG_MORE */
+       eor = !(flags & MSG_MORE);
+
+       lock_sock(sk);
+
+       sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);
+
+       err = -EPIPE;
+       if (sk->sk_err)
+               goto out_error;
+
+       if (kcm->seq_skb) {
+               /* Previously opened message */
+               head = kcm->seq_skb;
+               skb = kcm_tx_msg(head)->last_skb;
+               i = skb_shinfo(skb)->nr_frags;
+
+               if (skb_can_coalesce(skb, i, page, offset)) {
+                       skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], size);
+                       skb_shinfo(skb)->tx_flags |= SKBTX_SHARED_FRAG;
+                       goto coalesced;
+               }
+
+               if (i >= MAX_SKB_FRAGS) {
+                       struct sk_buff *tskb;
+
+                       tskb = alloc_skb(0, sk->sk_allocation);
+                       while (!tskb) {
+                               kcm_push(kcm);
+                               err = sk_stream_wait_memory(sk, &timeo);
+                               if (err)
+                                       goto out_error;
+                       }
+
+                       if (head == skb)
+                               skb_shinfo(head)->frag_list = tskb;
+                       else
+                               skb->next = tskb;
+
+                       skb = tskb;
+                       skb->ip_summed = CHECKSUM_UNNECESSARY;
+                       i = 0;
+               }
+       } else {
+               /* Call the sk_stream functions to manage the sndbuf mem. */
+               if (!sk_stream_memory_free(sk)) {
+                       kcm_push(kcm);
+                       set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
+                       err = sk_stream_wait_memory(sk, &timeo);
+                       if (err)
+                               goto out_error;
+               }
+
+               head = alloc_skb(0, sk->sk_allocation);
+               while (!head) {
+                       kcm_push(kcm);
+                       err = sk_stream_wait_memory(sk, &timeo);
+                       if (err)
+                               goto out_error;
+               }
+
+               skb = head;
+               i = 0;
+       }
+
+       get_page(page);
+       skb_fill_page_desc(skb, i, page, offset, size);
+       skb_shinfo(skb)->tx_flags |= SKBTX_SHARED_FRAG;
+
+coalesced:
+       skb->len += size;
+       skb->data_len += size;
+       skb->truesize += size;
+       sk->sk_wmem_queued += size;
+       sk_mem_charge(sk, size);
+
+       if (head != skb) {
+               head->len += size;
+               head->data_len += size;
+               head->truesize += size;
+       }
+
+       if (eor) {
+               bool not_busy = skb_queue_empty(&sk->sk_write_queue);
+
+               /* Message complete, queue it on send buffer */
+               __skb_queue_tail(&sk->sk_write_queue, head);
+               kcm->seq_skb = NULL;
+               KCM_STATS_INCR(kcm->stats.tx_msgs);
+
+               if (flags & MSG_BATCH) {
+                       kcm->tx_wait_more = true;
+               } else if (kcm->tx_wait_more || not_busy) {
+                       err = kcm_write_msgs(kcm);
+                       if (err < 0) {
+                               /* We got a hard error in write_msgs but have
+                                * already queued this message. Report an error
+                                * in the socket, but don't affect return value
+                                * from sendmsg
+                                */
+                               pr_warn("KCM: Hard failure on kcm_write_msgs\n");
+                               report_csk_error(&kcm->sk, -err);
+                       }
+               }
+       } else {
+               /* Message not complete, save state */
+               kcm->seq_skb = head;
+               kcm_tx_msg(head)->last_skb = skb;
+       }
+
+       KCM_STATS_ADD(kcm->stats.tx_bytes, size);
+
+       release_sock(sk);
+       return size;
+
+out_error:
+       kcm_push(kcm);
+
+       err = sk_stream_error(sk, flags, err);
+
+       /* make sure we wake any epoll edge trigger waiter */
+       if (unlikely(skb_queue_len(&sk->sk_write_queue) == 0 && err == -EAGAIN))
+               sk->sk_write_space(sk);
+
+       release_sock(sk);
+       return err;
+}
+
 static int kcm_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
 {
        struct sock *sk = sock->sk;
@@ -1995,7 +2138,7 @@ static const struct proto_ops kcm_dgram_ops = {
        .sendmsg =      kcm_sendmsg,
        .recvmsg =      kcm_recvmsg,
        .mmap =         sock_no_mmap,
-       .sendpage =     sock_no_sendpage,
+       .sendpage =     kcm_sendpage,
 };
 
 static const struct proto_ops kcm_seqpacket_ops = {
@@ -2016,7 +2159,7 @@ static const struct proto_ops kcm_seqpacket_ops = {
        .sendmsg =      kcm_sendmsg,
        .recvmsg =      kcm_recvmsg,
        .mmap =         sock_no_mmap,
-       .sendpage =     sock_no_sendpage,
+       .sendpage =     kcm_sendpage,
        .splice_read =  kcm_splice_read,
 };