1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use std::cell::UnsafeCell; |
4 | use std::collections::HashMap; |
5 | use std::fmt; |
6 | use std::fs::{self, File}; |
7 | use std::io::Read; |
8 | use std::path::{Path, PathBuf}; |
9 | use std::str::FromStr; |
10 | |
11 | use libc::{gid_t, kill, uid_t}; |
12 | |
13 | use crate::sys::system::SystemInfo; |
14 | use crate::sys::utils::{ |
15 | get_all_data, get_all_data_from_file, realpath, FileCounter, PathHandler, PathPush, |
16 | }; |
17 | use crate::utils::into_iter; |
18 | use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; |
19 | |
20 | #[doc (hidden)] |
21 | impl From<char> for ProcessStatus { |
22 | fn from(status: char) -> ProcessStatus { |
23 | match status { |
24 | 'R' => ProcessStatus::Run, |
25 | 'S' => ProcessStatus::Sleep, |
26 | 'I' => ProcessStatus::Idle, |
27 | 'D' => ProcessStatus::UninterruptibleDiskSleep, |
28 | 'Z' => ProcessStatus::Zombie, |
29 | 'T' => ProcessStatus::Stop, |
30 | 't' => ProcessStatus::Tracing, |
31 | 'X' | 'x' => ProcessStatus::Dead, |
32 | 'K' => ProcessStatus::Wakekill, |
33 | 'W' => ProcessStatus::Waking, |
34 | 'P' => ProcessStatus::Parked, |
35 | x: char => ProcessStatus::Unknown(x as u32), |
36 | } |
37 | } |
38 | } |
39 | |
40 | impl fmt::Display for ProcessStatus { |
41 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
42 | f.write_str(data:match *self { |
43 | ProcessStatus::Idle => "Idle" , |
44 | ProcessStatus::Run => "Runnable" , |
45 | ProcessStatus::Sleep => "Sleeping" , |
46 | ProcessStatus::Stop => "Stopped" , |
47 | ProcessStatus::Zombie => "Zombie" , |
48 | ProcessStatus::Tracing => "Tracing" , |
49 | ProcessStatus::Dead => "Dead" , |
50 | ProcessStatus::Wakekill => "Wakekill" , |
51 | ProcessStatus::Waking => "Waking" , |
52 | ProcessStatus::Parked => "Parked" , |
53 | ProcessStatus::UninterruptibleDiskSleep => "UninterruptibleDiskSleep" , |
54 | _ => "Unknown" , |
55 | }) |
56 | } |
57 | } |
58 | |
59 | #[doc = include_str!("../../md_doc/process.md" )] |
60 | pub struct Process { |
61 | pub(crate) name: String, |
62 | pub(crate) cmd: Vec<String>, |
63 | pub(crate) exe: PathBuf, |
64 | pub(crate) pid: Pid, |
65 | parent: Option<Pid>, |
66 | pub(crate) environ: Vec<String>, |
67 | pub(crate) cwd: PathBuf, |
68 | pub(crate) root: PathBuf, |
69 | pub(crate) memory: u64, |
70 | pub(crate) virtual_memory: u64, |
71 | utime: u64, |
72 | stime: u64, |
73 | old_utime: u64, |
74 | old_stime: u64, |
75 | start_time_without_boot_time: u64, |
76 | start_time: u64, |
77 | run_time: u64, |
78 | pub(crate) updated: bool, |
79 | cpu_usage: f32, |
80 | user_id: Option<Uid>, |
81 | effective_user_id: Option<Uid>, |
82 | group_id: Option<Gid>, |
83 | effective_group_id: Option<Gid>, |
84 | pub(crate) status: ProcessStatus, |
85 | /// Tasks run by this process. |
86 | pub tasks: HashMap<Pid, Process>, |
87 | pub(crate) stat_file: Option<FileCounter>, |
88 | old_read_bytes: u64, |
89 | old_written_bytes: u64, |
90 | read_bytes: u64, |
91 | written_bytes: u64, |
92 | } |
93 | |
94 | impl Process { |
95 | pub(crate) fn new(pid: Pid) -> Process { |
96 | Process { |
97 | name: String::with_capacity(20), |
98 | pid, |
99 | parent: None, |
100 | cmd: Vec::with_capacity(2), |
101 | environ: Vec::with_capacity(10), |
102 | exe: PathBuf::new(), |
103 | cwd: PathBuf::new(), |
104 | root: PathBuf::new(), |
105 | memory: 0, |
106 | virtual_memory: 0, |
107 | cpu_usage: 0., |
108 | utime: 0, |
109 | stime: 0, |
110 | old_utime: 0, |
111 | old_stime: 0, |
112 | updated: true, |
113 | start_time_without_boot_time: 0, |
114 | start_time: 0, |
115 | run_time: 0, |
116 | user_id: None, |
117 | effective_user_id: None, |
118 | group_id: None, |
119 | effective_group_id: None, |
120 | status: ProcessStatus::Unknown(0), |
121 | tasks: if pid.0 == 0 { |
122 | HashMap::with_capacity(1000) |
123 | } else { |
124 | HashMap::new() |
125 | }, |
126 | stat_file: None, |
127 | old_read_bytes: 0, |
128 | old_written_bytes: 0, |
129 | read_bytes: 0, |
130 | written_bytes: 0, |
131 | } |
132 | } |
133 | } |
134 | |
135 | impl ProcessExt for Process { |
136 | fn kill_with(&self, signal: Signal) -> Option<bool> { |
137 | let c_signal = super::system::convert_signal(signal)?; |
138 | unsafe { Some(kill(self.pid.0, c_signal) == 0) } |
139 | } |
140 | |
141 | fn name(&self) -> &str { |
142 | &self.name |
143 | } |
144 | |
145 | fn cmd(&self) -> &[String] { |
146 | &self.cmd |
147 | } |
148 | |
149 | fn exe(&self) -> &Path { |
150 | self.exe.as_path() |
151 | } |
152 | |
153 | fn pid(&self) -> Pid { |
154 | self.pid |
155 | } |
156 | |
157 | fn environ(&self) -> &[String] { |
158 | &self.environ |
159 | } |
160 | |
161 | fn cwd(&self) -> &Path { |
162 | self.cwd.as_path() |
163 | } |
164 | |
165 | fn root(&self) -> &Path { |
166 | self.root.as_path() |
167 | } |
168 | |
169 | fn memory(&self) -> u64 { |
170 | self.memory |
171 | } |
172 | |
173 | fn virtual_memory(&self) -> u64 { |
174 | self.virtual_memory |
175 | } |
176 | |
177 | fn parent(&self) -> Option<Pid> { |
178 | self.parent |
179 | } |
180 | |
181 | fn status(&self) -> ProcessStatus { |
182 | self.status |
183 | } |
184 | |
185 | fn start_time(&self) -> u64 { |
186 | self.start_time |
187 | } |
188 | |
189 | fn run_time(&self) -> u64 { |
190 | self.run_time |
191 | } |
192 | |
193 | fn cpu_usage(&self) -> f32 { |
194 | self.cpu_usage |
195 | } |
196 | |
197 | fn disk_usage(&self) -> DiskUsage { |
198 | DiskUsage { |
199 | written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes), |
200 | total_written_bytes: self.written_bytes, |
201 | read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes), |
202 | total_read_bytes: self.read_bytes, |
203 | } |
204 | } |
205 | |
206 | fn user_id(&self) -> Option<&Uid> { |
207 | self.user_id.as_ref() |
208 | } |
209 | |
210 | fn effective_user_id(&self) -> Option<&Uid> { |
211 | self.effective_user_id.as_ref() |
212 | } |
213 | |
214 | fn group_id(&self) -> Option<Gid> { |
215 | self.group_id |
216 | } |
217 | |
218 | fn effective_group_id(&self) -> Option<Gid> { |
219 | self.effective_group_id |
220 | } |
221 | |
222 | fn wait(&self) { |
223 | let mut status = 0; |
224 | // attempt waiting |
225 | unsafe { |
226 | if retry_eintr!(libc::waitpid(self.pid.0, &mut status, 0)) < 0 { |
227 | // attempt failed (non-child process) so loop until process ends |
228 | let duration = std::time::Duration::from_millis(10); |
229 | while kill(self.pid.0, 0) == 0 { |
230 | std::thread::sleep(duration); |
231 | } |
232 | } |
233 | } |
234 | } |
235 | |
236 | fn session_id(&self) -> Option<Pid> { |
237 | unsafe { |
238 | let session_id = libc::getsid(self.pid.0); |
239 | if session_id < 0 { |
240 | None |
241 | } else { |
242 | Some(Pid(session_id)) |
243 | } |
244 | } |
245 | } |
246 | } |
247 | |
248 | pub(crate) fn compute_cpu_usage(p: &mut Process, total_time: f32, max_value: f32) { |
249 | // First time updating the values without reference, wait for a second cycle to update cpu_usage |
250 | if p.old_utime == 0 && p.old_stime == 0 { |
251 | return; |
252 | } |
253 | |
254 | // We use `max_value` to ensure that the process CPU usage will never get bigger than: |
255 | // `"number of CPUs" * 100.` |
256 | p.cpu_usage = (pu64 |
257 | .utime |
258 | .saturating_sub(p.old_utime) |
259 | .saturating_add(p.stime.saturating_sub(p.old_stime)) as f32 |
260 | / total_time |
261 | * 100.) |
262 | .min(max_value); |
263 | |
264 | for task: &mut Process in p.tasks.values_mut() { |
265 | compute_cpu_usage(p:task, total_time, max_value); |
266 | } |
267 | } |
268 | |
269 | pub(crate) fn unset_updated(p: &mut Process) { |
270 | p.updated = false; |
271 | for task: &mut Process in p.tasks.values_mut() { |
272 | unset_updated(task); |
273 | } |
274 | } |
275 | |
276 | pub(crate) fn set_time(p: &mut Process, utime: u64, stime: u64) { |
277 | p.old_utime = p.utime; |
278 | p.old_stime = p.stime; |
279 | p.utime = utime; |
280 | p.stime = stime; |
281 | p.updated = true; |
282 | } |
283 | |
284 | pub(crate) fn update_process_disk_activity(p: &mut Process, path: &Path) { |
285 | let data = match get_all_data(path.join("io" ), 16_384) { |
286 | Ok(d) => d, |
287 | Err(_) => return, |
288 | }; |
289 | let mut done = 0; |
290 | for line in data.split(' \n' ) { |
291 | let mut parts = line.split(": " ); |
292 | match parts.next() { |
293 | Some("read_bytes" ) => { |
294 | p.old_read_bytes = p.read_bytes; |
295 | p.read_bytes = parts |
296 | .next() |
297 | .and_then(|x| x.parse::<u64>().ok()) |
298 | .unwrap_or(p.old_read_bytes); |
299 | } |
300 | Some("write_bytes" ) => { |
301 | p.old_written_bytes = p.written_bytes; |
302 | p.written_bytes = parts |
303 | .next() |
304 | .and_then(|x| x.parse::<u64>().ok()) |
305 | .unwrap_or(p.old_written_bytes); |
306 | } |
307 | _ => continue, |
308 | } |
309 | done += 1; |
310 | if done > 1 { |
311 | // No need to continue the reading. |
312 | break; |
313 | } |
314 | } |
315 | } |
316 | |
317 | struct Wrap<'a, T>(UnsafeCell<&'a mut T>); |
318 | |
319 | impl<'a, T> Wrap<'a, T> { |
320 | fn get(&self) -> &'a mut T { |
321 | unsafe { *(self.0.get()) } |
322 | } |
323 | } |
324 | |
325 | #[allow (clippy::non_send_fields_in_send_ty)] |
326 | unsafe impl<'a, T> Send for Wrap<'a, T> {} |
327 | unsafe impl<'a, T> Sync for Wrap<'a, T> {} |
328 | |
329 | #[inline (always)] |
330 | fn compute_start_time_without_boot_time(parts: &[&str], info: &SystemInfo) -> u64 { |
331 | // To be noted that the start time is invalid here, it still needs to be converted into |
332 | // "real" time. |
333 | u64::from_str(parts[21]).unwrap_or(default:0) / info.clock_cycle |
334 | } |
335 | |
336 | fn _get_stat_data(path: &Path, stat_file: &mut Option<FileCounter>) -> Result<String, ()> { |
337 | let mut file: File = File::open(path.join("stat" )).map_err(|_| ())?; |
338 | let data: String = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; |
339 | *stat_file = FileCounter::new(file); |
340 | Ok(data) |
341 | } |
342 | |
343 | #[inline (always)] |
344 | fn get_status(p: &mut Process, part: &str) { |
345 | p.status = partOption |
346 | .chars() |
347 | .next() |
348 | .map(ProcessStatus::from) |
349 | .unwrap_or_else(|| ProcessStatus::Unknown(0)); |
350 | } |
351 | |
352 | fn refresh_user_group_ids<P: PathPush>(p: &mut Process, path: &mut P) { |
353 | if let Some(((user_id: u32, effective_user_id: u32), (group_id: u32, effective_group_id: u32))) = |
354 | get_uid_and_gid(file_path:path.join("status" )) |
355 | { |
356 | p.user_id = Some(Uid(user_id)); |
357 | p.effective_user_id = Some(Uid(effective_user_id)); |
358 | p.group_id = Some(Gid(group_id)); |
359 | p.effective_group_id = Some(Gid(effective_group_id)); |
360 | } |
361 | } |
362 | |
363 | fn retrieve_all_new_process_info( |
364 | pid: Pid, |
365 | proc_list: &Process, |
366 | parts: &[&str], |
367 | path: &Path, |
368 | info: &SystemInfo, |
369 | refresh_kind: ProcessRefreshKind, |
370 | uptime: u64, |
371 | ) -> Process { |
372 | let mut p = Process::new(pid); |
373 | let mut tmp = PathHandler::new(path); |
374 | let name = parts[1]; |
375 | |
376 | p.parent = if proc_list.pid.0 != 0 { |
377 | Some(proc_list.pid) |
378 | } else { |
379 | match Pid::from_str(parts[3]) { |
380 | Ok(p) if p.0 != 0 => Some(p), |
381 | _ => None, |
382 | } |
383 | }; |
384 | |
385 | p.start_time_without_boot_time = compute_start_time_without_boot_time(parts, info); |
386 | p.start_time = p |
387 | .start_time_without_boot_time |
388 | .saturating_add(info.boot_time); |
389 | |
390 | get_status(&mut p, parts[2]); |
391 | |
392 | if refresh_kind.user() { |
393 | refresh_user_group_ids(&mut p, &mut tmp); |
394 | } |
395 | |
396 | p.name = name.into(); |
397 | |
398 | match tmp.join("exe" ).read_link() { |
399 | Ok(exe_path) => { |
400 | p.exe = exe_path; |
401 | } |
402 | Err(_) => { |
403 | // Do not use cmd[0] because it is not the same thing. |
404 | // See https://github.com/GuillaumeGomez/sysinfo/issues/697. |
405 | p.exe = PathBuf::new() |
406 | } |
407 | } |
408 | |
409 | p.cmd = copy_from_file(tmp.join("cmdline" )); |
410 | p.environ = copy_from_file(tmp.join("environ" )); |
411 | p.cwd = realpath(tmp.join("cwd" )); |
412 | p.root = realpath(tmp.join("root" )); |
413 | |
414 | update_time_and_memory( |
415 | path, |
416 | &mut p, |
417 | parts, |
418 | proc_list.memory, |
419 | proc_list.virtual_memory, |
420 | uptime, |
421 | info, |
422 | refresh_kind, |
423 | ); |
424 | if refresh_kind.disk_usage() { |
425 | update_process_disk_activity(&mut p, path); |
426 | } |
427 | p |
428 | } |
429 | |
430 | pub(crate) fn _get_process_data( |
431 | path: &Path, |
432 | proc_list: &mut Process, |
433 | pid: Pid, |
434 | uptime: u64, |
435 | info: &SystemInfo, |
436 | refresh_kind: ProcessRefreshKind, |
437 | ) -> Result<(Option<Process>, Pid), ()> { |
438 | let pid = match path.file_name().and_then(|x| x.to_str()).map(Pid::from_str) { |
439 | // If `pid` and `nb` are the same, it means the file is linking to itself so we skip it. |
440 | // |
441 | // It's because when reading `/proc/[PID]` folder, we then go through the folders inside it. |
442 | // Then, if we encounter a sub-folder with the same PID as the parent, then it's a link to |
443 | // the current folder we already did read so no need to do anything. |
444 | Some(Ok(nb)) if nb != pid => nb, |
445 | _ => return Err(()), |
446 | }; |
447 | |
448 | let parent_memory = proc_list.memory; |
449 | let parent_virtual_memory = proc_list.virtual_memory; |
450 | |
451 | let data; |
452 | let parts = if let Some(ref mut entry) = proc_list.tasks.get_mut(&pid) { |
453 | data = if let Some(mut f) = entry.stat_file.take() { |
454 | match get_all_data_from_file(&mut f, 1024) { |
455 | Ok(data) => { |
456 | // Everything went fine, we put back the file descriptor. |
457 | entry.stat_file = Some(f); |
458 | data |
459 | } |
460 | Err(_) => { |
461 | // It's possible that the file descriptor is no longer valid in case the |
462 | // original process was terminated and another one took its place. |
463 | _get_stat_data(path, &mut entry.stat_file)? |
464 | } |
465 | } |
466 | } else { |
467 | _get_stat_data(path, &mut entry.stat_file)? |
468 | }; |
469 | let parts = parse_stat_file(&data).ok_or(())?; |
470 | let start_time_without_boot_time = compute_start_time_without_boot_time(&parts, info); |
471 | |
472 | // It's possible that a new process took this same PID when the "original one" terminated. |
473 | // If the start time differs, then it means it's not the same process anymore and that we |
474 | // need to get all its information, hence why we check it here. |
475 | if start_time_without_boot_time == entry.start_time_without_boot_time { |
476 | get_status(entry, parts[2]); |
477 | update_time_and_memory( |
478 | path, |
479 | entry, |
480 | &parts, |
481 | parent_memory, |
482 | parent_virtual_memory, |
483 | uptime, |
484 | info, |
485 | refresh_kind, |
486 | ); |
487 | if refresh_kind.disk_usage() { |
488 | update_process_disk_activity(entry, path); |
489 | } |
490 | if refresh_kind.user() && entry.user_id.is_none() { |
491 | refresh_user_group_ids(entry, &mut PathBuf::from(path)); |
492 | } |
493 | return Ok((None, pid)); |
494 | } |
495 | parts |
496 | } else { |
497 | let mut stat_file = None; |
498 | let data = _get_stat_data(path, &mut stat_file)?; |
499 | let parts = parse_stat_file(&data).ok_or(())?; |
500 | |
501 | let mut p = |
502 | retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime); |
503 | p.stat_file = stat_file; |
504 | return Ok((Some(p), pid)); |
505 | }; |
506 | |
507 | // If we're here, it means that the PID still exists but it's a different process. |
508 | let p = retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime); |
509 | match proc_list.tasks.get_mut(&pid) { |
510 | Some(ref mut entry) => **entry = p, |
511 | // If it ever enters this case, it means that the process was removed from the HashMap |
512 | // in-between with the usage of dark magic. |
513 | None => unreachable!(), |
514 | } |
515 | // Since this PID is already in the HashMap, no need to add it again. |
516 | Ok((None, pid)) |
517 | } |
518 | |
519 | #[allow (clippy::too_many_arguments)] |
520 | fn update_time_and_memory( |
521 | path: &Path, |
522 | entry: &mut Process, |
523 | parts: &[&str], |
524 | parent_memory: u64, |
525 | parent_virtual_memory: u64, |
526 | uptime: u64, |
527 | info: &SystemInfo, |
528 | refresh_kind: ProcessRefreshKind, |
529 | ) { |
530 | { |
531 | // rss |
532 | entry.memory = u64::from_str(parts[23]) |
533 | .unwrap_or(0) |
534 | .saturating_mul(info.page_size_kb); |
535 | if entry.memory >= parent_memory { |
536 | entry.memory -= parent_memory; |
537 | } |
538 | // vsz correspond to the Virtual memory size in bytes. |
539 | // see: https://man7.org/linux/man-pages/man5/proc.5.html |
540 | entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0); |
541 | if entry.virtual_memory >= parent_virtual_memory { |
542 | entry.virtual_memory -= parent_virtual_memory; |
543 | } |
544 | set_time( |
545 | entry, |
546 | u64::from_str(parts[13]).unwrap_or(0), |
547 | u64::from_str(parts[14]).unwrap_or(0), |
548 | ); |
549 | entry.run_time = uptime.saturating_sub(entry.start_time_without_boot_time); |
550 | } |
551 | refresh_procs( |
552 | entry, |
553 | &path.join("task" ), |
554 | entry.pid, |
555 | uptime, |
556 | info, |
557 | refresh_kind, |
558 | ); |
559 | } |
560 | |
561 | pub(crate) fn refresh_procs( |
562 | proc_list: &mut Process, |
563 | path: &Path, |
564 | pid: Pid, |
565 | uptime: u64, |
566 | info: &SystemInfo, |
567 | refresh_kind: ProcessRefreshKind, |
568 | ) -> bool { |
569 | let d = match fs::read_dir(path) { |
570 | Ok(d) => d, |
571 | Err(_) => return false, |
572 | }; |
573 | let folders = d |
574 | .filter_map(|entry| { |
575 | let entry = entry.ok()?; |
576 | let entry = entry.path(); |
577 | |
578 | if entry.is_dir() { |
579 | Some(entry) |
580 | } else { |
581 | None |
582 | } |
583 | }) |
584 | .collect::<Vec<_>>(); |
585 | if pid.0 == 0 { |
586 | let proc_list = Wrap(UnsafeCell::new(proc_list)); |
587 | |
588 | #[cfg (feature = "multithread" )] |
589 | use rayon::iter::ParallelIterator; |
590 | |
591 | into_iter(folders) |
592 | .filter_map(|e| { |
593 | let (p, _) = _get_process_data( |
594 | e.as_path(), |
595 | proc_list.get(), |
596 | pid, |
597 | uptime, |
598 | info, |
599 | refresh_kind, |
600 | ) |
601 | .ok()?; |
602 | p |
603 | }) |
604 | .collect::<Vec<_>>() |
605 | } else { |
606 | let mut updated_pids = Vec::with_capacity(folders.len()); |
607 | let new_tasks = folders |
608 | .iter() |
609 | .filter_map(|e| { |
610 | let (p, pid) = |
611 | _get_process_data(e.as_path(), proc_list, pid, uptime, info, refresh_kind) |
612 | .ok()?; |
613 | updated_pids.push(pid); |
614 | p |
615 | }) |
616 | .collect::<Vec<_>>(); |
617 | // Sub-tasks are not cleaned up outside so we do it here directly. |
618 | proc_list |
619 | .tasks |
620 | .retain(|&pid, _| updated_pids.iter().any(|&x| x == pid)); |
621 | new_tasks |
622 | } |
623 | .into_iter() |
624 | .for_each(|e| { |
625 | proc_list.tasks.insert(e.pid(), e); |
626 | }); |
627 | true |
628 | } |
629 | |
630 | fn copy_from_file(entry: &Path) -> Vec<String> { |
631 | match File::open(entry) { |
632 | Ok(mut f) => { |
633 | let mut data = Vec::with_capacity(16_384); |
634 | |
635 | if let Err(_e) = f.read_to_end(&mut data) { |
636 | sysinfo_debug!("Failed to read file in `copy_from_file`: {:?}" , _e); |
637 | Vec::new() |
638 | } else { |
639 | let mut out = Vec::with_capacity(20); |
640 | let mut start = 0; |
641 | for (pos, x) in data.iter().enumerate() { |
642 | if *x == 0 { |
643 | if pos - start >= 1 { |
644 | if let Ok(s) = |
645 | std::str::from_utf8(&data[start..pos]).map(|x| x.trim().to_owned()) |
646 | { |
647 | out.push(s); |
648 | } |
649 | } |
650 | start = pos + 1; // to keeping prevent '\0' |
651 | } |
652 | } |
653 | out |
654 | } |
655 | } |
656 | Err(_e) => { |
657 | sysinfo_debug!("Failed to open file in `copy_from_file`: {:?}" , _e); |
658 | Vec::new() |
659 | } |
660 | } |
661 | } |
662 | |
663 | // Fetch tuples of real and effective UID and GID. |
664 | fn get_uid_and_gid(file_path: &Path) -> Option<((uid_t, uid_t), (gid_t, gid_t))> { |
665 | let status_data = get_all_data(file_path, 16_385).ok()?; |
666 | |
667 | // We're only interested in the lines starting with Uid: and Gid: |
668 | // here. From these lines, we're looking at the first and second entries to get |
669 | // the real u/gid. |
670 | |
671 | let f = |h: &str, n: &str| -> (Option<uid_t>, Option<uid_t>) { |
672 | if h.starts_with(n) { |
673 | let mut ids = h.split_whitespace(); |
674 | let real = ids.nth(1).unwrap_or("0" ).parse().ok(); |
675 | let effective = ids.next().unwrap_or("0" ).parse().ok(); |
676 | |
677 | (real, effective) |
678 | } else { |
679 | (None, None) |
680 | } |
681 | }; |
682 | let mut uid = None; |
683 | let mut effective_uid = None; |
684 | let mut gid = None; |
685 | let mut effective_gid = None; |
686 | for line in status_data.lines() { |
687 | if let (Some(real), Some(effective)) = f(line, "Uid:" ) { |
688 | debug_assert!(uid.is_none() && effective_uid.is_none()); |
689 | uid = Some(real); |
690 | effective_uid = Some(effective); |
691 | } else if let (Some(real), Some(effective)) = f(line, "Gid:" ) { |
692 | debug_assert!(gid.is_none() && effective_gid.is_none()); |
693 | gid = Some(real); |
694 | effective_gid = Some(effective); |
695 | } else { |
696 | continue; |
697 | } |
698 | if uid.is_some() && gid.is_some() { |
699 | break; |
700 | } |
701 | } |
702 | match (uid, effective_uid, gid, effective_gid) { |
703 | (Some(uid), Some(effective_uid), Some(gid), Some(effective_gid)) => { |
704 | Some(((uid, effective_uid), (gid, effective_gid))) |
705 | } |
706 | _ => None, |
707 | } |
708 | } |
709 | |
710 | fn parse_stat_file(data: &str) -> Option<Vec<&str>> { |
711 | // The stat file is "interesting" to parse, because spaces cannot |
712 | // be used as delimiters. The second field stores the command name |
713 | // surrounded by parentheses. Unfortunately, whitespace and |
714 | // parentheses are legal parts of the command, so parsing has to |
715 | // proceed like this: The first field is delimited by the first |
716 | // whitespace, the second field is everything until the last ')' |
717 | // in the entire string. All other fields are delimited by |
718 | // whitespace. |
719 | |
720 | let mut parts: Vec<&str> = Vec::with_capacity(52); |
721 | let mut data_it: SplitN<'_, char> = data.splitn(n:2, pat:' ' ); |
722 | parts.push(data_it.next()?); |
723 | let mut data_it: RSplitN<'_, char> = data_it.next()?.rsplitn(n:2, pat:')' ); |
724 | let data: &str = data_it.next()?; |
725 | parts.push(data_it.next()?); |
726 | parts.extend(iter:data.split_whitespace()); |
727 | // Remove command name '(' |
728 | if let Some(name: &str) = parts[1].strip_prefix('(' ) { |
729 | parts[1] = name; |
730 | } |
731 | Some(parts) |
732 | } |
733 | |