sysinfo/unix/linux/
process.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use 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    // More exist but we only use the listed ones. For more, take a look at `man proc`.
94}
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        // If anything fails when trying to retrieve the start time, better to return `None`.
267        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    // First time updating the values without reference, wait for a second cycle to update cpu_usage
345    if p.old_utime == 0 && p.old_stime == 0 {
346        return;
347    }
348
349    // We use `max_value` to ensure that the process CPU usage will never get bigger than:
350    // `"number of CPUs" * 100.`
351    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            // No need to continue the reading.
395            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    // To be noted that the start time is invalid here, it still needs to be converted into
421    // "real" time.
422    (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        // Do not use cmd[0] because it is not the same thing.
482        // See https://github.com/GuillaumeGomez/sysinfo/issues/697.
483        p.exe = realpath(proc_path.replace_and_join("exe"));
484        // If the target executable file was modified or removed, linux appends ` (deleted)` at
485        // the end. We need to remove it.
486        // See https://github.com/GuillaumeGomez/sysinfo/issues/1585.
487        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    // Needs to be after `update_time_and_memory`.
518    if refresh_kind.cpu() {
519        // The external values for CPU times are in "ticks", which are
520        // scaled by "HZ", which is pegged externally at 100 ticks/second.
521        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                // Everything went fine, we put back the file descriptor.
594                entry.stat_file = Some(f);
595                data
596            }
597            Err(_) => {
598                // It's possible that the file descriptor is no longer valid in case the
599                // original process was terminated and another one took its place.
600                _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    // It's possible that a new process took this same PID when the "original one" terminated.
612    // If the start time differs, then it means it's not the same process anymore and that we
613    // need to get all its information, hence why we check it here.
614    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    // If we're here, it means that the PID still exists but it's a different process.
631    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    // Since this PID is already in the HashMap, no need to add it again.
642    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    // rss
672    entry.memory = u64::from_str(str_parts[ProcIndex::ResidentSetSize as usize])
673        .unwrap_or(0)
674        .saturating_mul(info.page_size_b);
675    // vsz correspond to the Virtual memory size in bytes.
676    // see: https://man7.org/linux/man-pages/man5/proc.5.html
677    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            // Keeping this nested level for readability reasons.
735            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
773/// We're forced to read the whole `/proc` folder because if a process died and another took its
774/// place, we need to get the task parent (if it's a task).
775pub(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    // This code goes through processes (listed in `/proc`) and through tasks (listed in
789    // `/proc/[PID]/task`). However, the stored tasks information is supposed to be already present
790    // in the PIDs listed from `/proc` so there will be no duplicates between PIDs and tasks PID.
791    //
792    // If a task is not listed in `/proc`, then we don't retrieve its information.
793    //
794    // So in short: since we update the `HashMap` itself by adding/removing entries outside of the
795    // parallel iterator, we can safely use it inside the parallel iterator and update its entries
796    // concurrently.
797    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 the process' tgid doesn't match its pid, it is a task
873        if let Some(tgid) = get_tgid(&path.join("status"))
874            && tgid != pid
875        {
876            parent_pid = Some(tgid);
877            tasks = None;
878        }
879
880        // Don't add the tasks to the list of processes to update
881        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                // Needed because tasks have their own PID listed in the "task" folder.
903                .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
952// Fetch tuples of real and effective UID and GID.
953fn 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    // We're only interested in the lines starting with Uid: and Gid:
957    // here. From these lines, we're looking at the first and second entries to get
958    // the real u/gid.
959
960    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    // The stat file is "interesting" to parse, because spaces cannot
1015    // be used as delimiters. The second field stores the command name
1016    // surrounded by parentheses. Unfortunately, whitespace and
1017    // parentheses are legal parts of the command, so parsing has to
1018    // proceed like this: The first field is delimited by the first
1019    // whitespace, the second field is everything until the last ')'
1020    // in the entire string. All other fields are delimited by
1021    // whitespace.
1022
1023    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
1036/// Type used to correctly handle the `REMAINING_FILES` global.
1037struct 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                    // All file descriptors we were allowed are being used.
1047                    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    // This test ensures that all the parts of the data are split.
1080    #[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}