find: -ls: don't panic on an unmapped owner uid/gid

`find <file> -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.
This commit is contained in:
weili
2026-06-09 07:49:00 +00:00
committed by Sylvestre Ledru
parent 5992901805
commit a70f5ccd7c
2 changed files with 38 additions and 9 deletions
+7 -9
View File
@@ -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();
+31
View File
@@ -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() {