Skip to main content

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::{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    // More exist but we only use the listed ones. For more, take a look at `man proc`.
93}
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        // If anything fails when trying to retrieve the start time, better to return `None`.
270        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    // First time updating the values without reference, wait for a second cycle to update cpu_usage
348    if p.old_utime == 0 && p.old_stime == 0 {
349        return;
350    }
351
352    // We use `max_value` to ensure that the process CPU usage will never get bigger than:
353    // `"number of CPUs" * 100.`
354    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            // No need to continue the reading.
398            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    // To be noted that the start time is invalid here, it still needs to be converted into
428    // "real" time.
429    (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
472/// Only overwrite if the new value is `Some` or the old value was `None`,
473/// to avoid wiping previously-read data if the process terminated mid-refresh.
474fn 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        // Do not use cmd[0] because it is not the same thing.
498        // See https://github.com/GuillaumeGomez/sysinfo/issues/697.
499        let new_exe = realpath(proc_path.replace_and_join("exe"));
500        // Avoid overwriting with None if the process terminated mid-refresh.
501        if new_exe.is_some() || p.exe.is_none() {
502            p.exe = new_exe;
503            // If the target executable file was modified or removed, linux appends ` (deleted)`
504            // at the end. We need to remove it.
505            // See https://github.com/GuillaumeGomez/sysinfo/issues/1585.
506            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    // Needs to be after `update_time_and_memory`.
544    if refresh_kind.cpu() {
545        // The external values for CPU times are in "ticks", which are
546        // scaled by "HZ", which is pegged externally at 100 ticks/second.
547        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                // Everything went fine, we put back the file descriptor.
625                entry.stat_file = Some(f);
626                data
627            }
628            Err(_) => {
629                // It's possible that the file descriptor is no longer valid in case the
630                // original process was terminated and another one took its place.
631                _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    // It's possible that a new process took this same PID when the "original one" terminated.
643    // If the start time differs, then it means it's not the same process anymore and that we
644    // need to get all its information, hence why we check it here.
645    if start_time_raw == entry.start_time_raw {
646        let mut proc_path = PathHandler::new(&entry.proc_path);
647
648        // If the entry was first discovered without thread info
649        // (e.g. in ProcessesToUpdate::All mode), fix its thread_kind now.
650        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    // If we're here, it means that the PID still exists but it's a different process.
668    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    // Since this PID is already in the HashMap, no need to add it again.
680    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    // rss
727    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    // vsz correspond to the Virtual memory size in bytes.
733    // see: https://man7.org/linux/man-pages/man5/proc.5.html
734    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            // Keeping this nested level for readability reasons.
795            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
840/// We're forced to read the whole `/proc` folder because if a process died and another took its
841/// place, we need to get the task parent (if it's a task).
842pub(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    // This code goes through processes (listed in `/proc`) and through tasks (listed in
856    // `/proc/[PID]/task`). However, the stored tasks information is supposed to be already present
857    // in the PIDs listed from `/proc` so there will be no duplicates between PIDs and tasks PID.
858    //
859    // If a task is not listed in `/proc`, then we don't retrieve its information.
860    //
861    // So in short: since we update the `HashMap` itself by adding/removing entries outside of the
862    // parallel iterator, we can safely use it inside the parallel iterator and update its entries
863    // concurrently.
864    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 the process' tgid doesn't match its pid, it is a task (thread).
941    // This check must apply in ALL modes, not just `Some`.
942    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        // Threads don't have meaningful tasks, clear whatever was fetched.
949        procs.clear();
950    } else if processes_to_update != ProcessesToUpdate::All {
951        // Don't add the tasks to the list of processes to update
952        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                // Needed because tasks have their own PID listed in the "task" folder.
975                .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
1025// Fetch tuples of real and effective UID and GID.
1026fn 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    // We're only interested in the lines starting with Uid: and Gid:
1030    // here. From these lines, we're looking at the first and second entries to get
1031    // the real u/gid.
1032
1033    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    // The stat file is "interesting" to parse, because spaces cannot
1088    // be used as delimiters. The second field stores the command name
1089    // surrounded by parentheses. Unfortunately, whitespace and
1090    // parentheses are legal parts of the command, so parsing has to
1091    // proceed like this: The first field is delimited by the first
1092    // whitespace, the second field is everything until the last ')'
1093    // in the entire string. All other fields are delimited by
1094    // whitespace.
1095
1096    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
1109/// Type used to correctly handle the `REMAINING_FILES` global.
1110struct 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                    // All file descriptors we were allowed are being used.
1120                    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    // This test ensures that all the parts of the data are split.
1154    #[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}