diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/core/datagram.c | 81 |
1 files changed, 53 insertions, 28 deletions
diff --git a/net/core/datagram.c b/net/core/datagram.c index f8d322e1ea92..b8ce6bf81188 100644 --- a/net/core/datagram.c +++ b/net/core/datagram.c | |||
@@ -247,49 +247,74 @@ EXPORT_SYMBOL(skb_kill_datagram); | |||
247 | int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset, | 247 | int skb_copy_datagram_iovec(const struct sk_buff *skb, int offset, |
248 | struct iovec *to, int len) | 248 | struct iovec *to, int len) |
249 | { | 249 | { |
250 | int i, err, fraglen, end = 0; | 250 | int start = skb_headlen(skb); |
251 | struct sk_buff *next = skb_shinfo(skb)->frag_list; | 251 | int i, copy = start - offset; |
252 | 252 | ||
253 | if (!len) | 253 | /* Copy header. */ |
254 | return 0; | 254 | if (copy > 0) { |
255 | if (copy > len) | ||
256 | copy = len; | ||
257 | if (memcpy_toiovec(to, skb->data + offset, copy)) | ||
258 | goto fault; | ||
259 | if ((len -= copy) == 0) | ||
260 | return 0; | ||
261 | offset += copy; | ||
262 | } | ||
255 | 263 | ||
256 | next_skb: | 264 | /* Copy paged appendix. Hmm... why does this look so complicated? */ |
257 | fraglen = skb_headlen(skb); | 265 | for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { |
258 | i = -1; | 266 | int end; |
259 | 267 | ||
260 | while (1) { | 268 | BUG_TRAP(start <= offset + len); |
261 | int start = end; | ||
262 | 269 | ||
263 | if ((end += fraglen) > offset) { | 270 | end = start + skb_shinfo(skb)->frags[i].size; |
264 | int copy = end - offset, o = offset - start; | 271 | if ((copy = end - offset) > 0) { |
272 | int err; | ||
273 | u8 *vaddr; | ||
274 | skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; | ||
275 | struct page *page = frag->page; | ||
265 | 276 | ||
266 | if (copy > len) | 277 | if (copy > len) |
267 | copy = len; | 278 | copy = len; |
268 | if (i == -1) | 279 | vaddr = kmap(page); |
269 | err = memcpy_toiovec(to, skb->data + o, copy); | 280 | err = memcpy_toiovec(to, vaddr + frag->page_offset + |
270 | else { | 281 | offset - start, copy); |
271 | skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; | 282 | kunmap(page); |
272 | struct page *page = frag->page; | ||
273 | void *p = kmap(page) + frag->page_offset + o; | ||
274 | err = memcpy_toiovec(to, p, copy); | ||
275 | kunmap(page); | ||
276 | } | ||
277 | if (err) | 283 | if (err) |
278 | goto fault; | 284 | goto fault; |
279 | if (!(len -= copy)) | 285 | if (!(len -= copy)) |
280 | return 0; | 286 | return 0; |
281 | offset += copy; | 287 | offset += copy; |
282 | } | 288 | } |
283 | if (++i >= skb_shinfo(skb)->nr_frags) | 289 | start = end; |
284 | break; | ||
285 | fraglen = skb_shinfo(skb)->frags[i].size; | ||
286 | } | 290 | } |
287 | if (next) { | 291 | |
288 | skb = next; | 292 | if (skb_shinfo(skb)->frag_list) { |
289 | BUG_ON(skb_shinfo(skb)->frag_list); | 293 | struct sk_buff *list = skb_shinfo(skb)->frag_list; |
290 | next = skb->next; | 294 | |
291 | goto next_skb; | 295 | for (; list; list = list->next) { |
296 | int end; | ||
297 | |||
298 | BUG_TRAP(start <= offset + len); | ||
299 | |||
300 | end = start + list->len; | ||
301 | if ((copy = end - offset) > 0) { | ||
302 | if (copy > len) | ||
303 | copy = len; | ||
304 | if (skb_copy_datagram_iovec(list, | ||
305 | offset - start, | ||
306 | to, copy)) | ||
307 | goto fault; | ||
308 | if ((len -= copy) == 0) | ||
309 | return 0; | ||
310 | offset += copy; | ||
311 | } | ||
312 | start = end; | ||
313 | } | ||
292 | } | 314 | } |
315 | if (!len) | ||
316 | return 0; | ||
317 | |||
293 | fault: | 318 | fault: |
294 | return -EFAULT; | 319 | return -EFAULT; |
295 | } | 320 | } |