Add a function to extract a slice of data from an iterator of any type into
a bvec queue chain.

Signed-off-by: David Howells <[email protected]>
cc: Paulo Alcantara <[email protected]>
cc: Matthew Wilcox <[email protected]>
cc: Christoph Hellwig <[email protected]>
cc: Steve French <[email protected]>
cc: [email protected]
cc: [email protected]
cc: [email protected]
---
 fs/netfs/iterator.c   | 125 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/netfs.h |   3 +
 2 files changed, 128 insertions(+)

diff --git a/fs/netfs/iterator.c b/fs/netfs/iterator.c
index b375567e0520..d2c3055a488c 100644
--- a/fs/netfs/iterator.c
+++ b/fs/netfs/iterator.c
@@ -13,6 +13,131 @@
 #include <linux/netfs.h>
 #include "internal.h"
 
+/**
+ * netfs_extract_iter - Extract virtually contiguous pages from an iterator 
into a bvecq
+ * @orig: The original iterator
+ * @max_len: Maximum number of bytes to extract
+ * @max_pages: Maximum number of pages to extract
+ * @fpos: Starting file position to label the bvecq with
+ * @_bvecq_head: Where to cache the bvec queue
+ * @extraction_flags: Flags to qualify the request
+ *
+ * Extract virtually contiguous page fragments from the source iterator up to
+ * the given maxima and build bvec queue that refers to all of those bits.
+ * This allows the original iterator to disposed of.
+ *
+ * @extraction_flags can have ITER_ALLOW_P2PDMA set to request peer-to-peer 
DMA be
+ * allowed on the pages extracted.
+ *
+ * On success, the amount of data in the bvec is returned, the original
+ * iterator will have been advanced by the amount extracted.
+ *
+ * The bvecq segments are marked with indications on how to get clean up the
+ * extracted fragments.
+ */
+ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t 
max_pages,
+                          unsigned long long fpos, struct bvecq **_bvecq_head,
+                          iov_iter_extraction_t extraction_flags)
+{
+       struct bvecq *bq_tail = NULL;
+       ssize_t ret = 0;
+       size_t extracted = 0;
+
+       _enter("{%u,%zx},%zx", orig->iter_type, orig->count, max_len);
+
+       if (max_len > orig->count)
+               max_len = orig->count;
+       if (WARN_ON_ONCE(!max_len || !max_pages))
+               return 0;
+
+       max_pages = iov_iter_npages(orig, max_pages);
+       if (!max_pages)
+               return 0;
+
+       do {
+               struct bvecq *bq;
+
+               bq = bvecq_alloc_one(max_pages, GFP_NOFS);
+               if (!bq) {
+                       ret = -ENOMEM;
+                       break;
+               }
+               if (user_backed_iter(orig))
+                       bq->mem_type = iov_iter_extract_will_pin(orig) ?
+                               BVECQ_MEM_GUP : BVECQ_MEM_PAGECACHE;
+               bq->prev        = bq_tail;
+               bq->fpos        = fpos + extracted;
+
+               if (bq_tail)
+                       bq_tail->next = bq;
+               else
+                       *_bvecq_head = bq;
+               bq_tail = bq;
+
+               if (max_len == 0)
+                       break;
+
+               struct bio_vec *bv = bq->bv;
+               do {
+                       struct page **pages;
+                       ssize_t got;
+                       size_t offset;
+                       size_t space = bq->max_slots - bq->nr_slots;
+                       size_t bv_size = array_size(bq->max_slots, sizeof(*bv));
+                       size_t pg_size = array_size(space, sizeof(*pages));
+
+                       /* Put the page list at the end of the bvec list
+                        * storage.  bvec elements are larger than page
+                        * pointers, so as long as we work 0->last, we should
+                        * be fine.
+                        */
+                       pages = (void *)bv + bv_size - pg_size;
+
+                       got = iov_iter_extract_pages(orig, &pages, max_len,
+                                                    space, extraction_flags, 
&offset);
+                       if (got < 0) {
+                               ret = got;
+                               goto out;
+                       }
+
+                       if (got == 0) {
+                               pr_err("extract_pages gave nothing from %zu, 
%zu\n",
+                                      extracted, max_len);
+                               ret = -EIO;
+                               goto out;
+                       }
+
+                       if (WARN(got > max_len,
+                                "%s: extract_pages overrun %zd > %zu bytes\n",
+                                __func__, got, max_len)) {
+                               ret = -EIO;
+                               break;
+                       }
+
+                       extracted += got;
+                       max_len -= got;
+
+                       do {
+                               size_t len = umin(got, PAGE_SIZE - offset);
+
+                               BUG_ON(bq->nr_slots >= bq->max_slots);
+
+                               bvec_set_page(&bq->bv[bq->nr_slots],
+                                             *pages++, len, offset);
+                               bq->nr_slots++;
+                               got -= len;
+                               offset = 0;
+                       } while (got > 0);
+               } while (max_len > 0 && !bvecq_is_full(bq));
+
+               max_pages -= bq->nr_slots;
+       } while (max_len > 0 && max_pages > 0);
+
+out:
+       return extracted ?: ret;
+}
+EXPORT_SYMBOL_GPL(netfs_extract_iter);
+
 /**
  * netfs_extract_user_iter - Extract the pages from a user iterator into a bvec
  * @orig: The original iterator
diff --git a/include/linux/netfs.h b/include/linux/netfs.h
index 12e5c51c11c8..40f45ecf1db8 100644
--- a/include/linux/netfs.h
+++ b/include/linux/netfs.h
@@ -460,6 +460,9 @@ void netfs_get_subrequest(struct netfs_io_subrequest 
*subreq,
                          enum netfs_sreq_ref_trace what);
 void netfs_put_subrequest(struct netfs_io_subrequest *subreq,
                          enum netfs_sreq_ref_trace what);
+ssize_t netfs_extract_iter(struct iov_iter *orig, size_t max_len, size_t 
max_pages,
+                          unsigned long long fpos, struct bvecq **_bvecq_head,
+                          iov_iter_extraction_t extraction_flags);
 ssize_t netfs_extract_user_iter(struct iov_iter *orig, size_t orig_len,
                                struct iov_iter *new,
                                iov_iter_extraction_t extraction_flags);


Reply via email to