1use std::cell::UnsafeCell;
4use std::collections::{HashMap, HashSet};
5use std::ffi::{OsStr, OsString};
6use std::fmt;
7use std::fs::{self, DirEntry, File, read_dir};
8use std::io::Read;
9use std::os::unix::ffi::OsStrExt;
10use std::path::{Path, PathBuf};
11use std::process::ExitStatus;
12use std::str::{self, FromStr};
13use std::sync::atomic::{AtomicUsize, Ordering};
14
15use libc::{c_ulong, gid_t, uid_t};
16
17use crate::sys::system::SystemInfo;
18use crate::sys::utils::{PathHandler, PathPush, get_all_data_from_file, get_all_utf8_data};
19use crate::unix::utils::realpath;
20use crate::{
21 DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, ProcessesToUpdate, Signal,
22 ThreadKind, Uid,
23};
24
25use crate::sys::system::remaining_files;
26
27#[doc(hidden)]
28impl From<char> for ProcessStatus {
29 fn from(status: char) -> ProcessStatus {
30 match status {
31 'R' => ProcessStatus::Run,
32 'S' => ProcessStatus::Sleep,
33 'I' => ProcessStatus::Idle,
34 'D' => ProcessStatus::UninterruptibleDiskSleep,
35 'Z' => ProcessStatus::Zombie,
36 'T' => ProcessStatus::Stop,
37 't' => ProcessStatus::Tracing,
38 'X' | 'x' => ProcessStatus::Dead,
39 'K' => ProcessStatus::Wakekill,
40 'W' => ProcessStatus::Waking,
41 'P' => ProcessStatus::Parked,
42 x => ProcessStatus::Unknown(x as u32),
43 }
44 }
45}
46
47impl fmt::Display for ProcessStatus {
48 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49 f.write_str(match *self {
50 ProcessStatus::Idle => "Idle",
51 ProcessStatus::Run => "Runnable",
52 ProcessStatus::Sleep => "Sleeping",
53 ProcessStatus::Stop => "Stopped",
54 ProcessStatus::Zombie => "Zombie",
55 ProcessStatus::Tracing => "Tracing",
56 ProcessStatus::Dead => "Dead",
57 ProcessStatus::Wakekill => "Wakekill",
58 ProcessStatus::Waking => "Waking",
59 ProcessStatus::Parked => "Parked",
60 ProcessStatus::UninterruptibleDiskSleep => "UninterruptibleDiskSleep",
61 _ => "Unknown",
62 })
63 }
64}
65
66#[allow(dead_code)]
67#[repr(usize)]
68enum ProcIndex {
69 Pid = 0,
70 State,
71 ParentPid,
72 GroupId,
73 SessionId,
74 Tty,
75 ForegroundProcessGroupId,
76 Flags,
77 MinorFaults,
78 ChildrenMinorFaults,
79 MajorFaults,
80 ChildrenMajorFaults,
81 UserTime,
82 SystemTime,
83 ChildrenUserTime,
84 ChildrenKernelTime,
85 Priority,
86 Nice,
87 NumberOfThreads,
88 IntervalTimerSigalarm,
89 StartTime,
90 VirtualSize,
91 ResidentSetSize,
92 }
94
95pub(crate) struct ProcessInner {
96 pub(crate) name: OsString,
97 pub(crate) cmd: Vec<OsString>,
98 pub(crate) exe: Option<PathBuf>,
99 pub(crate) pid: Pid,
100 parent: Option<Pid>,
101 pub(crate) environ: Vec<OsString>,
102 pub(crate) cwd: Option<PathBuf>,
103 pub(crate) root: Option<PathBuf>,
104 pub(crate) memory: u64,
105 pub(crate) virtual_memory: u64,
106 utime: u64,
107 stime: u64,
108 old_utime: u64,
109 old_stime: u64,
110 start_time_without_boot_time: u64,
111 start_time: u64,
112 start_time_raw: u64,
113 run_time: u64,
114 pub(crate) updated: bool,
115 cpu_usage: f32,
116 user_id: Option<Uid>,
117 effective_user_id: Option<Uid>,
118 group_id: Option<Gid>,
119 effective_group_id: Option<Gid>,
120 pub(crate) status: ProcessStatus,
121 pub(crate) tasks: Option<HashSet<Pid>>,
122 stat_file: Option<FileCounter>,
123 old_read_bytes: u64,
124 old_written_bytes: u64,
125 read_bytes: u64,
126 written_bytes: u64,
127 thread_kind: Option<ThreadKind>,
128 proc_path: PathBuf,
129 accumulated_cpu_time: u64,
130 exists: bool,
131}
132
133impl ProcessInner {
134 pub(crate) fn new(pid: Pid, proc_path: PathBuf) -> Self {
135 Self {
136 name: OsString::new(),
137 pid,
138 parent: None,
139 cmd: Vec::new(),
140 environ: Vec::new(),
141 exe: None,
142 cwd: None,
143 root: None,
144 memory: 0,
145 virtual_memory: 0,
146 cpu_usage: 0.,
147 utime: 0,
148 stime: 0,
149 old_utime: 0,
150 old_stime: 0,
151 updated: true,
152 start_time_without_boot_time: 0,
153 start_time: 0,
154 start_time_raw: 0,
155 run_time: 0,
156 user_id: None,
157 effective_user_id: None,
158 group_id: None,
159 effective_group_id: None,
160 status: ProcessStatus::Unknown(0),
161 tasks: None,
162 stat_file: None,
163 old_read_bytes: 0,
164 old_written_bytes: 0,
165 read_bytes: 0,
166 written_bytes: 0,
167 thread_kind: None,
168 proc_path,
169 accumulated_cpu_time: 0,
170 exists: true,
171 }
172 }
173
174 pub(crate) fn kill_with(&self, signal: Signal) -> Option<bool> {
175 let c_signal = crate::sys::system::convert_signal(signal)?;
176 unsafe { Some(libc::kill(self.pid.0, c_signal) == 0) }
177 }
178
179 pub(crate) fn name(&self) -> &OsStr {
180 &self.name
181 }
182
183 pub(crate) fn cmd(&self) -> &[OsString] {
184 &self.cmd
185 }
186
187 pub(crate) fn exe(&self) -> Option<&Path> {
188 self.exe.as_deref()
189 }
190
191 pub(crate) fn pid(&self) -> Pid {
192 self.pid
193 }
194
195 pub(crate) fn environ(&self) -> &[OsString] {
196 &self.environ
197 }
198
199 pub(crate) fn cwd(&self) -> Option<&Path> {
200 self.cwd.as_deref()
201 }
202
203 pub(crate) fn root(&self) -> Option<&Path> {
204 self.root.as_deref()
205 }
206
207 pub(crate) fn memory(&self) -> u64 {
208 self.memory
209 }
210
211 pub(crate) fn virtual_memory(&self) -> u64 {
212 self.virtual_memory
213 }
214
215 pub(crate) fn parent(&self) -> Option<Pid> {
216 self.parent
217 }
218
219 pub(crate) fn status(&self) -> ProcessStatus {
220 self.status
221 }
222
223 pub(crate) fn start_time(&self) -> u64 {
224 self.start_time
225 }
226
227 pub(crate) fn run_time(&self) -> u64 {
228 self.run_time
229 }
230
231 pub(crate) fn cpu_usage(&self) -> f32 {
232 self.cpu_usage
233 }
234
235 pub(crate) fn accumulated_cpu_time(&self) -> u64 {
236 self.accumulated_cpu_time
237 }
238
239 pub(crate) fn disk_usage(&self) -> DiskUsage {
240 DiskUsage {
241 written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
242 total_written_bytes: self.written_bytes,
243 read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
244 total_read_bytes: self.read_bytes,
245 }
246 }
247
248 pub(crate) fn user_id(&self) -> Option<&Uid> {
249 self.user_id.as_ref()
250 }
251
252 pub(crate) fn effective_user_id(&self) -> Option<&Uid> {
253 self.effective_user_id.as_ref()
254 }
255
256 pub(crate) fn group_id(&self) -> Option<Gid> {
257 self.group_id
258 }
259
260 pub(crate) fn effective_group_id(&self) -> Option<Gid> {
261 self.effective_group_id
262 }
263
264 pub(crate) fn wait(&self) -> Option<ExitStatus> {
265 let (data, _) = _get_stat_data_and_file(&self.proc_path).ok()?;
267 let parts = parse_stat_file(&data)?;
268
269 if start_time_raw(&parts) != self.start_time_raw {
270 sysinfo_debug!("Seems to not be the same process anymore");
271 return None;
272 }
273
274 crate::unix::utils::wait_process(self.pid)
275 }
276
277 pub(crate) fn session_id(&self) -> Option<Pid> {
278 unsafe {
279 let session_id = libc::getsid(self.pid.0);
280 if session_id < 0 {
281 None
282 } else {
283 Some(Pid(session_id))
284 }
285 }
286 }
287
288 pub(crate) fn thread_kind(&self) -> Option<ThreadKind> {
289 self.thread_kind
290 }
291
292 pub(crate) fn switch_updated(&mut self) -> bool {
293 std::mem::replace(&mut self.updated, false)
294 }
295
296 pub(crate) fn set_nonexistent(&mut self) {
297 self.exists = false;
298 }
299
300 pub(crate) fn exists(&self) -> bool {
301 self.exists
302 }
303
304 pub(crate) fn open_files(&self) -> Option<usize> {
305 let open_files_dir = self.proc_path.as_path().join("fd");
306 match fs::read_dir(&open_files_dir) {
307 Ok(entries) => Some(entries.count() as _),
308 Err(_error) => {
309 sysinfo_debug!(
310 "Failed to get open files in `{}`: {_error:?}",
311 open_files_dir.display(),
312 );
313 None
314 }
315 }
316 }
317
318 pub(crate) fn open_files_limit(&self) -> Option<usize> {
319 let limits_files = self.proc_path.as_path().join("limits");
320 match fs::read_to_string(&limits_files) {
321 Ok(content) => {
322 for line in content.lines() {
323 if let Some(line) = line.strip_prefix("Max open files ")
324 && let Some(nb) = line.split_whitespace().find(|p| !p.is_empty())
325 {
326 return usize::from_str(nb).ok();
327 }
328 }
329 None
330 }
331 Err(_error) => {
332 sysinfo_debug!(
333 "Failed to get limits in `{}`: {_error:?}",
334 limits_files.display()
335 );
336 None
337 }
338 }
339 }
340}
341
342pub(crate) fn compute_cpu_usage(p: &mut ProcessInner, total_time: f32, max_value: f32) {
343 if p.old_utime == 0 && p.old_stime == 0 {
345 return;
346 }
347
348 p.cpu_usage = (p
351 .utime
352 .saturating_sub(p.old_utime)
353 .saturating_add(p.stime.saturating_sub(p.old_stime)) as f32
354 / total_time
355 * 100.)
356 .min(max_value);
357}
358
359pub(crate) fn set_time(p: &mut ProcessInner, utime: u64, stime: u64) {
360 p.old_utime = p.utime;
361 p.old_stime = p.stime;
362 p.utime = utime;
363 p.stime = stime;
364}
365
366pub(crate) fn update_process_disk_activity(p: &mut ProcessInner, path: &mut PathHandler) {
367 let data = match get_all_utf8_data(path.replace_and_join("io"), 16_384) {
368 Ok(d) => d,
369 Err(_) => return,
370 };
371 let mut done = 0;
372 for line in data.split('\n') {
373 let mut parts = line.split(": ");
374 match parts.next() {
375 Some("read_bytes") => {
376 p.old_read_bytes = p.read_bytes;
377 p.read_bytes = parts
378 .next()
379 .and_then(|x| x.parse::<u64>().ok())
380 .unwrap_or(p.old_read_bytes);
381 }
382 Some("write_bytes") => {
383 p.old_written_bytes = p.written_bytes;
384 p.written_bytes = parts
385 .next()
386 .and_then(|x| x.parse::<u64>().ok())
387 .unwrap_or(p.old_written_bytes);
388 }
389 _ => continue,
390 }
391 done += 1;
392 if done > 1 {
393 break;
395 }
396 }
397}
398
399struct Wrap<'a, T>(UnsafeCell<&'a mut T>);
400
401impl<'a, T> Wrap<'a, T> {
402 fn get(&self) -> &'a mut T {
403 unsafe { *(self.0.get()) }
404 }
405}
406
407#[allow(clippy::non_send_fields_in_send_ty)]
408unsafe impl<T> Send for Wrap<'_, T> {}
409unsafe impl<T> Sync for Wrap<'_, T> {}
410
411#[inline(always)]
412fn start_time_raw(parts: &Parts<'_>) -> u64 {
413 u64::from_str(parts.str_parts[ProcIndex::StartTime as usize]).unwrap_or(0)
414}
415
416#[inline(always)]
417fn compute_start_time_without_boot_time(parts: &Parts<'_>, info: &SystemInfo) -> (u64, u64) {
418 let raw = start_time_raw(parts);
419 (raw, raw / info.clock_cycle)
422}
423
424fn _get_stat_data_and_file(path: &Path) -> Result<(Vec<u8>, File), ()> {
425 let mut file = File::open(path.join("stat")).map_err(|_| ())?;
426 let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?;
427 Ok((data, file))
428}
429
430fn _get_stat_data(path: &Path, stat_file: &mut Option<FileCounter>) -> Result<Vec<u8>, ()> {
431 let (data, file) = _get_stat_data_and_file(path)?;
432 *stat_file = FileCounter::new(file);
433 Ok(data)
434}
435
436#[inline(always)]
437fn get_status(p: &mut ProcessInner, part: &str) {
438 p.status = part
439 .chars()
440 .next()
441 .map(ProcessStatus::from)
442 .unwrap_or_else(|| ProcessStatus::Unknown(0));
443}
444
445fn refresh_user_group_ids(
446 p: &mut ProcessInner,
447 path: &mut PathHandler,
448 refresh_kind: ProcessRefreshKind,
449) {
450 if !refresh_kind.user().needs_update(|| p.user_id.is_none()) {
451 return;
452 }
453
454 if let Some(((user_id, effective_user_id), (group_id, effective_group_id))) =
455 get_uid_and_gid(path.replace_and_join("status"))
456 {
457 p.user_id = Some(Uid(user_id));
458 p.effective_user_id = Some(Uid(effective_user_id));
459 p.group_id = Some(Gid(group_id));
460 p.effective_group_id = Some(Gid(effective_group_id));
461 }
462}
463
464#[allow(clippy::too_many_arguments)]
465fn update_proc_info(
466 p: &mut ProcessInner,
467 parent_pid: Option<Pid>,
468 refresh_kind: ProcessRefreshKind,
469 proc_path: &mut PathHandler,
470 str_parts: &[&str],
471 uptime: u64,
472 info: &SystemInfo,
473) {
474 update_parent_pid(p, parent_pid, str_parts);
475
476 get_status(p, str_parts[ProcIndex::State as usize]);
477 refresh_user_group_ids(p, proc_path, refresh_kind);
478
479 if refresh_kind.exe().needs_update(|| p.exe.is_none()) {
480 p.exe = realpath(proc_path.replace_and_join("exe"));
483 let deleted = b" (deleted)";
487 if let Some(exe) = &mut p.exe
488 && let Some(file_name) = exe.file_name()
489 && file_name.as_encoded_bytes().ends_with(deleted)
490 {
491 let mut file_name = file_name.as_encoded_bytes().to_vec();
492 file_name.truncate(file_name.len() - deleted.len());
493 unsafe {
494 exe.set_file_name(OsString::from_encoded_bytes_unchecked(file_name));
495 }
496 }
497 }
498
499 if refresh_kind.cmd().needs_update(|| p.cmd.is_empty()) {
500 p.cmd = copy_from_file(proc_path.replace_and_join("cmdline"));
501 }
502 if refresh_kind.environ().needs_update(|| p.environ.is_empty()) {
503 p.environ = copy_from_file(proc_path.replace_and_join("environ"));
504 }
505 if refresh_kind.cwd().needs_update(|| p.cwd.is_none()) {
506 p.cwd = realpath(proc_path.replace_and_join("cwd"));
507 }
508 if refresh_kind.root().needs_update(|| p.root.is_none()) {
509 p.root = realpath(proc_path.replace_and_join("root"));
510 }
511
512 update_time_and_memory(proc_path, p, str_parts, uptime, info, refresh_kind);
513 if refresh_kind.disk_usage() {
514 update_process_disk_activity(p, proc_path);
515 }
516 if refresh_kind.cpu() {
518 p.accumulated_cpu_time =
521 p.utime.saturating_add(p.stime).saturating_mul(1_000) / info.clock_cycle;
522 }
523 p.updated = true;
524}
525
526fn update_parent_pid(p: &mut ProcessInner, parent_pid: Option<Pid>, str_parts: &[&str]) {
527 p.parent = match parent_pid {
528 Some(parent_pid) if parent_pid.0 != 0 => Some(parent_pid),
529 _ => match Pid::from_str(str_parts[ProcIndex::ParentPid as usize]) {
530 Ok(p) if p.0 != 0 => Some(p),
531 _ => None,
532 },
533 };
534}
535
536fn retrieve_all_new_process_info(
537 pid: Pid,
538 parent_pid: Option<Pid>,
539 parts: &Parts<'_>,
540 path: &Path,
541 info: &SystemInfo,
542 refresh_kind: ProcessRefreshKind,
543 uptime: u64,
544) -> Process {
545 let mut p = ProcessInner::new(pid, path.to_owned());
546 let mut proc_path = PathHandler::new(path);
547 let name = parts.short_exe;
548
549 let (start_time_raw, start_time_without_boot_time) =
550 compute_start_time_without_boot_time(parts, info);
551 p.start_time_raw = start_time_raw;
552 p.start_time_without_boot_time = start_time_without_boot_time;
553 p.start_time = p
554 .start_time_without_boot_time
555 .saturating_add(info.boot_time);
556
557 p.name = OsStr::from_bytes(name).to_os_string();
558 if c_ulong::from_str(parts.str_parts[ProcIndex::Flags as usize])
559 .map(|flags| flags & libc::PF_KTHREAD as c_ulong != 0)
560 .unwrap_or(false)
561 {
562 p.thread_kind = Some(ThreadKind::Kernel);
563 } else if parent_pid.is_some() {
564 p.thread_kind = Some(ThreadKind::Userland);
565 }
566
567 update_proc_info(
568 &mut p,
569 parent_pid,
570 refresh_kind,
571 &mut proc_path,
572 &parts.str_parts,
573 uptime,
574 info,
575 );
576
577 Process { inner: p }
578}
579
580fn update_existing_process(
581 proc: &mut Process,
582 parent_pid: Option<Pid>,
583 uptime: u64,
584 info: &SystemInfo,
585 refresh_kind: ProcessRefreshKind,
586 tasks: Option<HashSet<Pid>>,
587) -> Result<Option<Process>, ()> {
588 let entry = &mut proc.inner;
589 let data = if let Some(mut f) = entry.stat_file.take() {
590 match get_all_data_from_file(&mut f, 1024) {
591 Ok(data) => {
592 entry.stat_file = Some(f);
594 data
595 }
596 Err(_) => {
597 _get_stat_data(&entry.proc_path, &mut entry.stat_file)?
600 }
601 }
602 } else {
603 _get_stat_data(&entry.proc_path, &mut entry.stat_file)?
604 };
605 entry.tasks = tasks;
606
607 let parts = parse_stat_file(&data).ok_or(())?;
608 let start_time_raw = start_time_raw(&parts);
609
610 if start_time_raw == entry.start_time_raw {
614 let mut proc_path = PathHandler::new(&entry.proc_path);
615
616 update_proc_info(
617 entry,
618 parent_pid,
619 refresh_kind,
620 &mut proc_path,
621 &parts.str_parts,
622 uptime,
623 info,
624 );
625
626 refresh_user_group_ids(entry, &mut proc_path, refresh_kind);
627 return Ok(None);
628 }
629 let p = retrieve_all_new_process_info(
631 entry.pid,
632 parent_pid,
633 &parts,
634 &entry.proc_path,
635 info,
636 refresh_kind,
637 uptime,
638 );
639 *proc = p;
640 Ok(None)
642}
643
644#[allow(clippy::too_many_arguments)]
645pub(crate) fn _get_process_data(
646 path: &Path,
647 proc_list: &mut HashMap<Pid, Process>,
648 pid: Pid,
649 parent_pid: Option<Pid>,
650 uptime: u64,
651 info: &SystemInfo,
652 refresh_kind: ProcessRefreshKind,
653 tasks: Option<HashSet<Pid>>,
654) -> Result<Option<Process>, ()> {
655 if let Some(ref mut entry) = proc_list.get_mut(&pid) {
656 return update_existing_process(entry, parent_pid, uptime, info, refresh_kind, tasks);
657 }
658 let mut stat_file = None;
659 let data = _get_stat_data(path, &mut stat_file)?;
660 let parts = parse_stat_file(&data).ok_or(())?;
661
662 let mut new_process =
663 retrieve_all_new_process_info(pid, parent_pid, &parts, path, info, refresh_kind, uptime);
664 new_process.inner.stat_file = stat_file;
665 new_process.inner.tasks = tasks;
666 Ok(Some(new_process))
667}
668
669fn old_get_memory(entry: &mut ProcessInner, str_parts: &[&str], info: &SystemInfo) {
670 entry.memory = u64::from_str(str_parts[ProcIndex::ResidentSetSize as usize])
672 .unwrap_or(0)
673 .saturating_mul(info.page_size_b);
674 entry.virtual_memory = u64::from_str(str_parts[ProcIndex::VirtualSize as usize]).unwrap_or(0);
677}
678
679fn slice_to_nb(s: &[u8]) -> u64 {
680 let mut nb: u64 = 0;
681
682 for c in s {
683 nb = nb * 10 + (c - b'0') as u64;
684 }
685 nb
686}
687
688fn get_memory(path: &Path, entry: &mut ProcessInner, info: &SystemInfo) -> bool {
689 let mut file = match File::open(path) {
690 Ok(f) => f,
691 Err(_e) => {
692 sysinfo_debug!(
693 "Using old memory information (failed to open {:?}: {_e:?})",
694 path
695 );
696 return false;
697 }
698 };
699 let mut buf = Vec::new();
700 if let Err(_e) = file.read_to_end(&mut buf) {
701 sysinfo_debug!(
702 "Using old memory information (failed to read {:?}: {_e:?})",
703 path
704 );
705 return false;
706 }
707 let mut parts = buf.split(|c| *c == b' ');
708 entry.virtual_memory = parts
709 .next()
710 .map(slice_to_nb)
711 .unwrap_or(0)
712 .saturating_mul(info.page_size_b);
713 entry.memory = parts
714 .next()
715 .map(slice_to_nb)
716 .unwrap_or(0)
717 .saturating_mul(info.page_size_b);
718 true
719}
720
721#[allow(clippy::too_many_arguments)]
722fn update_time_and_memory(
723 path: &mut PathHandler,
724 entry: &mut ProcessInner,
725 str_parts: &[&str],
726 uptime: u64,
727 info: &SystemInfo,
728 refresh_kind: ProcessRefreshKind,
729) {
730 {
731 #[allow(clippy::collapsible_if)]
732 if refresh_kind.memory() {
733 if !get_memory(path.replace_and_join("statm"), entry, info) {
735 old_get_memory(entry, str_parts, info);
736 }
737 }
738 set_time(
739 entry,
740 u64::from_str(str_parts[ProcIndex::UserTime as usize]).unwrap_or(0),
741 u64::from_str(str_parts[ProcIndex::SystemTime as usize]).unwrap_or(0),
742 );
743 entry.run_time = uptime.saturating_sub(entry.start_time_without_boot_time);
744 }
745}
746
747struct ProcAndTasks {
748 pid: Pid,
749 parent_pid: Option<Pid>,
750 path: PathBuf,
751 tasks: Option<HashSet<Pid>>,
752}
753
754#[cfg(feature = "multithread")]
755#[inline]
756pub(crate) fn iter<T>(val: T) -> rayon::iter::IterBridge<T>
757where
758 T: rayon::iter::ParallelBridge,
759{
760 val.par_bridge()
761}
762
763#[cfg(not(feature = "multithread"))]
764#[inline]
765pub(crate) fn iter<T>(val: T) -> T
766where
767 T: Iterator,
768{
769 val
770}
771
772pub(crate) fn refresh_procs(
775 proc_list: &mut HashMap<Pid, Process>,
776 proc_path: &Path,
777 uptime: u64,
778 info: &SystemInfo,
779 processes_to_update: ProcessesToUpdate<'_>,
780 refresh_kind: ProcessRefreshKind,
781) -> usize {
782 #[cfg(feature = "multithread")]
783 use rayon::iter::ParallelIterator;
784
785 let nb_updated = AtomicUsize::new(0);
786
787 let procs = {
797 let pid_iter: Box<dyn Iterator<Item = (PathBuf, Pid)> + Send> = match processes_to_update {
798 ProcessesToUpdate::All => match read_dir(proc_path) {
799 Ok(proc_entries) => Box::new(proc_entries.filter_map(filter_pid_entries)),
800 Err(_err) => {
801 sysinfo_debug!("Failed to read folder {proc_path:?}: {_err:?}");
802 return 0;
803 }
804 },
805 ProcessesToUpdate::Some(pids) => Box::new(
806 pids.iter()
807 .map(|pid| (proc_path.join(pid.to_string()), *pid)),
808 ),
809 };
810
811 let proc_list = Wrap(UnsafeCell::new(proc_list));
812
813 iter(pid_iter)
814 .flat_map(|(path, pid)| {
815 get_proc_and_tasks(path, pid, refresh_kind, processes_to_update)
816 })
817 .filter_map(|e| {
818 let proc_list = proc_list.get();
819 let new_process = _get_process_data(
820 e.path.as_path(),
821 proc_list,
822 e.pid,
823 e.parent_pid,
824 uptime,
825 info,
826 refresh_kind,
827 e.tasks,
828 )
829 .ok()?;
830 nb_updated.fetch_add(1, Ordering::Relaxed);
831 new_process
832 })
833 .collect::<Vec<_>>()
834 };
835 for proc_ in procs {
836 proc_list.insert(proc_.pid(), proc_);
837 }
838 nb_updated.into_inner()
839}
840
841fn filter_pid_entries(entry: Result<DirEntry, std::io::Error>) -> Option<(PathBuf, Pid)> {
842 if let Ok(entry) = entry
843 && let Ok(file_type) = entry.file_type()
844 && file_type.is_dir()
845 && let Some(name) = entry.file_name().to_str()
846 && let Ok(pid) = usize::from_str(name)
847 {
848 Some((entry.path(), Pid::from(pid)))
849 } else {
850 None
851 }
852}
853
854fn get_proc_and_tasks(
855 path: PathBuf,
856 pid: Pid,
857 refresh_kind: ProcessRefreshKind,
858 processes_to_update: ProcessesToUpdate<'_>,
859) -> Vec<ProcAndTasks> {
860 let mut parent_pid = None;
861 let (mut procs, mut tasks) = if refresh_kind.tasks() {
862 let procs = get_proc_tasks(&path, pid);
863 let tasks = procs.iter().map(|ProcAndTasks { pid, .. }| *pid).collect();
864
865 (procs, Some(tasks))
866 } else {
867 (Vec::new(), None)
868 };
869
870 if processes_to_update != ProcessesToUpdate::All {
871 if let Some(tgid) = get_tgid(&path.join("status"))
873 && tgid != pid
874 {
875 parent_pid = Some(tgid);
876 tasks = None;
877 }
878
879 procs.clear();
881 }
882
883 procs.push(ProcAndTasks {
884 pid,
885 parent_pid,
886 path,
887 tasks,
888 });
889
890 procs
891}
892
893fn get_proc_tasks(path: &Path, parent_pid: Pid) -> Vec<ProcAndTasks> {
894 let task_path = path.join("task");
895
896 read_dir(task_path)
897 .ok()
898 .map(|task_entries| {
899 task_entries
900 .filter_map(filter_pid_entries)
901 .filter(|(_, pid)| *pid != parent_pid)
903 .map(|(path, pid)| ProcAndTasks {
904 pid,
905 path,
906 parent_pid: Some(parent_pid),
907 tasks: None,
908 })
909 .collect()
910 })
911 .unwrap_or_default()
912}
913
914fn split_content(mut data: &[u8]) -> Vec<OsString> {
915 let mut out = Vec::with_capacity(10);
916 while let Some(pos) = data.iter().position(|c| *c == 0) {
917 let s = &data[..pos].trim_ascii();
918 if !s.is_empty() {
919 out.push(OsStr::from_bytes(s).to_os_string());
920 }
921 data = &data[pos + 1..];
922 }
923 if !data.is_empty() {
924 let s = data.trim_ascii();
925 if !s.is_empty() {
926 out.push(OsStr::from_bytes(s).to_os_string());
927 }
928 }
929 out
930}
931
932fn copy_from_file(entry: &Path) -> Vec<OsString> {
933 match File::open(entry) {
934 Ok(mut f) => {
935 let mut data = Vec::with_capacity(16_384);
936
937 if let Err(_e) = f.read_to_end(&mut data) {
938 sysinfo_debug!("Failed to read file in `copy_from_file`: {:?}", _e);
939 Vec::new()
940 } else {
941 split_content(&data)
942 }
943 }
944 Err(_e) => {
945 sysinfo_debug!("Failed to open file in `copy_from_file`: {:?}", _e);
946 Vec::new()
947 }
948 }
949}
950
951fn get_uid_and_gid(file_path: &Path) -> Option<((uid_t, uid_t), (gid_t, gid_t))> {
953 let status_data = get_all_utf8_data(file_path, 16_385).ok()?;
954
955 let f = |h: &str, n: &str| -> (Option<uid_t>, Option<uid_t>) {
960 if h.starts_with(n) {
961 let mut ids = h.split_whitespace();
962 let real = ids.nth(1).unwrap_or("0").parse().ok();
963 let effective = ids.next().unwrap_or("0").parse().ok();
964
965 (real, effective)
966 } else {
967 (None, None)
968 }
969 };
970 let mut uid = None;
971 let mut effective_uid = None;
972 let mut gid = None;
973 let mut effective_gid = None;
974 for line in status_data.lines() {
975 if let (Some(real), Some(effective)) = f(line, "Uid:") {
976 debug_assert!(uid.is_none() && effective_uid.is_none());
977 uid = Some(real);
978 effective_uid = Some(effective);
979 } else if let (Some(real), Some(effective)) = f(line, "Gid:") {
980 debug_assert!(gid.is_none() && effective_gid.is_none());
981 gid = Some(real);
982 effective_gid = Some(effective);
983 } else {
984 continue;
985 }
986 if uid.is_some() && gid.is_some() {
987 break;
988 }
989 }
990 match (uid, effective_uid, gid, effective_gid) {
991 (Some(uid), Some(effective_uid), Some(gid), Some(effective_gid)) => {
992 Some(((uid, effective_uid), (gid, effective_gid)))
993 }
994 _ => None,
995 }
996}
997
998fn get_tgid(file_path: &Path) -> Option<Pid> {
999 const TGID_KEY: &str = "Tgid:";
1000 let status_data = get_all_utf8_data(file_path, 16_385).ok()?;
1001 let tgid_line = status_data
1002 .lines()
1003 .find(|line| line.starts_with(TGID_KEY))?;
1004 tgid_line[TGID_KEY.len()..].trim_start().parse().ok()
1005}
1006
1007struct Parts<'a> {
1008 str_parts: Vec<&'a str>,
1009 short_exe: &'a [u8],
1010}
1011
1012fn parse_stat_file(data: &[u8]) -> Option<Parts<'_>> {
1013 let mut str_parts = Vec::with_capacity(51);
1023 let mut data_it = data.splitn(2, |&b| b == b' ');
1024 str_parts.push(str::from_utf8(data_it.next()?).ok()?);
1025 let mut data_it = data_it.next()?.rsplitn(2, |&b| b == b')');
1026 let data = str::from_utf8(data_it.next()?).ok()?;
1027 let short_exe = data_it.next()?;
1028 str_parts.extend(data.split_whitespace());
1029 Some(Parts {
1030 str_parts,
1031 short_exe: short_exe.strip_prefix(b"(").unwrap_or(short_exe),
1032 })
1033}
1034
1035struct FileCounter(File);
1037
1038impl FileCounter {
1039 fn new(f: File) -> Option<Self> {
1040 let any_remaining =
1041 remaining_files().fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| {
1042 if remaining > 0 {
1043 Some(remaining - 1)
1044 } else {
1045 None
1047 }
1048 });
1049
1050 any_remaining.ok().map(|_| Self(f))
1051 }
1052}
1053
1054impl std::ops::Deref for FileCounter {
1055 type Target = File;
1056
1057 fn deref(&self) -> &Self::Target {
1058 &self.0
1059 }
1060}
1061impl std::ops::DerefMut for FileCounter {
1062 fn deref_mut(&mut self) -> &mut Self::Target {
1063 &mut self.0
1064 }
1065}
1066
1067impl Drop for FileCounter {
1068 fn drop(&mut self) {
1069 remaining_files().fetch_add(1, Ordering::Relaxed);
1070 }
1071}
1072
1073#[cfg(test)]
1074mod tests {
1075 use super::split_content;
1076 use std::ffi::OsString;
1077
1078 #[test]
1080 fn test_copy_file() {
1081 assert_eq!(split_content(b"hello\0"), vec![OsString::from("hello")]);
1082 assert_eq!(split_content(b"hello"), vec![OsString::from("hello")]);
1083 assert_eq!(
1084 split_content(b"hello\0b"),
1085 vec![OsString::from("hello"), "b".into()]
1086 );
1087 assert_eq!(
1088 split_content(b"hello\0\0\0\0b"),
1089 vec![OsString::from("hello"), "b".into()]
1090 );
1091 }
1092}