1use std::time::Duration;
27
28use tokio::time::Instant;
29
30#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct PasswdEntry {
34 pub username: String,
36 pub passwd: String,
38 pub uid: u32,
40 pub gid: u32,
42 pub gecos: String,
44 pub home_dir: String,
46 pub shell: String,
48}
49
50impl PasswdEntry {
51 pub fn parse(s: &str) -> Option<PasswdEntry> {
53 let mut entries = s.splitn(7, ':');
54 Some(PasswdEntry {
55 username: match entries.next() {
56 None => return None,
57 Some(s) => s.to_string(),
58 },
59 passwd: match entries.next() {
60 None => return None,
61 Some(s) => s.to_string(),
62 },
63 uid: match entries.next().and_then(|s| s.parse().ok()) {
64 None => return None,
65 Some(s) => s,
66 },
67 gid: match entries.next().and_then(|s| s.parse().ok()) {
68 None => return None,
69 Some(s) => s,
70 },
71 gecos: match entries.next() {
72 None => return None,
73 Some(s) => s.to_string(),
74 },
75 home_dir: match entries.next() {
76 None => return None,
77 Some(s) => s.to_string(),
78 },
79 shell: match entries.next() {
80 None => return None,
81 Some(s) => s.to_string(),
82 },
83 })
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct GroupEntry {
91 pub name: String,
93 pub passwd: String,
95 pub gid: u32,
97 pub users: Vec<String>,
99}
100
101impl GroupEntry {
102 pub fn parse(s: &str) -> Option<GroupEntry> {
104 let mut entries = s.splitn(4, ':');
105 Some(GroupEntry {
106 name: match entries.next() {
107 None => return None,
108 Some(s) => s.to_string(),
109 },
110 passwd: match entries.next() {
111 None => return None,
112 Some(s) => s.to_string(),
113 },
114 gid: match entries.next().and_then(|s| s.parse().ok()) {
115 None => return None,
116 Some(s) => s,
117 },
118 users: match entries.next() {
119 None => return None,
120 Some(s) => s.split(',').map(|p| p.to_string()).collect(),
121 },
122 })
123 }
124}
125
126pub struct PasswdReader {
144 file: Option<String>,
145 cache_time: Duration,
146 last_check: Instant,
147 passwd: Vec<PasswdEntry>,
148}
149
150impl PasswdReader {
151 pub fn new(cache_time: Duration) -> Self {
156 let last_check = Instant::now() - (cache_time);
157 Self {
158 file: None,
159 cache_time,
160 last_check,
161 passwd: vec![],
162 }
163 }
164
165 pub fn new_at(file: &str, cache_time: Duration) -> Self {
171 let last_check = Instant::now() - (cache_time);
172 Self {
173 file: Some(file.to_string()),
174 cache_time,
175 last_check,
176 passwd: vec![],
177 }
178 }
179
180 async fn refresh_if_needed(&mut self) -> Result<(), std::io::Error> {
181 if Instant::now() < (self.last_check + self.cache_time) {
182 return Ok(());
183 }
184 let contents =
185 tokio::fs::read_to_string(self.file.as_ref().unwrap_or(&"/etc/passwd".to_string()))
186 .await?;
187 self.passwd = contents.lines().filter_map(PasswdEntry::parse).collect();
188 Ok(())
189 }
190
191 pub async fn get_entries(&mut self) -> Result<&Vec<PasswdEntry>, std::io::Error> {
193 self.refresh_if_needed().await?;
194 Ok(&self.passwd)
195 }
196
197 pub async fn to_iter(mut self) -> Result<std::vec::IntoIter<PasswdEntry>, std::io::Error> {
199 self.refresh_if_needed().await?;
200 Ok(self.passwd.into_iter())
201 }
202
203 pub async fn get_by_username(
205 &mut self,
206 username: &str,
207 ) -> Result<Option<PasswdEntry>, std::io::Error> {
208 self.refresh_if_needed().await?;
209 Ok(self
210 .passwd
211 .iter()
212 .find(|e| e.username == username)
213 .map(|e| e.to_owned()))
214 }
215
216 pub async fn get_by_uid(&mut self, uid: u32) -> Result<Option<PasswdEntry>, std::io::Error> {
218 self.refresh_if_needed().await?;
219 Ok(self
220 .passwd
221 .iter()
222 .find(|e| e.uid == uid)
223 .map(|e| e.to_owned()))
224 }
225
226 pub async fn get_username_by_uid(
228 &mut self,
229 uid: u32,
230 ) -> Result<Option<String>, std::io::Error> {
231 self.refresh_if_needed().await?;
232 Ok(self
233 .passwd
234 .iter()
235 .find(|e| e.uid == uid)
236 .map(|e| e.username.to_owned()))
237 }
238
239 pub async fn get_uid_by_username(
241 &mut self,
242 username: &str,
243 ) -> Result<Option<u32>, std::io::Error> {
244 self.refresh_if_needed().await?;
245 Ok(self
246 .passwd
247 .iter()
248 .find(|e| e.username == username)
249 .map(|e| e.uid))
250 }
251}
252
253pub struct GroupReader {
270 file: Option<String>,
271 cache_time: Duration,
272 last_check: Instant,
273 groups: Vec<GroupEntry>,
274}
275
276impl GroupReader {
277 pub fn new(cache_time: Duration) -> Self {
282 let last_check = Instant::now() - (cache_time);
283 Self {
284 file: None,
285 cache_time,
286 last_check,
287 groups: vec![],
288 }
289 }
290
291 pub fn new_at(file: &str, cache_time: Duration) -> Self {
297 let last_check = Instant::now() - (cache_time);
298 Self {
299 file: Some(file.to_string()),
300 cache_time,
301 last_check,
302 groups: vec![],
303 }
304 }
305
306 async fn refresh_if_needed(&mut self) -> Result<(), std::io::Error> {
307 if Instant::now() < (self.last_check + self.cache_time) {
308 return Ok(());
309 }
310 let contents =
311 tokio::fs::read_to_string(self.file.as_ref().unwrap_or(&"/etc/group".to_string()))
312 .await?;
313 self.groups = contents.lines().filter_map(GroupEntry::parse).collect();
314 Ok(())
315 }
316
317 pub async fn get_groups(&mut self) -> Result<&Vec<GroupEntry>, std::io::Error> {
319 self.refresh_if_needed().await?;
320 Ok(&self.groups)
321 }
322
323 pub async fn to_iter(mut self) -> Result<std::vec::IntoIter<GroupEntry>, std::io::Error> {
325 self.refresh_if_needed().await?;
326 Ok(self.groups.into_iter())
327 }
328
329 pub async fn get_by_name(&mut self, name: &str) -> Result<Option<GroupEntry>, std::io::Error> {
331 self.refresh_if_needed().await?;
332 Ok(self
333 .groups
334 .iter()
335 .find(|e| e.name == name)
336 .map(|e| e.to_owned()))
337 }
338
339 pub async fn get_by_gid(&mut self, gid: u32) -> Result<Option<GroupEntry>, std::io::Error> {
341 self.refresh_if_needed().await?;
342 Ok(self
343 .groups
344 .iter()
345 .find(|e| e.gid == gid)
346 .map(|e| e.to_owned()))
347 }
348
349 pub async fn get_name_by_gid(&mut self, gid: u32) -> Result<Option<String>, std::io::Error> {
351 self.refresh_if_needed().await?;
352 Ok(self
353 .groups
354 .iter()
355 .find(|e| e.gid == gid)
356 .map(|e| e.name.to_owned()))
357 }
358
359 pub async fn get_gid_by_name(&mut self, name: &str) -> Result<Option<u32>, std::io::Error> {
361 self.refresh_if_needed().await?;
362 Ok(self.groups.iter().find(|e| e.name == name).map(|e| e.gid))
363 }
364}