procutils_common/uid.rs
1//! UID/username resolution backed by `/etc/passwd`.
2//!
3//! Two free functions plus a small cache. We read `/etc/passwd`
4//! directly rather than calling `getpwnam(3)` / `getpwuid(3)` so that
5//! the test harness's bind-mounted `/proc` (and any sibling
6//! bind-mounts) work without leaking host data: only what's mounted
7//! into the namespace is visible.
8//!
9//! Tools that look up many UIDs in a single invocation (`pgrep`,
10//! `top`, `w`) should use [`UidCache`] to avoid re-reading
11//! `/etc/passwd` per call.
12
13use std::collections::HashMap;
14
15/// Resolve a username or numeric UID string to a UID.
16///
17/// If `name` parses as a `u32`, it is returned directly. Otherwise
18/// `/etc/passwd` is searched for a matching `pw_name`. Returns `None`
19/// if `/etc/passwd` is unreadable or has no matching entry.
20pub fn resolve_uid(name: &str) -> Option<u32> {
21 if let Ok(uid) = name.parse::<u32>() {
22 return Some(uid);
23 }
24 let contents = std::fs::read_to_string("/etc/passwd").ok()?;
25 for line in contents.lines() {
26 let fields: Vec<&str> = line.split(':').collect();
27 if fields.len() >= 3 && fields[0] == name {
28 return fields[2].parse().ok();
29 }
30 }
31 None
32}
33
34/// Look up a username by numeric UID via `/etc/passwd`.
35///
36/// Returns `None` if `/etc/passwd` is unreadable or has no matching
37/// entry. Prefer [`UidCache`] when looking up many UIDs at once.
38pub fn uid_to_name(uid: u32) -> Option<String> {
39 let contents = std::fs::read_to_string("/etc/passwd").ok()?;
40 for line in contents.lines() {
41 let fields: Vec<&str> = line.split(':').collect();
42 if fields.len() >= 3
43 && let Ok(file_uid) = fields[2].parse::<u32>()
44 && file_uid == uid
45 {
46 return Some(fields[0].to_string());
47 }
48 }
49 None
50}
51
52/// A small cache for UID-to-username lookups.
53///
54/// Each call to [`UidCache::get`] reads `/etc/passwd` at most once per
55/// distinct UID for the lifetime of the cache. Useful for tools that
56/// resolve a username column for every row in a process listing.
57pub struct UidCache {
58 map: HashMap<u32, String>,
59}
60
61impl UidCache {
62 /// Construct an empty cache.
63 pub fn new() -> Self {
64 Self {
65 map: HashMap::new(),
66 }
67 }
68
69 /// Returns the username for the given UID, falling back to the
70 /// numeric string representation if no entry is found in
71 /// `/etc/passwd`. Subsequent calls with the same UID return the
72 /// cached value without re-reading the file.
73 pub fn get(&mut self, uid: u32) -> &str {
74 self.map.entry(uid).or_insert_with(|| {
75 uid_to_name(uid).unwrap_or_else(|| uid.to_string())
76 })
77 }
78}
79
80impl Default for UidCache {
81 fn default() -> Self {
82 Self::new()
83 }
84}