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(""); +}