blob: e29aad1da0b3c401d74fb81e2e8179ad09197701 [file] [edit]
// SPDX-License-Identifier: GPL-2.0-or-later
/* Iterator helpers.
*
* Copyright (C) 2022 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*/
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/uio.h>
#include <linux/scatterlist.h>
#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;
unsigned int slot = 0;
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 %zx, %zx\n",
extracted, max_len);
ret = -EIO;
goto out;
}
if (WARN(got > max_len,
"%s: extract_pages overrun %zx > %zx 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(slot >= bq->max_slots);
bvec_set_page(&bq->bv[slot], *pages++, len, offset);
slot++;
got -= len;
offset = 0;
} while (got > 0);
bvecq_filled_to(bq, slot);
} 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);