From 57043cbb635da36dd030fb2a5654858932503272 Mon Sep 17 00:00:00 2001 From: Chris Dryden Date: Thu, 12 Feb 2026 14:46:47 -0500 Subject: [PATCH] ls: release directory fds before recursing to avoid EMFILE - 12x mem improv (#10894) --- src/uu/ls/src/ls.rs | 5 +++++ tests/by-util/test_ls.rs | 25 ++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/uu/ls/src/ls.rs b/src/uu/ls/src/ls.rs index 7365cb4dc..f2f9cc301 100644 --- a/src/uu/ls/src/ls.rs +++ b/src/uu/ls/src/ls.rs @@ -2496,6 +2496,11 @@ fn enter_directory( display_items(&entries, config, state, dired)?; if config.recursive { + // release the open fd before recursing to not run out of resources + for entry in &entries { + entry.de.take(); + } + drop(read_dir); for e in entries .iter() .skip(if config.files == Files::All { 2 } else { 0 }) diff --git a/tests/by-util/test_ls.rs b/tests/by-util/test_ls.rs index 37492d80a..11ce1c5b9 100644 --- a/tests/by-util/test_ls.rs +++ b/tests/by-util/test_ls.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. // spell-checker:ignore (words) READMECAREFULLY birthtime doesntexist oneline somebackup lrwx somefile somegroup somehiddenbackup somehiddenfile tabsize aaaaaaaa bbbb cccc dddddddd ncccc neee naaaaa nbcdef nfffff dired subdired tmpfs mdir COLORTERM mexe bcdef mfoo timefile -// spell-checker:ignore (words) fakeroot setcap drwxr bcdlps mdangling mentry awith acolons +// spell-checker:ignore (words) fakeroot setcap drwxr bcdlps mdangling mentry awith acolons NOFILE #![allow( clippy::similar_names, clippy::too_many_lines, @@ -13,6 +13,8 @@ #[cfg(all(unix, feature = "chmod"))] use nix::unistd::{close, dup}; use regex::Regex; +#[cfg(unix)] +use rlimit::Resource; #[cfg(not(target_os = "openbsd"))] use std::collections::HashMap; #[cfg(target_os = "linux")] @@ -6992,3 +6994,24 @@ fn test_ls_proc_self_fd_no_errors() { .succeeds() .stderr_does_not_contain("cannot access"); } + +#[test] +#[cfg(unix)] +fn test_ls_recursive_no_fd_leak() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + let deep_path: String = (1..=30) + .map(|i| i.to_string()) + .collect::>() + .join("/"); + at.mkdir_all(&deep_path); + + scene + .ucmd() + .arg("-R") + .arg("1") + .limit(Resource::NOFILE, 20, 20) + .succeeds() + .stderr_is(""); +}