*/
 int skb_copy_ubufs(struct sk_buff *skb, gfp_t gfp_mask)
 {
-       int i;
+       struct ubuf_info *uarg = skb_shinfo(skb)->destructor_arg;
        int num_frags = skb_shinfo(skb)->nr_frags;
        struct page *page, *head = NULL;
-       struct ubuf_info *uarg = skb_shinfo(skb)->destructor_arg;
+       int i, new_frags;
+       u32 d_off;
 
-       for (i = 0; i < num_frags; i++) {
-               skb_frag_t *f = &skb_shinfo(skb)->frags[i];
-               u32 p_off, p_len, copied;
-               struct page *p;
-               u8 *vaddr;
+       if (!num_frags)
+               return 0;
+
+       if (skb_shared(skb) || skb_unclone(skb, gfp_mask))
+               return -EINVAL;
 
+       new_frags = (__skb_pagelen(skb) + PAGE_SIZE - 1) >> PAGE_SHIFT;
+       for (i = 0; i < new_frags; i++) {
                page = alloc_page(gfp_mask);
                if (!page) {
                        while (head) {
                        }
                        return -ENOMEM;
                }
+               set_page_private(page, (unsigned long)head);
+               head = page;
+       }
+
+       page = head;
+       d_off = 0;
+       for (i = 0; i < num_frags; i++) {
+               skb_frag_t *f = &skb_shinfo(skb)->frags[i];
+               u32 p_off, p_len, copied;
+               struct page *p;
+               u8 *vaddr;
 
                skb_frag_foreach_page(f, f->page_offset, skb_frag_size(f),
                                      p, p_off, p_len, copied) {
+                       u32 copy, done = 0;
                        vaddr = kmap_atomic(p);
-                       memcpy(page_address(page) + copied, vaddr + p_off,
-                              p_len);
+
+                       while (done < p_len) {
+                               if (d_off == PAGE_SIZE) {
+                                       d_off = 0;
+                                       page = (struct page *)page_private(page);
+                               }
+                               copy = min_t(u32, PAGE_SIZE - d_off, p_len - done);
+                               memcpy(page_address(page) + d_off,
+                                      vaddr + p_off + done, copy);
+                               done += copy;
+                               d_off += copy;
+                       }
                        kunmap_atomic(vaddr);
                }
-
-               set_page_private(page, (unsigned long)head);
-               head = page;
        }
 
        /* skb frags release userspace buffers */
        uarg->callback(uarg, false);
 
        /* skb frags point to kernel buffers */
-       for (i = num_frags - 1; i >= 0; i--) {
-               __skb_fill_page_desc(skb, i, head, 0,
-                                    skb_shinfo(skb)->frags[i].size);
+       for (i = 0; i < new_frags - 1; i++) {
+               __skb_fill_page_desc(skb, i, head, 0, PAGE_SIZE);
                head = (struct page *)page_private(head);
        }
+       __skb_fill_page_desc(skb, new_frags - 1, head, 0, d_off);
+       skb_shinfo(skb)->nr_frags = new_frags;
 
        skb_shinfo(skb)->tx_flags &= ~SKBTX_DEV_ZEROCOPY;
        return 0;