Skip to main content

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}