From a70f5ccd7c071aaeb114774936e79e543dd5c361 Mon Sep 17 00:00:00 2001 From: weili <541602953@qq.com> Date: Tue, 9 Jun 2026 07:49:00 +0000 Subject: [PATCH] find: -ls: don't panic on an unmapped owner uid/gid `find -ls` panicked (exit 101) when the file's owning uid (or gid) had no `/etc/passwd` (`/etc/group`) entry: it resolved the name with `User::from_uid(...).unwrap().unwrap()`, and the inner unwrap aborts on the `Ok(None)` that an unmapped id returns. Unmapped owners are routine (extracted archives, NFS, deleted accounts, container images). Resolve the uid/gid via `uucore::entries::uid2usr`/`gid2grp`, falling back to the numeric id when there is no entry, matching GNU find (which prints the number). These helpers already serialize the non-thread-safe getpwuid/getgrgid behind a mutex, so reuse them rather than reimplementing the lookup. Add a -ls integration test that chowns a file to an unmapped id and checks the numeric fallback; it skips when the process lacks the privilege to do so. --- src/find/matchers/ls.rs | 16 +++++++--------- tests/test_find.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/find/matchers/ls.rs b/src/find/matchers/ls.rs index 732f4ec..9b05f82 100644 --- a/src/find/matchers/ls.rs +++ b/src/find/matchers/ls.rs @@ -126,7 +126,6 @@ impl Ls { mut out: impl Write, print_error_message: bool, ) { - use nix::unistd::{Gid, Group, Uid, User}; use std::os::unix::fs::{MetadataExt, PermissionsExt}; let metadata = file_info.metadata().unwrap(); @@ -150,14 +149,13 @@ impl Ls { let permission = { format_permissions(metadata.permissions().mode() as uucore::libc::mode_t) }; let hard_links = metadata.nlink(); - let user = { - let uid = metadata.uid(); - User::from_uid(Uid::from_raw(uid)).unwrap().unwrap().name - }; - let group = { - let gid = metadata.gid(); - Group::from_gid(Gid::from_raw(gid)).unwrap().unwrap().name - }; + // Fall back to the numeric id when the uid/gid has no passwd/group entry + // (unmapped owner) — matching GNU find, which never crashes. uucore::entries + // serializes the non-thread-safe getpwuid/getgrgid behind a mutex. + let user = + uucore::entries::uid2usr(metadata.uid()).unwrap_or_else(|_| metadata.uid().to_string()); + let group = + uucore::entries::gid2grp(metadata.gid()).unwrap_or_else(|_| metadata.gid().to_string()); let size = metadata.size(); let last_modified = { let system_time = metadata.modified().unwrap(); diff --git a/tests/test_find.rs b/tests/test_find.rs index 70a5ec9..cfee583 100644 --- a/tests/test_find.rs +++ b/tests/test_find.rs @@ -1018,6 +1018,37 @@ fn find_ls() { .no_stderr(); } +// Regression test for uutils/findutils#717: `-ls` used to abort (exit 101) when +// a file's owning uid/gid had no passwd/group entry; it must fall back to the +// numeric id like GNU find. Creating such a file needs privilege to chown to an +// unmapped id, so skip when that isn't available (e.g. non-root CI). +#[test] +#[cfg(unix)] +fn find_ls_unmapped_owner_renders_numeric_id() { + use nix::unistd::{chown, Gid, Uid}; + + let temp_dir = Builder::new().prefix("find_ls_unmapped").tempdir().unwrap(); + let file = temp_dir.path().join("orphan"); + File::create(&file).unwrap(); + + // A uid/gid essentially never present in /etc/passwd or /etc/group. + let unmapped = 4_000_000_000u32; + if chown( + &file, + Some(Uid::from_raw(unmapped)), + Some(Gid::from_raw(unmapped)), + ) + .is_err() + { + return; + } + + ucmd() + .args(&[file.to_str().unwrap(), "-ls"]) + .succeeds() + .stdout_contains(unmapped.to_string()); +} + #[test] #[cfg(unix)] fn find_slashes() {