#include <linux/posix_acl.h>
#include <linux/pagemap.h>
#include <linux/highmem.h>
+#include <linux/vmalloc.h>
static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)
{
return 0;
}
+static struct page **fuse_readdir_alloc_buf(struct fuse_args_pages *ap, size_t *bufsize)
+{
+ unsigned int i, nr_alloc, nr_pages = DIV_ROUND_UP(*bufsize, PAGE_SIZE);
+ struct page **pages = kcalloc(nr_pages, sizeof(*pages), GFP_KERNEL);
+
+ if (!pages)
+ return NULL;
+
+ nr_alloc = alloc_pages_bulk(GFP_KERNEL, nr_pages, pages);
+ if (!nr_alloc)
+ goto free_array;
+
+ if (nr_alloc < nr_pages) {
+ nr_pages = nr_alloc;
+ *bufsize = (size_t) nr_pages << PAGE_SHIFT;
+ }
+
+ ap->folios = fuse_folios_alloc(nr_pages, GFP_KERNEL, &ap->descs);
+ if (!ap->folios)
+ goto release_pages;
+
+ for (i = 0; i < nr_pages; i++) {
+ ap->folios[i] = page_folio(pages[i]);
+ ap->descs[i].length = min_t(size_t, *bufsize - (size_t)i * PAGE_SIZE, PAGE_SIZE);
+ }
+ ap->num_folios = nr_pages;
+ ap->args.out_pages = true;
+
+ return pages;
+
+release_pages:
+ release_pages(pages, nr_pages);
+free_array:
+ kfree(pages);
+ return NULL;
+}
+
static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx)
{
int plus;
struct fuse_mount *fm = get_fuse_mount(inode);
struct fuse_conn *fc = fm->fc;
struct fuse_io_args ia = {};
- struct fuse_args *args = &ia.ap.args;
+ struct fuse_args_pages *ap = &ia.ap;
void *buf;
size_t bufsize = clamp((unsigned int) ctx->count, PAGE_SIZE, fc->max_pages << PAGE_SHIFT);
u64 attr_version = 0, evict_ctr = 0;
bool locked;
+ struct page **pages = fuse_readdir_alloc_buf(ap, &bufsize);
- buf = kvmalloc(bufsize, GFP_KERNEL);
- if (!buf)
+ if (!pages)
return -ENOMEM;
- args->out_args[0].value = buf;
-
plus = fuse_use_readdirplus(inode, ctx);
if (plus) {
attr_version = fuse_get_attr_version(fm->fc);
fuse_read_args_fill(&ia, file, ctx->pos, bufsize, FUSE_READDIR);
}
locked = fuse_lock_inode(inode);
- res = fuse_simple_request(fm, args);
+ res = fuse_simple_request(fm, &ap->args);
fuse_unlock_inode(inode, locked);
- if (res >= 0) {
- if (!res) {
- struct fuse_file *ff = file->private_data;
-
- if (ff->open_flags & FOPEN_CACHE_DIR)
- fuse_readdir_cache_end(file, ctx->pos);
- } else if (plus) {
- res = parse_dirplusfile(buf, res, file, ctx, attr_version,
- evict_ctr);
- } else {
+ if (res < 0)
+ goto out;
+
+ if (!res) {
+ struct fuse_file *ff = file->private_data;
+
+ if (ff->open_flags & FOPEN_CACHE_DIR)
+ fuse_readdir_cache_end(file, ctx->pos);
+ goto out;
+ }
+
+ buf = vm_map_ram(pages, ap->num_folios, -1);
+ if (!buf) {
+ res = -ENOMEM;
+ } else {
+ if (plus)
+ res = parse_dirplusfile(buf, res, file, ctx, attr_version, evict_ctr);
+ else
res = parse_dirfile(buf, res, file, ctx);
- }
+
+ vm_unmap_ram(buf, ap->num_folios);
}
+out:
+ kfree(ap->folios);
+ release_pages(pages, ap->num_folios);
+ kfree(pages);
- kvfree(buf);
fuse_invalidate_atime(inode);
+
return res;
}