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