Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

readdir: supply dir_context.count as readdir buffer size hint

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>

authored by

Miklos Szeredi and committed by
Christian Brauner
e0410e95 e7b9cea7

+33 -16
+1
fs/exportfs/expfs.c
··· 284 284 }; 285 285 struct getdents_callback buffer = { 286 286 .ctx.actor = filldir_one, 287 + .ctx.count = INT_MAX, 287 288 .name = name, 288 289 }; 289 290
+11 -1
fs/overlayfs/readdir.c
··· 351 351 struct path realpath; 352 352 struct ovl_readdir_data rdd = { 353 353 .ctx.actor = ovl_fill_merge, 354 + .ctx.count = INT_MAX, 354 355 .dentry = dentry, 355 356 .list = list, 356 357 .root = root, ··· 572 571 struct ovl_cache_entry *p, *n; 573 572 struct ovl_readdir_data rdd = { 574 573 .ctx.actor = ovl_fill_plain, 574 + .ctx.count = INT_MAX, 575 575 .list = list, 576 576 .root = root, 577 577 }; ··· 674 672 struct ovl_readdir_translate *rdt = 675 673 container_of(ctx, struct ovl_readdir_translate, ctx); 676 674 struct dir_context *orig_ctx = rdt->orig_ctx; 675 + bool res; 677 676 678 677 if (rdt->parent_ino && strcmp(name, "..") == 0) { 679 678 ino = rdt->parent_ino; ··· 689 686 name, namelen, rdt->xinowarn); 690 687 } 691 688 692 - return orig_ctx->actor(orig_ctx, name, namelen, offset, ino, d_type); 689 + res = orig_ctx->actor(orig_ctx, name, namelen, offset, ino, d_type); 690 + ctx->count = orig_ctx->count; 691 + 692 + return res; 693 693 } 694 694 695 695 static bool ovl_is_impure_dir(struct file *file) ··· 719 713 const struct ovl_layer *lower_layer = ovl_layer_lower(dir); 720 714 struct ovl_readdir_translate rdt = { 721 715 .ctx.actor = ovl_fill_real, 716 + .ctx.count = ctx->count, 722 717 .orig_ctx = ctx, 723 718 .xinobits = ovl_xino_bits(ofs), 724 719 .xinowarn = ovl_xino_warn(ofs), ··· 1080 1073 int err; 1081 1074 struct ovl_readdir_data rdd = { 1082 1075 .ctx.actor = ovl_check_d_type, 1076 + .ctx.count = INT_MAX, 1083 1077 .d_type_supported = false, 1084 1078 }; 1085 1079 ··· 1102 1094 struct ovl_cache_entry *p; 1103 1095 struct ovl_readdir_data rdd = { 1104 1096 .ctx.actor = ovl_fill_plain, 1097 + .ctx.count = INT_MAX, 1105 1098 .list = &list, 1106 1099 }; 1107 1100 bool incompat = false; ··· 1187 1178 struct ovl_cache_entry *p; 1188 1179 struct ovl_readdir_data rdd = { 1189 1180 .ctx.actor = ovl_fill_plain, 1181 + .ctx.count = INT_MAX, 1190 1182 .list = &list, 1191 1183 }; 1192 1184
+14 -15
fs/readdir.c
··· 222 222 CLASS(fd_pos, f)(fd); 223 223 struct readdir_callback buf = { 224 224 .ctx.actor = fillonedir, 225 + .ctx.count = 1, /* Hint to fs: just one entry. */ 225 226 .dirent = dirent 226 227 }; 227 228 ··· 253 252 struct dir_context ctx; 254 253 struct linux_dirent __user * current_dir; 255 254 int prev_reclen; 256 - int count; 257 255 int error; 258 256 }; 259 257 ··· 275 275 if (unlikely(buf->error)) 276 276 return false; 277 277 buf->error = -EINVAL; /* only used if we fail.. */ 278 - if (reclen > buf->count) 278 + if (reclen > ctx->count) 279 279 return false; 280 280 d_ino = ino; 281 281 if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) { ··· 300 300 301 301 buf->current_dir = (void __user *)dirent + reclen; 302 302 buf->prev_reclen = reclen; 303 - buf->count -= reclen; 303 + ctx->count -= reclen; 304 304 return true; 305 305 efault_end: 306 306 user_write_access_end(); ··· 315 315 CLASS(fd_pos, f)(fd); 316 316 struct getdents_callback buf = { 317 317 .ctx.actor = filldir, 318 - .count = count, 318 + .ctx.count = count, 319 319 .current_dir = dirent 320 320 }; 321 321 int error; ··· 333 333 if (put_user(buf.ctx.pos, &lastdirent->d_off)) 334 334 error = -EFAULT; 335 335 else 336 - error = count - buf.count; 336 + error = count - buf.ctx.count; 337 337 } 338 338 return error; 339 339 } ··· 342 342 struct dir_context ctx; 343 343 struct linux_dirent64 __user * current_dir; 344 344 int prev_reclen; 345 - int count; 346 345 int error; 347 346 }; 348 347 ··· 363 364 if (unlikely(buf->error)) 364 365 return false; 365 366 buf->error = -EINVAL; /* only used if we fail.. */ 366 - if (reclen > buf->count) 367 + if (reclen > ctx->count) 367 368 return false; 368 369 prev_reclen = buf->prev_reclen; 369 370 if (!(flags & FILLDIR_FLAG_NOINTR) && prev_reclen && signal_pending(current)) ··· 383 384 384 385 buf->prev_reclen = reclen; 385 386 buf->current_dir = (void __user *)dirent + reclen; 386 - buf->count -= reclen; 387 + ctx->count -= reclen; 387 388 return true; 388 389 389 390 efault_end: ··· 399 400 CLASS(fd_pos, f)(fd); 400 401 struct getdents_callback64 buf = { 401 402 .ctx.actor = filldir64, 402 - .count = count, 403 + .ctx.count = count, 403 404 .current_dir = dirent 404 405 }; 405 406 int error; ··· 418 419 if (put_user(d_off, &lastdirent->d_off)) 419 420 error = -EFAULT; 420 421 else 421 - error = count - buf.count; 422 + error = count - buf.ctx.count; 422 423 } 423 424 return error; 424 425 } ··· 482 483 CLASS(fd_pos, f)(fd); 483 484 struct compat_readdir_callback buf = { 484 485 .ctx.actor = compat_fillonedir, 486 + .ctx.count = 1, /* Hint to fs: just one entry. */ 485 487 .dirent = dirent 486 488 }; 487 489 ··· 507 507 struct dir_context ctx; 508 508 struct compat_linux_dirent __user *current_dir; 509 509 int prev_reclen; 510 - int count; 511 510 int error; 512 511 }; 513 512 ··· 529 530 if (unlikely(buf->error)) 530 531 return false; 531 532 buf->error = -EINVAL; /* only used if we fail.. */ 532 - if (reclen > buf->count) 533 + if (reclen > ctx->count) 533 534 return false; 534 535 d_ino = ino; 535 536 if (sizeof(d_ino) < sizeof(ino) && d_ino != ino) { ··· 553 554 554 555 buf->prev_reclen = reclen; 555 556 buf->current_dir = (void __user *)dirent + reclen; 556 - buf->count -= reclen; 557 + ctx->count -= reclen; 557 558 return true; 558 559 efault_end: 559 560 user_write_access_end(); ··· 568 569 CLASS(fd_pos, f)(fd); 569 570 struct compat_getdents_callback buf = { 570 571 .ctx.actor = compat_filldir, 572 + .ctx.count = count, 571 573 .current_dir = dirent, 572 - .count = count 573 574 }; 574 575 int error; 575 576 ··· 586 587 if (put_user(buf.ctx.pos, &lastdirent->d_off)) 587 588 error = -EFAULT; 588 589 else 589 - error = count - buf.count; 590 + error = count - buf.ctx.count; 590 591 } 591 592 return error; 592 593 }
+7
include/linux/fs.h
··· 2071 2071 struct dir_context { 2072 2072 filldir_t actor; 2073 2073 loff_t pos; 2074 + /* 2075 + * Filesystems MUST NOT MODIFY count, but may use as a hint: 2076 + * 0 unknown 2077 + * > 0 space in buffer (assume at least one entry) 2078 + * INT_MAX unlimited 2079 + */ 2080 + int count; 2074 2081 }; 2075 2082 2076 2083 /* If OR-ed with d_type, pending signals are not checked */