]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
readdir: supply dir_context.count as readdir buffer size hint
authorMiklos Szeredi <mszeredi@redhat.com>
Tue, 13 May 2025 15:10:08 +0000 (17:10 +0200)
committerChristian Brauner <brauner@kernel.org>
Thu, 15 May 2025 09:26:05 +0000 (11:26 +0200)
This is a preparation for large readdir buffers in fuse.

Simply setting the fuse buffer size to the userspace buffer size should
work, the record sizes are similar (fuse's is slightly larger than libc's,
so no overflow should ever happen).

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Signed-off-by: Jaco Kroon <jaco@uls.co.za>
Link: https://lore.kernel.org/20250513151012.1476536-1-mszeredi@redhat.com
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/exportfs/expfs.c
fs/overlayfs/readdir.c
fs/readdir.c
include/linux/fs.h

index 128dd092916bf74f7849456777a8a80dfa006468..48f0c505b50fef22375e7ec297b646c125f897dc 100644 (file)
@@ -284,6 +284,7 @@ static int get_name(const struct path *path, char *name, struct dentry *child)
        };
        struct getdents_callback buffer = {
                .ctx.actor = filldir_one,
+               .ctx.count = INT_MAX,
                .name = name,
        };
 
index 881ec5592da52dfb27a588496582e7084b2fbd3b..126c797751e9fbf0399eb255f91d31a54e6588dc 100644 (file)
@@ -351,6 +351,7 @@ static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list,
        struct path realpath;
        struct ovl_readdir_data rdd = {
                .ctx.actor = ovl_fill_merge,
+               .ctx.count = INT_MAX,
                .dentry = dentry,
                .list = list,
                .root = root,
@@ -571,6 +572,7 @@ static int ovl_dir_read_impure(const struct path *path,  struct list_head *list,
        struct ovl_cache_entry *p, *n;
        struct ovl_readdir_data rdd = {
                .ctx.actor = ovl_fill_plain,
+               .ctx.count = INT_MAX,
                .list = list,
                .root = root,
        };
@@ -672,6 +674,7 @@ static bool ovl_fill_real(struct dir_context *ctx, const char *name,
        struct ovl_readdir_translate *rdt =
                container_of(ctx, struct ovl_readdir_translate, ctx);
        struct dir_context *orig_ctx = rdt->orig_ctx;
+       bool res;
 
        if (rdt->parent_ino && strcmp(name, "..") == 0) {
                ino = rdt->parent_ino;
@@ -686,7 +689,10 @@ static bool ovl_fill_real(struct dir_context *ctx, const char *name,
                                          name, namelen, rdt->xinowarn);
        }
 
-       return orig_ctx->actor(orig_ctx, name, namelen, offset, ino, d_type);
+       res = orig_ctx->actor(orig_ctx, name, namelen, offset, ino, d_type);
+       ctx->count = orig_ctx->count;
+
+       return res;
 }
 
 static bool ovl_is_impure_dir(struct file *file)
@@ -713,6 +719,7 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
        const struct ovl_layer *lower_layer = ovl_layer_lower(dir);
        struct ovl_readdir_translate rdt = {
                .ctx.actor = ovl_fill_real,
+               .ctx.count = ctx->count,
                .orig_ctx = ctx,
                .xinobits = ovl_xino_bits(ofs),
                .xinowarn = ovl_xino_warn(ofs),
@@ -1073,6 +1080,7 @@ int ovl_check_d_type_supported(const struct path *realpath)
        int err;
        struct ovl_readdir_data rdd = {
                .ctx.actor = ovl_check_d_type,
+               .ctx.count = INT_MAX,
                .d_type_supported = false,
        };
 
@@ -1094,6 +1102,7 @@ static int ovl_workdir_cleanup_recurse(struct ovl_fs *ofs, const struct path *pa
        struct ovl_cache_entry *p;
        struct ovl_readdir_data rdd = {
                .ctx.actor = ovl_fill_plain,
+               .ctx.count = INT_MAX,
                .list = &list,
        };
        bool incompat = false;
@@ -1178,6 +1187,7 @@ int ovl_indexdir_cleanup(struct ovl_fs *ofs)
        struct ovl_cache_entry *p;
        struct ovl_readdir_data rdd = {
                .ctx.actor = ovl_fill_plain,
+               .ctx.count = INT_MAX,
                .list = &list,
        };
 
index 857d402bc53159670a2d6f5c768c530c5ca2dcb2..7764b863897888b9876821ee8c3ba4f6078bf315 100644 (file)
@@ -222,6 +222,7 @@ SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
        CLASS(fd_pos, f)(fd);
        struct readdir_callback buf = {
                .ctx.actor = fillonedir,
+               .ctx.count = 1, /* Hint to fs: just one entry. */
                .dirent = dirent
        };
 
@@ -252,7 +253,6 @@ struct getdents_callback {
        struct dir_context ctx;
        struct linux_dirent __user * current_dir;
        int prev_reclen;
-       int count;
        int error;
 };
 
@@ -275,7 +275,7 @@ static bool filldir(struct dir_context *ctx, const char *name, int namlen,
        if (unlikely(buf->error))
                return false;
        buf->error = -EINVAL;   /* only used if we fail.. */
-       if (reclen > buf->count)
+       if (reclen > ctx->count)
                return false;
        d_ino = ino;
        if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
@@ -300,7 +300,7 @@ static bool filldir(struct dir_context *ctx, const char *name, int namlen,
 
        buf->current_dir = (void __user *)dirent + reclen;
        buf->prev_reclen = reclen;
-       buf->count -= reclen;
+       ctx->count -= reclen;
        return true;
 efault_end:
        user_write_access_end();
@@ -315,7 +315,7 @@ SYSCALL_DEFINE3(getdents, unsigned int, fd,
        CLASS(fd_pos, f)(fd);
        struct getdents_callback buf = {
                .ctx.actor = filldir,
-               .count = count,
+               .ctx.count = count,
                .current_dir = dirent
        };
        int error;
@@ -333,7 +333,7 @@ SYSCALL_DEFINE3(getdents, unsigned int, fd,
                if (put_user(buf.ctx.pos, &lastdirent->d_off))
                        error = -EFAULT;
                else
-                       error = count - buf.count;
+                       error = count - buf.ctx.count;
        }
        return error;
 }
@@ -342,7 +342,6 @@ struct getdents_callback64 {
        struct dir_context ctx;
        struct linux_dirent64 __user * current_dir;
        int prev_reclen;
-       int count;
        int error;
 };
 
@@ -364,7 +363,7 @@ static bool filldir64(struct dir_context *ctx, const char *name, int namlen,
        if (unlikely(buf->error))
                return false;
        buf->error = -EINVAL;   /* only used if we fail.. */
-       if (reclen > buf->count)
+       if (reclen > ctx->count)
                return false;
        prev_reclen = buf->prev_reclen;
        if (!(flags & FILLDIR_FLAG_NOINTR) && prev_reclen && signal_pending(current))
@@ -384,7 +383,7 @@ static bool filldir64(struct dir_context *ctx, const char *name, int namlen,
 
        buf->prev_reclen = reclen;
        buf->current_dir = (void __user *)dirent + reclen;
-       buf->count -= reclen;
+       ctx->count -= reclen;
        return true;
 
 efault_end:
@@ -400,7 +399,7 @@ SYSCALL_DEFINE3(getdents64, unsigned int, fd,
        CLASS(fd_pos, f)(fd);
        struct getdents_callback64 buf = {
                .ctx.actor = filldir64,
-               .count = count,
+               .ctx.count = count,
                .current_dir = dirent
        };
        int error;
@@ -419,7 +418,7 @@ SYSCALL_DEFINE3(getdents64, unsigned int, fd,
                if (put_user(d_off, &lastdirent->d_off))
                        error = -EFAULT;
                else
-                       error = count - buf.count;
+                       error = count - buf.ctx.count;
        }
        return error;
 }
@@ -483,6 +482,7 @@ COMPAT_SYSCALL_DEFINE3(old_readdir, unsigned int, fd,
        CLASS(fd_pos, f)(fd);
        struct compat_readdir_callback buf = {
                .ctx.actor = compat_fillonedir,
+               .ctx.count = 1, /* Hint to fs: just one entry. */
                .dirent = dirent
        };
 
@@ -507,7 +507,6 @@ struct compat_getdents_callback {
        struct dir_context ctx;
        struct compat_linux_dirent __user *current_dir;
        int prev_reclen;
-       int count;
        int error;
 };
 
@@ -530,7 +529,7 @@ static bool compat_filldir(struct dir_context *ctx, const char *name, int namlen
        if (unlikely(buf->error))
                return false;
        buf->error = -EINVAL;   /* only used if we fail.. */
-       if (reclen > buf->count)
+       if (reclen > ctx->count)
                return false;
        d_ino = ino;
        if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) {
@@ -554,7 +553,7 @@ static bool compat_filldir(struct dir_context *ctx, const char *name, int namlen
 
        buf->prev_reclen = reclen;
        buf->current_dir = (void __user *)dirent + reclen;
-       buf->count -= reclen;
+       ctx->count -= reclen;
        return true;
 efault_end:
        user_write_access_end();
@@ -569,8 +568,8 @@ COMPAT_SYSCALL_DEFINE3(getdents, unsigned int, fd,
        CLASS(fd_pos, f)(fd);
        struct compat_getdents_callback buf = {
                .ctx.actor = compat_filldir,
+               .ctx.count = count,
                .current_dir = dirent,
-               .count = count
        };
        int error;
 
@@ -587,7 +586,7 @@ COMPAT_SYSCALL_DEFINE3(getdents, unsigned int, fd,
                if (put_user(buf.ctx.pos, &lastdirent->d_off))
                        error = -EFAULT;
                else
-                       error = count - buf.count;
+                       error = count - buf.ctx.count;
        }
        return error;
 }
index c8b5af17060e463d1e14914e78ee2304e6e5e6b6..ddd54a6649161d104d21f876ee67a47ea00b6400 100644 (file)
@@ -2071,6 +2071,13 @@ typedef bool (*filldir_t)(struct dir_context *, const char *, int, loff_t, u64,
 struct dir_context {
        filldir_t actor;
        loff_t pos;
+       /*
+        * Filesystems MUST NOT MODIFY count, but may use as a hint:
+        * 0        unknown
+        * > 0      space in buffer (assume at least one entry)
+        * INT_MAX  unlimited
+        */
+       int count;
 };
 
 /* If OR-ed with d_type, pending signals are not checked */