1 /* 2 * linux/net/sunrpc/socklib.c 3 * 4 * Common socket helper routines for RPC client and server 5 * 6 * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> 7 */ 8 9 #include <linux/compiler.h> 10 #include <linux/netdevice.h> 11 #include <linux/gfp.h> 12 #include <linux/skbuff.h> 13 #include <linux/types.h> 14 #include <linux/pagemap.h> 15 #include <linux/udp.h> 16 #include <linux/sunrpc/xdr.h> 17 #include <linux/export.h> 18 19 20 /** 21 * xdr_skb_read_bits - copy some data bits from skb to internal buffer 22 * @desc: sk_buff copy helper 23 * @to: copy destination 24 * @len: number of bytes to copy 25 * 26 * Possibly called several times to iterate over an sk_buff and copy 27 * data out of it. 28 */ 29 static size_t 30 xdr_skb_read_bits(struct xdr_skb_reader *desc, void *to, size_t len) 31 { 32 if (len > desc->count) 33 len = desc->count; 34 if (unlikely(skb_copy_bits(desc->skb, desc->offset, to, len))) 35 return 0; 36 desc->count -= len; 37 desc->offset += len; 38 return len; 39 } 40 41 /** 42 * xdr_skb_read_and_csum_bits - copy and checksum from skb to buffer 43 * @desc: sk_buff copy helper 44 * @to: copy destination 45 * @len: number of bytes to copy 46 * 47 * Same as skb_read_bits, but calculate a checksum at the same time. 48 */ 49 static size_t xdr_skb_read_and_csum_bits(struct xdr_skb_reader *desc, void *to, size_t len) 50 { 51 unsigned int pos; 52 __wsum csum2; 53 54 if (len > desc->count) 55 len = desc->count; 56 pos = desc->offset; 57 csum2 = skb_copy_and_csum_bits(desc->skb, pos, to, len, 0); 58 desc->csum = csum_block_add(desc->csum, csum2, pos); 59 desc->count -= len; 60 desc->offset += len; 61 return len; 62 } 63 64 /** 65 * xdr_partial_copy_from_skb - copy data out of an skb 66 * @xdr: target XDR buffer 67 * @base: starting offset 68 * @desc: sk_buff copy helper 69 * @copy_actor: virtual method for copying data 70 * 71 */ 72 static ssize_t 73 xdr_partial_copy_from_skb(struct xdr_buf *xdr, unsigned int base, struct xdr_skb_reader *desc, xdr_skb_read_actor copy_actor) 74 { 75 struct page **ppage = xdr->pages; 76 unsigned int len, pglen = xdr->page_len; 77 ssize_t copied = 0; 78 size_t ret; 79 80 len = xdr->head[0].iov_len; 81 if (base < len) { 82 len -= base; 83 ret = copy_actor(desc, (char *)xdr->head[0].iov_base + base, len); 84 copied += ret; 85 if (ret != len || !desc->count) 86 goto out; 87 base = 0; 88 } else 89 base -= len; 90 91 if (unlikely(pglen == 0)) 92 goto copy_tail; 93 if (unlikely(base >= pglen)) { 94 base -= pglen; 95 goto copy_tail; 96 } 97 if (base || xdr->page_base) { 98 pglen -= base; 99 base += xdr->page_base; 100 ppage += base >> PAGE_SHIFT; 101 base &= ~PAGE_MASK; 102 } 103 do { 104 char *kaddr; 105 106 /* ACL likes to be lazy in allocating pages - ACLs 107 * are small by default but can get huge. */ 108 if ((xdr->flags & XDRBUF_SPARSE_PAGES) && *ppage == NULL) { 109 *ppage = alloc_page(GFP_NOWAIT | __GFP_NOWARN); 110 if (unlikely(*ppage == NULL)) { 111 if (copied == 0) 112 copied = -ENOMEM; 113 goto out; 114 } 115 } 116 117 len = PAGE_SIZE; 118 kaddr = kmap_atomic(*ppage); 119 if (base) { 120 len -= base; 121 if (pglen < len) 122 len = pglen; 123 ret = copy_actor(desc, kaddr + base, len); 124 base = 0; 125 } else { 126 if (pglen < len) 127 len = pglen; 128 ret = copy_actor(desc, kaddr, len); 129 } 130 flush_dcache_page(*ppage); 131 kunmap_atomic(kaddr); 132 copied += ret; 133 if (ret != len || !desc->count) 134 goto out; 135 ppage++; 136 } while ((pglen -= len) != 0); 137 copy_tail: 138 len = xdr->tail[0].iov_len; 139 if (base < len) 140 copied += copy_actor(desc, (char *)xdr->tail[0].iov_base + base, len - base); 141 out: 142 return copied; 143 } 144 145 /** 146 * csum_partial_copy_to_xdr - checksum and copy data 147 * @xdr: target XDR buffer 148 * @skb: source skb 149 * 150 * We have set things up such that we perform the checksum of the UDP 151 * packet in parallel with the copies into the RPC client iovec. -DaveM 152 */ 153 int csum_partial_copy_to_xdr(struct xdr_buf *xdr, struct sk_buff *skb) 154 { 155 struct xdr_skb_reader desc; 156 157 desc.skb = skb; 158 desc.offset = 0; 159 desc.count = skb->len - desc.offset; 160 161 if (skb_csum_unnecessary(skb)) 162 goto no_checksum; 163 164 desc.csum = csum_partial(skb->data, desc.offset, skb->csum); 165 if (xdr_partial_copy_from_skb(xdr, 0, &desc, xdr_skb_read_and_csum_bits) < 0) 166 return -1; 167 if (desc.offset != skb->len) { 168 __wsum csum2; 169 csum2 = skb_checksum(skb, desc.offset, skb->len - desc.offset, 0); 170 desc.csum = csum_block_add(desc.csum, csum2, desc.offset); 171 } 172 if (desc.count) 173 return -1; 174 if (csum_fold(desc.csum)) 175 return -1; 176 if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE) && 177 !skb->csum_complete_sw) 178 netdev_rx_csum_fault(skb->dev, skb); 179 return 0; 180 no_checksum: 181 if (xdr_partial_copy_from_skb(xdr, 0, &desc, xdr_skb_read_bits) < 0) 182 return -1; 183 if (desc.count) 184 return -1; 185 return 0; 186 } 187 EXPORT_SYMBOL_GPL(csum_partial_copy_to_xdr); 188