1use std::collections::HashMap;
9
10use crate::Pid;
11
12#[derive(Default)]
17pub struct Users {
18 by_uid: HashMap<u32, Name>,
19}
20
21#[derive(Clone, Debug, PartialEq, Eq)]
22enum Name {
23 Resolved(String),
24 Unknown,
26}
27
28impl Users {
29 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn name(&mut self, uid: u32) -> &str {
35 let entry = self.by_uid.entry(uid).or_insert_with(|| match lookup(uid) {
36 Some(name) => Name::Resolved(name),
37 None => Name::Unknown,
38 });
39
40 match entry {
41 Name::Resolved(name) => name.as_str(),
42 Name::Unknown => {
43 *entry = Name::Resolved(uid.to_string());
45 match entry {
46 Name::Resolved(name) => name.as_str(),
47 Name::Unknown => unreachable!(),
48 }
49 }
50 }
51 }
52
53 pub fn name_of(uid: u32) -> String {
55 lookup(uid).unwrap_or_else(|| uid.to_string())
56 }
57
58 pub fn len(&self) -> usize {
59 self.by_uid.len()
60 }
61
62 pub fn is_empty(&self) -> bool {
63 self.by_uid.is_empty()
64 }
65}
66
67#[cfg(unix)]
69fn lookup(uid: u32) -> Option<String> {
70 let mut buf = vec![0i8; 1024];
73
74 loop {
75 let mut pwd: libc::passwd = unsafe { std::mem::zeroed() };
76 let mut result: *mut libc::passwd = std::ptr::null_mut();
77
78 let rc = unsafe {
82 libc::getpwuid_r(
83 uid as libc::uid_t,
84 &mut pwd,
85 buf.as_mut_ptr().cast(),
86 buf.len(),
87 &mut result,
88 )
89 };
90
91 if rc == libc::ERANGE && buf.len() < 1 << 20 {
92 buf.resize(buf.len() * 2, 0);
93 continue;
94 }
95 if rc != 0 || result.is_null() {
96 return None;
97 }
98
99 let name = unsafe { std::ffi::CStr::from_ptr(pwd.pw_name) };
102 return Some(name.to_string_lossy().into_owned());
103 }
104}
105
106#[cfg(not(unix))]
107fn lookup(_uid: u32) -> Option<String> {
108 None
109}
110
111pub fn pids_of_user(rows: &[crate::row::Row], uid: u32) -> Vec<Pid> {
113 rows.iter()
114 .filter(|r| r.uid == uid)
115 .map(|r| r.pid)
116 .collect()
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn resolves_root() {
125 assert_eq!(Users::name_of(0), "root");
127 }
128
129 #[test]
130 fn resolves_the_current_user() {
131 let uid = unsafe { libc::getuid() };
133 let name = Users::name_of(uid);
134 assert!(!name.is_empty());
135 assert_ne!(
136 name,
137 uid.to_string(),
138 "current user should have a passwd entry"
139 );
140 }
141
142 #[test]
143 fn unknown_uid_falls_back_to_the_number() {
144 assert_eq!(Users::name_of(4_000_000), "4,000,000".replace(',', ""));
146 }
147
148 #[test]
149 fn cache_returns_a_stable_borrow() {
150 let mut users = Users::new();
151 let a = users.name(0).to_string();
152 let b = users.name(0).to_string();
153 assert_eq!(a, b);
154 assert_eq!(users.len(), 1, "second lookup must hit the cache");
155 }
156
157 #[test]
158 fn cache_caches_the_fallback_too() {
159 let mut users = Users::new();
160 assert_eq!(users.name(4_000_001), "4000001");
161 assert_eq!(users.name(4_000_001), "4000001");
162 assert_eq!(users.len(), 1);
163 }
164}