use std::collections::HashMap;
use crate::Pid;
#[derive(Default)]
pub struct Users {
by_uid: HashMap<u32, Name>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
enum Name {
Resolved(String),
Unknown,
}
impl Users {
pub fn new() -> Self {
Self::default()
}
pub fn name(&mut self, uid: u32) -> &str {
let entry = self.by_uid.entry(uid).or_insert_with(|| match lookup(uid) {
Some(name) => Name::Resolved(name),
None => Name::Unknown,
});
match entry {
Name::Resolved(name) => name.as_str(),
Name::Unknown => {
*entry = Name::Resolved(uid.to_string());
match entry {
Name::Resolved(name) => name.as_str(),
Name::Unknown => unreachable!(),
}
}
}
}
pub fn name_of(uid: u32) -> String {
lookup(uid).unwrap_or_else(|| uid.to_string())
}
pub fn len(&self) -> usize {
self.by_uid.len()
}
pub fn is_empty(&self) -> bool {
self.by_uid.is_empty()
}
}
#[cfg(unix)]
fn lookup(uid: u32) -> Option<String> {
let mut buf = vec![0i8; 1024];
loop {
let mut pwd: libc::passwd = unsafe { std::mem::zeroed() };
let mut result: *mut libc::passwd = std::ptr::null_mut();
let rc = unsafe {
libc::getpwuid_r(
uid as libc::uid_t,
&mut pwd,
buf.as_mut_ptr().cast(),
buf.len(),
&mut result,
)
};
if rc == libc::ERANGE && buf.len() < 1 << 20 {
buf.resize(buf.len() * 2, 0);
continue;
}
if rc != 0 || result.is_null() {
return None;
}
let name = unsafe { std::ffi::CStr::from_ptr(pwd.pw_name) };
return Some(name.to_string_lossy().into_owned());
}
}
#[cfg(not(unix))]
fn lookup(_uid: u32) -> Option<String> {
None
}
pub fn pids_of_user(rows: &[crate::row::Row], uid: u32) -> Vec<Pid> {
rows.iter()
.filter(|r| r.uid == uid)
.map(|r| r.pid)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolves_root() {
assert_eq!(Users::name_of(0), "root");
}
#[test]
fn resolves_the_current_user() {
let uid = unsafe { libc::getuid() };
let name = Users::name_of(uid);
assert!(!name.is_empty());
assert_ne!(
name,
uid.to_string(),
"current user should have a passwd entry"
);
}
#[test]
fn unknown_uid_falls_back_to_the_number() {
assert_eq!(Users::name_of(4_000_000), "4,000,000".replace(',', ""));
}
#[test]
fn cache_returns_a_stable_borrow() {
let mut users = Users::new();
let a = users.name(0).to_string();
let b = users.name(0).to_string();
assert_eq!(a, b);
assert_eq!(users.len(), 1, "second lookup must hit the cache");
}
#[test]
fn cache_caches_the_fallback_too() {
let mut users = Users::new();
assert_eq!(users.name(4_000_001), "4000001");
assert_eq!(users.name(4_000_001), "4000001");
assert_eq!(users.len(), 1);
}
}