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 cgroup_limits(&self) -> Option<crate::CGroupLimits> {
196 crate::sys::cgroup::limits_for_process(&self.proc_path)
197 }
198
199 pub(crate) fn environ(&self) -> &[OsString] {
200 &self.environ
201 }
202
203 pub(crate) fn cwd(&self) -> Option<&Path> {
204 self.cwd.as_deref()
205 }
206
207 pub(crate) fn root(&self) -> Option<&Path> {
208 self.root.as_deref()
209 }
210
211 pub(crate) fn memory(&self) -> u64 {
212 self.memory
213 }
214
215 pub(crate) fn virtual_memory(&self) -> u64 {
216 self.virtual_memory
217 }
218
219 pub(crate) fn parent(&self) -> Option<Pid> {
220 self.parent
221 }
222
223 pub(crate) fn status(&self) -> ProcessStatus {
224 self.status
225 }
226
227 pub(crate) fn start_time(&self) -> u64 {
228 self.start_time
229 }
230
231 pub(crate) fn run_time(&self) -> u64 {
232 self.run_time
233 }
234
235 pub(crate) fn cpu_usage(&self) -> f32 {
236 self.cpu_usage
237 }
238
239 pub(crate) fn accumulated_cpu_time(&self) -> u64 {
240 self.accumulated_cpu_time
241 }
242
243 pub(crate) fn disk_usage(&self) -> DiskUsage {
244 DiskUsage {
245 written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
246 total_written_bytes: self.written_bytes,
247 read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
248 total_read_bytes: self.read_bytes,
249 }
250 }
251
252 pub(crate) fn user_id(&self) -> Option<&Uid> {
253 self.user_id.as_ref()
254 }
255
256 pub(crate) fn effective_user_id(&self) -> Option<&Uid> {
257 self.effective_user_id.as_ref()
258 }
259
260 pub(crate) fn group_id(&self) -> Option<Gid> {
261 self.group_id
262 }
263
264 pub(crate) fn effective_group_id(&self) -> Option<Gid> {
265 self.effective_group_id
266 }
267
268 pub(crate) fn wait(&self) -> Option<ExitStatus> {
269 let (data, _) = _get_stat_data_and_file(&self.proc_path).ok()?;
271 let parts = parse_stat_file(&data)?;
272
273 if start_time_raw(&parts) != self.start_time_raw {
274 sysinfo_debug!("Seems to not be the same process anymore");
275 return None;
276 }
277
278 crate::unix::utils::wait_process(self.pid)
279 }
280
281 pub(crate) fn session_id(&self) -> Option<Pid> {
282 unsafe {
283 let session_id = libc::getsid(self.pid.0);
284 if session_id < 0 {
285 None
286 } else {
287 Some(Pid(session_id))
288 }
289 }
290 }
291
292 pub(crate) fn thread_kind(&self) -> Option<ThreadKind> {
293 self.thread_kind
294 }
295
296 pub(crate) fn switch_updated(&mut self) -> bool {
297 std::mem::replace(&mut self.updated, false)
298 }
299
300 pub(crate) fn set_nonexistent(&mut self) {
301 self.exists = false;
302 }
303
304 pub(crate) fn exists(&self) -> bool {
305 self.exists
306 }
307
308 pub(crate) fn open_files(&self) -> Option<usize> {
309 let open_files_dir = self.proc_path.as_path().join("fd");
310 match fs::read_dir(&open_files_dir) {
311 Ok(entries) => Some(entries.count() as _),
312 Err(_error) => {
313 sysinfo_debug!(
314 "Failed to get open files in `{}`: {_error:?}",
315 open_files_dir.display(),
316 );
317 None
318 }
319 }
320 }
321
322 pub(crate) fn open_files_limit(&self) -> Option<usize> {
323 let limits_files = self.proc_path.as_path().join("limits");
324 match fs::read_to_string(&limits_files) {
325 Ok(content) => {
326 for line in content.lines() {
327 if let Some(line) = line.strip_prefix("Max open files ")
328 && let Some(nb) = line.split_whitespace().find(|p| !p.is_empty())
329 {
330 return usize::from_str(nb).ok();
331 }
332 }
333 None
334 }
335 Err(_error) => {
336 sysinfo_debug!(
337 "Failed to get limits in `{}`: {_error:?}",
338 limits_files.display()
339 );
340 None
341 }
342 }
343 }
344}
345
346pub(crate) fn compute_cpu_usage(p: &mut ProcessInner, total_time: f32, max_value: f32) {
347 if p.old_utime == 0 && p.old_stime == 0 {
349 return;
350 }
351
352 p.cpu_usage = (p
355 .utime
356 .saturating_sub(p.old_utime)
357 .saturating_add(p.stime.saturating_sub(p.old_stime)) as f32
358 / total_time
359 * 100.)
360 .min(max_value);
361}
362
363pub(crate) fn set_time(p: &mut ProcessInner, utime: u64, stime: u64) {
364 p.old_utime = p.utime;
365 p.old_stime = p.stime;
366 p.utime = utime;
367 p.stime = stime;
368}
369
370pub(crate) fn update_process_disk_activity(p: &mut ProcessInner, path: &mut PathHandler) {
371 let data = match get_all_utf8_data(path.replace_and_join("io"), 16_384) {
372 Ok(d) => d,
373 Err(_) => return,
374 };
375 let mut done = 0;
376 for line in data.split('\n') {
377 let mut parts = line.split(": ");
378 match parts.next() {
379 Some("read_bytes") => {
380 p.old_read_bytes = p.read_bytes;
381 p.read_bytes = parts
382 .next()
383 .and_then(|x| x.parse::<u64>().ok())
384 .unwrap_or(p.old_read_bytes);
385 }
386 Some("write_bytes") => {
387 p.old_written_bytes = p.written_bytes;
388 p.written_bytes = parts
389 .next()
390 .and_then(|x| x.parse::<u64>().ok())
391 .unwrap_or(p.old_written_bytes);
392 }
393 _ => continue,
394 }
395 done += 1;
396 if done > 1 {
397 break;
399 }
400 }
401}
402
403struct Wrap<'a, T>(UnsafeCell<&'a mut T>);
404
405impl<'a, T> Wrap<'a, T> {
406 fn get(&self) -> &'a mut T {
407 unsafe { *(self.0.get()) }
408 }
409}
410
411#[allow(clippy::non_send_fields_in_send_ty)]
412unsafe impl<T> Send for Wrap<'_, T> {}
413unsafe impl<T> Sync for Wrap<'_, T> {}
414
415#[inline(always)]
416fn start_time_raw(parts: &Parts<'_>) -> u64 {
417 parts
418 .str_parts
419 .get(ProcIndex::StartTime as usize)
420 .and_then(|part| u64::from_str(part).ok())
421 .unwrap_or(0)
422}
423
424#[inline(always)]
425fn compute_start_time_without_boot_time(parts: &Parts<'_>, info: &SystemInfo) -> (u64, u64) {
426 let raw = start_time_raw(parts);
427 (raw, raw / info.clock_cycle)
430}
431
432fn _get_stat_data_and_file(path: &Path) -> Result<(Vec<u8>, File), ()> {
433 let mut file = File::open(path.join("stat")).map_err(|_| ())?;
434 let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?;
435 Ok((data, file))
436}
437
438fn _get_stat_data(path: &Path, stat_file: &mut Option<FileCounter>) -> Result<Vec<u8>, ()> {
439 let (data, file) = _get_stat_data_and_file(path)?;
440 *stat_file = FileCounter::new(file);
441 Ok(data)
442}
443
444#[inline(always)]
445fn get_status(p: &mut ProcessInner, part: &str) {
446 p.status = part
447 .chars()
448 .next()
449 .map(ProcessStatus::from)
450 .unwrap_or_else(|| ProcessStatus::Unknown(0));
451}
452
453fn refresh_user_group_ids(
454 p: &mut ProcessInner,
455 path: &mut PathHandler,
456 refresh_kind: ProcessRefreshKind,
457) {
458 if !refresh_kind.user().needs_update(|| p.user_id.is_none()) {
459 return;
460 }
461
462 if let Some(((user_id, effective_user_id), (group_id, effective_group_id))) =
463 get_uid_and_gid(path.replace_and_join("status"))
464 {
465 p.user_id = Some(Uid(user_id));
466 p.effective_user_id = Some(Uid(effective_user_id));
467 p.group_id = Some(Gid(group_id));
468 p.effective_group_id = Some(Gid(effective_group_id));
469 }
470}
471
472fn update_optional_path(target: &mut Option<PathBuf>, path: &std::path::Path) {
475 let new_val = realpath(path);
476 if new_val.is_some() || target.is_none() {
477 *target = new_val;
478 }
479}
480
481#[allow(clippy::too_many_arguments)]
482fn update_proc_info(
483 p: &mut ProcessInner,
484 parent_pid: Option<Pid>,
485 refresh_kind: ProcessRefreshKind,
486 proc_path: &mut PathHandler,
487 str_parts: &[&str],
488 uptime: u64,
489 info: &SystemInfo,
490) {
491 update_parent_pid(p, parent_pid, str_parts);
492
493 get_status(p, str_parts.get(ProcIndex::State as usize).unwrap_or(&""));
494 refresh_user_group_ids(p, proc_path, refresh_kind);
495
496 if refresh_kind.exe().needs_update(|| p.exe.is_none()) {
497 let new_exe = realpath(proc_path.replace_and_join("exe"));
500 if new_exe.is_some() || p.exe.is_none() {
502 p.exe = new_exe;
503 let deleted = b" (deleted)";
507 if let Some(exe) = &mut p.exe
508 && let Some(file_name) = exe.file_name()
509 && file_name.as_encoded_bytes().ends_with(deleted)
510 {
511 let mut file_name = file_name.as_encoded_bytes().to_vec();
512 file_name.truncate(file_name.len() - deleted.len());
513 unsafe {
514 exe.set_file_name(OsString::from_encoded_bytes_unchecked(file_name));
515 }
516 }
517 }
518 }
519
520 if refresh_kind.cmd().needs_update(|| p.cmd.is_empty()) {
521 let new_cmd = copy_from_file(proc_path.replace_and_join("cmdline"));
522 if !new_cmd.is_empty() || p.cmd.is_empty() {
523 p.cmd = new_cmd;
524 }
525 }
526 if refresh_kind.environ().needs_update(|| p.environ.is_empty()) {
527 let new_environ = copy_from_file(proc_path.replace_and_join("environ"));
528 if !new_environ.is_empty() || p.environ.is_empty() {
529 p.environ = new_environ;
530 }
531 }
532 if refresh_kind.cwd().needs_update(|| p.cwd.is_none()) {
533 update_optional_path(&mut p.cwd, proc_path.replace_and_join("cwd"));
534 }
535 if refresh_kind.root().needs_update(|| p.root.is_none()) {
536 update_optional_path(&mut p.root, proc_path.replace_and_join("root"));
537 }
538
539 update_time_and_memory(proc_path, p, str_parts, uptime, info, refresh_kind);
540 if refresh_kind.disk_usage() {
541 update_process_disk_activity(p, proc_path);
542 }
543 if refresh_kind.cpu() {
545 p.accumulated_cpu_time =
548 p.utime.saturating_add(p.stime).saturating_mul(1_000) / info.clock_cycle;
549 }
550 p.updated = true;
551}
552
553fn update_parent_pid(p: &mut ProcessInner, parent_pid: Option<Pid>, str_parts: &[&str]) {
554 p.parent = match parent_pid {
555 Some(parent_pid) if parent_pid.0 != 0 => Some(parent_pid),
556 _ => match str_parts
557 .get(ProcIndex::ParentPid as usize)
558 .and_then(|part| Pid::from_str(part).ok())
559 {
560 Some(p) if p.0 != 0 => Some(p),
561 _ => None,
562 },
563 };
564}
565
566#[allow(clippy::too_many_arguments)]
567fn retrieve_all_new_process_info(
568 is_thread: bool,
569 pid: Pid,
570 parent_pid: Option<Pid>,
571 parts: &Parts<'_>,
572 path: &Path,
573 info: &SystemInfo,
574 refresh_kind: ProcessRefreshKind,
575 uptime: u64,
576) -> Process {
577 let mut p = ProcessInner::new(pid, path.to_owned());
578 let mut proc_path = PathHandler::new(path);
579 let name = parts.short_exe;
580
581 let (start_time_raw, start_time_without_boot_time) =
582 compute_start_time_without_boot_time(parts, info);
583 p.start_time_raw = start_time_raw;
584 p.start_time_without_boot_time = start_time_without_boot_time;
585 p.start_time = p
586 .start_time_without_boot_time
587 .saturating_add(info.boot_time);
588
589 p.name = OsStr::from_bytes(name).to_os_string();
590 if let Some(part) = parts.str_parts.get(ProcIndex::Flags as usize)
591 && c_ulong::from_str(part).is_ok_and(|flags| flags & libc::PF_KTHREAD as c_ulong != 0)
592 {
593 p.thread_kind = Some(ThreadKind::Kernel);
594 } else if is_thread {
595 p.thread_kind = Some(ThreadKind::Userland);
596 }
597
598 update_proc_info(
599 &mut p,
600 parent_pid,
601 refresh_kind,
602 &mut proc_path,
603 &parts.str_parts,
604 uptime,
605 info,
606 );
607
608 Process { inner: p }
609}
610
611fn update_existing_process(
612 is_thread: bool,
613 proc: &mut Process,
614 parent_pid: Option<Pid>,
615 uptime: u64,
616 info: &SystemInfo,
617 refresh_kind: ProcessRefreshKind,
618 tasks: Option<HashSet<Pid>>,
619) -> Result<Option<Process>, ()> {
620 let entry = &mut proc.inner;
621 let data = if let Some(mut f) = entry.stat_file.take() {
622 match get_all_data_from_file(&mut f, 1024) {
623 Ok(data) => {
624 entry.stat_file = Some(f);
626 data
627 }
628 Err(_) => {
629 _get_stat_data(&entry.proc_path, &mut entry.stat_file)?
632 }
633 }
634 } else {
635 _get_stat_data(&entry.proc_path, &mut entry.stat_file)?
636 };
637 entry.tasks = tasks;
638
639 let parts = parse_stat_file(&data).ok_or(())?;
640 let start_time_raw = start_time_raw(&parts);
641
642 if start_time_raw == entry.start_time_raw {
646 let mut proc_path = PathHandler::new(&entry.proc_path);
647
648 if is_thread && entry.thread_kind.is_none() {
651 entry.thread_kind = Some(ThreadKind::Userland);
652 }
653
654 update_proc_info(
655 entry,
656 parent_pid,
657 refresh_kind,
658 &mut proc_path,
659 &parts.str_parts,
660 uptime,
661 info,
662 );
663
664 refresh_user_group_ids(entry, &mut proc_path, refresh_kind);
665 return Ok(None);
666 }
667 let p = retrieve_all_new_process_info(
669 is_thread,
670 entry.pid,
671 parent_pid,
672 &parts,
673 &entry.proc_path,
674 info,
675 refresh_kind,
676 uptime,
677 );
678 *proc = p;
679 Ok(None)
681}
682
683#[allow(clippy::too_many_arguments)]
684pub(crate) fn _get_process_data(
685 path: &Path,
686 proc_list: &mut HashMap<Pid, Process>,
687 pid: Pid,
688 is_thread: bool,
689 parent_pid: Option<Pid>,
690 uptime: u64,
691 info: &SystemInfo,
692 refresh_kind: ProcessRefreshKind,
693 tasks: Option<HashSet<Pid>>,
694) -> Result<Option<Process>, ()> {
695 if let Some(ref mut entry) = proc_list.get_mut(&pid) {
696 return update_existing_process(
697 is_thread,
698 entry,
699 parent_pid,
700 uptime,
701 info,
702 refresh_kind,
703 tasks,
704 );
705 }
706 let mut stat_file = None;
707 let data = _get_stat_data(path, &mut stat_file)?;
708 let parts = parse_stat_file(&data).ok_or(())?;
709
710 let mut new_process = retrieve_all_new_process_info(
711 is_thread,
712 pid,
713 parent_pid,
714 &parts,
715 path,
716 info,
717 refresh_kind,
718 uptime,
719 );
720 new_process.inner.stat_file = stat_file;
721 new_process.inner.tasks = tasks;
722 Ok(Some(new_process))
723}
724
725fn old_get_memory(entry: &mut ProcessInner, str_parts: &[&str], info: &SystemInfo) {
726 entry.memory = str_parts
728 .get(ProcIndex::ResidentSetSize as usize)
729 .and_then(|part| u64::from_str(part).ok())
730 .unwrap_or(0)
731 .saturating_mul(info.page_size_b);
732 entry.virtual_memory = str_parts
735 .get(ProcIndex::VirtualSize as usize)
736 .and_then(|part| u64::from_str(part).ok())
737 .unwrap_or(0);
738}
739
740fn slice_to_nb(s: &[u8]) -> u64 {
741 let mut nb: u64 = 0;
742
743 for c in s {
744 nb = nb * 10 + (c - b'0') as u64;
745 }
746 nb
747}
748
749fn get_memory(path: &Path, entry: &mut ProcessInner, info: &SystemInfo) -> bool {
750 let mut file = match File::open(path) {
751 Ok(f) => f,
752 Err(_e) => {
753 sysinfo_debug!(
754 "Using old memory information (failed to open {:?}: {_e:?})",
755 path
756 );
757 return false;
758 }
759 };
760 let mut buf = Vec::new();
761 if let Err(_e) = file.read_to_end(&mut buf) {
762 sysinfo_debug!(
763 "Using old memory information (failed to read {:?}: {_e:?})",
764 path
765 );
766 return false;
767 }
768 let mut parts = buf.split(|c| *c == b' ');
769 entry.virtual_memory = parts
770 .next()
771 .map(slice_to_nb)
772 .unwrap_or(0)
773 .saturating_mul(info.page_size_b);
774 entry.memory = parts
775 .next()
776 .map(slice_to_nb)
777 .unwrap_or(0)
778 .saturating_mul(info.page_size_b);
779 true
780}
781
782#[allow(clippy::too_many_arguments)]
783fn update_time_and_memory(
784 path: &mut PathHandler,
785 entry: &mut ProcessInner,
786 str_parts: &[&str],
787 uptime: u64,
788 info: &SystemInfo,
789 refresh_kind: ProcessRefreshKind,
790) {
791 {
792 #[allow(clippy::collapsible_if)]
793 if refresh_kind.memory() {
794 if !get_memory(path.replace_and_join("statm"), entry, info) {
796 old_get_memory(entry, str_parts, info);
797 }
798 }
799 set_time(
800 entry,
801 str_parts
802 .get(ProcIndex::UserTime as usize)
803 .and_then(|part| u64::from_str(part).ok())
804 .unwrap_or(0),
805 str_parts
806 .get(ProcIndex::SystemTime as usize)
807 .and_then(|part| u64::from_str(part).ok())
808 .unwrap_or(0),
809 );
810 entry.run_time = uptime.saturating_sub(entry.start_time_without_boot_time);
811 }
812}
813
814struct ProcAndTasks {
815 pid: Pid,
816 parent_pid: Option<Pid>,
817 path: PathBuf,
818 tasks: Option<HashSet<Pid>>,
819 is_thread: bool,
820}
821
822#[cfg(feature = "multithread")]
823#[inline]
824pub(crate) fn iter<T>(val: T) -> rayon::iter::IterBridge<T>
825where
826 T: rayon::iter::ParallelBridge,
827{
828 val.par_bridge()
829}
830
831#[cfg(not(feature = "multithread"))]
832#[inline]
833pub(crate) fn iter<T>(val: T) -> T
834where
835 T: Iterator,
836{
837 val
838}
839
840pub(crate) fn refresh_procs(
843 proc_list: &mut HashMap<Pid, Process>,
844 proc_path: &Path,
845 uptime: u64,
846 info: &SystemInfo,
847 processes_to_update: ProcessesToUpdate<'_>,
848 refresh_kind: ProcessRefreshKind,
849) -> usize {
850 #[cfg(feature = "multithread")]
851 use rayon::iter::ParallelIterator;
852
853 let nb_updated = AtomicUsize::new(0);
854
855 let procs = {
865 let pid_iter: Box<dyn Iterator<Item = (PathBuf, Pid)> + Send> = match processes_to_update {
866 ProcessesToUpdate::All => match read_dir(proc_path) {
867 Ok(proc_entries) => Box::new(proc_entries.filter_map(filter_pid_entries)),
868 Err(_err) => {
869 sysinfo_debug!("Failed to read folder {proc_path:?}: {_err:?}");
870 return 0;
871 }
872 },
873 ProcessesToUpdate::Some(pids) => Box::new(
874 pids.iter()
875 .map(|pid| (proc_path.join(pid.to_string()), *pid)),
876 ),
877 };
878
879 let proc_list = Wrap(UnsafeCell::new(proc_list));
880
881 iter(pid_iter)
882 .flat_map(|(path, pid)| {
883 get_proc_and_tasks(path, pid, refresh_kind, processes_to_update)
884 })
885 .filter_map(|e| {
886 let proc_list = proc_list.get();
887 let new_process = _get_process_data(
888 e.path.as_path(),
889 proc_list,
890 e.pid,
891 e.is_thread,
892 e.parent_pid,
893 uptime,
894 info,
895 refresh_kind,
896 e.tasks,
897 )
898 .ok()?;
899 nb_updated.fetch_add(1, Ordering::Relaxed);
900 new_process
901 })
902 .collect::<Vec<_>>()
903 };
904 for proc_ in procs {
905 proc_list.insert(proc_.pid(), proc_);
906 }
907 nb_updated.into_inner()
908}
909
910fn filter_pid_entries(entry: Result<DirEntry, std::io::Error>) -> Option<(PathBuf, Pid)> {
911 if let Ok(entry) = entry
912 && let Ok(file_type) = entry.file_type()
913 && file_type.is_dir()
914 && let Some(name) = entry.file_name().to_str()
915 && let Ok(pid) = usize::from_str(name)
916 {
917 Some((entry.path(), Pid::from(pid)))
918 } else {
919 None
920 }
921}
922
923fn get_proc_and_tasks(
924 path: PathBuf,
925 pid: Pid,
926 refresh_kind: ProcessRefreshKind,
927 processes_to_update: ProcessesToUpdate<'_>,
928) -> Vec<ProcAndTasks> {
929 let mut parent_pid = None;
930 let mut is_thread = false;
931 let (mut procs, mut tasks) = if refresh_kind.tasks() {
932 let procs = get_proc_tasks(&path, pid);
933 let tasks = procs.iter().map(|ProcAndTasks { pid, .. }| *pid).collect();
934
935 (procs, Some(tasks))
936 } else {
937 (Vec::new(), None)
938 };
939
940 if let Some(tgid) = get_tgid(&path.join("status"))
943 && tgid != pid
944 {
945 parent_pid = Some(tgid);
946 tasks = None;
947 is_thread = true;
948 procs.clear();
950 } else if processes_to_update != ProcessesToUpdate::All {
951 procs.clear();
953 }
954
955 procs.push(ProcAndTasks {
956 is_thread,
957 pid,
958 parent_pid,
959 path,
960 tasks,
961 });
962
963 procs
964}
965
966fn get_proc_tasks(path: &Path, parent_pid: Pid) -> Vec<ProcAndTasks> {
967 let task_path = path.join("task");
968
969 read_dir(task_path)
970 .ok()
971 .map(|task_entries| {
972 task_entries
973 .filter_map(filter_pid_entries)
974 .filter(|(_, pid)| *pid != parent_pid)
976 .map(|(path, pid)| ProcAndTasks {
977 pid,
978 is_thread: true,
979 path,
980 parent_pid: Some(parent_pid),
981 tasks: None,
982 })
983 .collect()
984 })
985 .unwrap_or_default()
986}
987
988fn split_content(mut data: &[u8]) -> Vec<OsString> {
989 let mut out = Vec::with_capacity(10);
990 while let Some(pos) = data.iter().position(|c| *c == 0) {
991 let s = &data[..pos].trim_ascii();
992 if !s.is_empty() {
993 out.push(OsStr::from_bytes(s).to_os_string());
994 }
995 data = &data[pos + 1..];
996 }
997 if !data.is_empty() {
998 let s = data.trim_ascii();
999 if !s.is_empty() {
1000 out.push(OsStr::from_bytes(s).to_os_string());
1001 }
1002 }
1003 out
1004}
1005
1006fn copy_from_file(entry: &Path) -> Vec<OsString> {
1007 match File::open(entry) {
1008 Ok(mut f) => {
1009 let mut data = Vec::with_capacity(16_384);
1010
1011 if let Err(_e) = f.read_to_end(&mut data) {
1012 sysinfo_debug!("Failed to read file in `copy_from_file`: {:?}", _e);
1013 Vec::new()
1014 } else {
1015 split_content(&data)
1016 }
1017 }
1018 Err(_e) => {
1019 sysinfo_debug!("Failed to open file in `copy_from_file`: {:?}", _e);
1020 Vec::new()
1021 }
1022 }
1023}
1024
1025fn get_uid_and_gid(file_path: &Path) -> Option<((uid_t, uid_t), (gid_t, gid_t))> {
1027 let status_data = get_all_utf8_data(file_path, 16_385).ok()?;
1028
1029 let f = |h: &str, n: &str| -> (Option<uid_t>, Option<uid_t>) {
1034 if h.starts_with(n) {
1035 let mut ids = h.split_whitespace();
1036 let real = ids.nth(1).unwrap_or("0").parse().ok();
1037 let effective = ids.next().unwrap_or("0").parse().ok();
1038
1039 (real, effective)
1040 } else {
1041 (None, None)
1042 }
1043 };
1044 let mut uid = None;
1045 let mut effective_uid = None;
1046 let mut gid = None;
1047 let mut effective_gid = None;
1048 for line in status_data.lines() {
1049 if let (Some(real), Some(effective)) = f(line, "Uid:") {
1050 debug_assert!(uid.is_none() && effective_uid.is_none());
1051 uid = Some(real);
1052 effective_uid = Some(effective);
1053 } else if let (Some(real), Some(effective)) = f(line, "Gid:") {
1054 debug_assert!(gid.is_none() && effective_gid.is_none());
1055 gid = Some(real);
1056 effective_gid = Some(effective);
1057 } else {
1058 continue;
1059 }
1060 if uid.is_some() && gid.is_some() {
1061 break;
1062 }
1063 }
1064 match (uid, effective_uid, gid, effective_gid) {
1065 (Some(uid), Some(effective_uid), Some(gid), Some(effective_gid)) => {
1066 Some(((uid, effective_uid), (gid, effective_gid)))
1067 }
1068 _ => None,
1069 }
1070}
1071
1072fn get_tgid(file_path: &Path) -> Option<Pid> {
1073 const TGID_KEY: &str = "Tgid:";
1074 let status_data = get_all_utf8_data(file_path, 16_385).ok()?;
1075 let tgid_line = status_data
1076 .lines()
1077 .find(|line| line.starts_with(TGID_KEY))?;
1078 tgid_line[TGID_KEY.len()..].trim_start().parse().ok()
1079}
1080
1081struct Parts<'a> {
1082 str_parts: Vec<&'a str>,
1083 short_exe: &'a [u8],
1084}
1085
1086fn parse_stat_file(data: &[u8]) -> Option<Parts<'_>> {
1087 let mut str_parts = Vec::with_capacity(51);
1097 let mut data_it = data.splitn(2, |&b| b == b' ');
1098 str_parts.push(str::from_utf8(data_it.next()?).ok()?);
1099 let mut data_it = data_it.next()?.rsplitn(2, |&b| b == b')');
1100 let data = str::from_utf8(data_it.next()?).ok()?;
1101 let short_exe = data_it.next()?;
1102 str_parts.extend(data.split_whitespace());
1103 Some(Parts {
1104 str_parts,
1105 short_exe: short_exe.strip_prefix(b"(").unwrap_or(short_exe),
1106 })
1107}
1108
1109struct FileCounter(File);
1111
1112impl FileCounter {
1113 fn new(f: File) -> Option<Self> {
1114 let any_remaining =
1115 remaining_files().fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| {
1116 if remaining > 0 {
1117 Some(remaining - 1)
1118 } else {
1119 None
1121 }
1122 });
1123
1124 any_remaining.ok().map(|_| Self(f))
1125 }
1126}
1127
1128impl std::ops::Deref for FileCounter {
1129 type Target = File;
1130
1131 fn deref(&self) -> &Self::Target {
1132 &self.0
1133 }
1134}
1135
1136impl std::ops::DerefMut for FileCounter {
1137 fn deref_mut(&mut self) -> &mut Self::Target {
1138 &mut self.0
1139 }
1140}
1141
1142impl Drop for FileCounter {
1143 fn drop(&mut self) {
1144 remaining_files().fetch_add(1, Ordering::Relaxed);
1145 }
1146}
1147
1148#[cfg(test)]
1149mod tests {
1150 use super::split_content;
1151 use std::ffi::OsString;
1152
1153 #[test]
1155 fn test_copy_file() {
1156 assert_eq!(split_content(b"hello\0"), vec![OsString::from("hello")]);
1157 assert_eq!(split_content(b"hello"), vec![OsString::from("hello")]);
1158 assert_eq!(
1159 split_content(b"hello\0b"),
1160 vec![OsString::from("hello"), "b".into()]
1161 );
1162 assert_eq!(
1163 split_content(b"hello\0\0\0\0b"),
1164 vec![OsString::from("hello"), "b".into()]
1165 );
1166 }
1167}