procutils-common 0.2.0

Shared utilities for procutils tools (utmp, signal, procmatch, fmt, uid)
Documentation
//! UID/username resolution backed by `/etc/passwd`.
//!
//! Two free functions plus a small cache. We read `/etc/passwd`
//! directly rather than calling `getpwnam(3)` / `getpwuid(3)` so that
//! the test harness's bind-mounted `/proc` (and any sibling
//! bind-mounts) work without leaking host data: only what's mounted
//! into the namespace is visible.
//!
//! Tools that look up many UIDs in a single invocation (`pgrep`,
//! `top`, `w`) should use [`UidCache`] to avoid re-reading
//! `/etc/passwd` per call.

use std::collections::HashMap;

/// Resolve a username or numeric UID string to a UID.
///
/// If `name` parses as a `u32`, it is returned directly. Otherwise
/// `/etc/passwd` is searched for a matching `pw_name`. Returns `None`
/// if `/etc/passwd` is unreadable or has no matching entry.
pub fn resolve_uid(name: &str) -> Option<u32> {
    if let Ok(uid) = name.parse::<u32>() {
        return Some(uid);
    }
    let contents = std::fs::read_to_string("/etc/passwd").ok()?;
    for line in contents.lines() {
        let fields: Vec<&str> = line.split(':').collect();
        if fields.len() >= 3 && fields[0] == name {
            return fields[2].parse().ok();
        }
    }
    None
}

/// Look up a username by numeric UID via `/etc/passwd`.
///
/// Returns `None` if `/etc/passwd` is unreadable or has no matching
/// entry. Prefer [`UidCache`] when looking up many UIDs at once.
pub fn uid_to_name(uid: u32) -> Option<String> {
    let contents = std::fs::read_to_string("/etc/passwd").ok()?;
    for line in contents.lines() {
        let fields: Vec<&str> = line.split(':').collect();
        if fields.len() >= 3
            && let Ok(file_uid) = fields[2].parse::<u32>()
            && file_uid == uid
        {
            return Some(fields[0].to_string());
        }
    }
    None
}

/// A small cache for UID-to-username lookups.
///
/// Each call to [`UidCache::get`] reads `/etc/passwd` at most once per
/// distinct UID for the lifetime of the cache. Useful for tools that
/// resolve a username column for every row in a process listing.
pub struct UidCache {
    map: HashMap<u32, String>,
}

impl UidCache {
    /// Construct an empty cache.
    pub fn new() -> Self {
        Self {
            map: HashMap::new(),
        }
    }

    /// Returns the username for the given UID, falling back to the
    /// numeric string representation if no entry is found in
    /// `/etc/passwd`. Subsequent calls with the same UID return the
    /// cached value without re-reading the file.
    pub fn get(&mut self, uid: u32) -> &str {
        self.map.entry(uid).or_insert_with(|| {
            uid_to_name(uid).unwrap_or_else(|| uid.to_string())
        })
    }
}

impl Default for UidCache {
    fn default() -> Self {
        Self::new()
    }
}