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};
8use std::io::Read;
9use std::os::unix::ffi::OsStrExt;
10use std::path::{Path, PathBuf};
11use std::str::{self, FromStr};
12use std::sync::atomic::{AtomicUsize, Ordering};
13
14use libc::{c_ulong, gid_t, kill, uid_t};
15
16use crate::sys::system::SystemInfo;
17use crate::sys::utils::{
18    get_all_data_from_file, get_all_utf8_data, realpath, PathHandler, PathPush,
19};
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    run_time: u64,
113    pub(crate) updated: bool,
114    cpu_usage: f32,
115    user_id: Option<Uid>,
116    effective_user_id: Option<Uid>,
117    group_id: Option<Gid>,
118    effective_group_id: Option<Gid>,
119    pub(crate) status: ProcessStatus,
120    pub(crate) tasks: Option<HashSet<Pid>>,
121    stat_file: Option<FileCounter>,
122    old_read_bytes: u64,
123    old_written_bytes: u64,
124    read_bytes: u64,
125    written_bytes: u64,
126    thread_kind: Option<ThreadKind>,
127    proc_path: PathBuf,
128}
129
130impl ProcessInner {
131    pub(crate) fn new(pid: Pid, proc_path: PathBuf) -> Self {
132        Self {
133            name: OsString::new(),
134            pid,
135            parent: None,
136            cmd: Vec::new(),
137            environ: Vec::new(),
138            exe: None,
139            cwd: None,
140            root: None,
141            memory: 0,
142            virtual_memory: 0,
143            cpu_usage: 0.,
144            utime: 0,
145            stime: 0,
146            old_utime: 0,
147            old_stime: 0,
148            updated: true,
149            start_time_without_boot_time: 0,
150            start_time: 0,
151            run_time: 0,
152            user_id: None,
153            effective_user_id: None,
154            group_id: None,
155            effective_group_id: None,
156            status: ProcessStatus::Unknown(0),
157            tasks: None,
158            stat_file: None,
159            old_read_bytes: 0,
160            old_written_bytes: 0,
161            read_bytes: 0,
162            written_bytes: 0,
163            thread_kind: None,
164            proc_path,
165        }
166    }
167
168    pub(crate) fn kill_with(&self, signal: Signal) -> Option<bool> {
169        let c_signal = crate::sys::system::convert_signal(signal)?;
170        unsafe { Some(kill(self.pid.0, c_signal) == 0) }
171    }
172
173    pub(crate) fn name(&self) -> &OsStr {
174        &self.name
175    }
176
177    pub(crate) fn cmd(&self) -> &[OsString] {
178        &self.cmd
179    }
180
181    pub(crate) fn exe(&self) -> Option<&Path> {
182        self.exe.as_deref()
183    }
184
185    pub(crate) fn pid(&self) -> Pid {
186        self.pid
187    }
188
189    pub(crate) fn environ(&self) -> &[OsString] {
190        &self.environ
191    }
192
193    pub(crate) fn cwd(&self) -> Option<&Path> {
194        self.cwd.as_deref()
195    }
196
197    pub(crate) fn root(&self) -> Option<&Path> {
198        self.root.as_deref()
199    }
200
201    pub(crate) fn memory(&self) -> u64 {
202        self.memory
203    }
204
205    pub(crate) fn virtual_memory(&self) -> u64 {
206        self.virtual_memory
207    }
208
209    pub(crate) fn parent(&self) -> Option<Pid> {
210        self.parent
211    }
212
213    pub(crate) fn status(&self) -> ProcessStatus {
214        self.status
215    }
216
217    pub(crate) fn start_time(&self) -> u64 {
218        self.start_time
219    }
220
221    pub(crate) fn run_time(&self) -> u64 {
222        self.run_time
223    }
224
225    pub(crate) fn cpu_usage(&self) -> f32 {
226        self.cpu_usage
227    }
228
229    pub(crate) fn disk_usage(&self) -> DiskUsage {
230        DiskUsage {
231            written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
232            total_written_bytes: self.written_bytes,
233            read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
234            total_read_bytes: self.read_bytes,
235        }
236    }
237
238    pub(crate) fn user_id(&self) -> Option<&Uid> {
239        self.user_id.as_ref()
240    }
241
242    pub(crate) fn effective_user_id(&self) -> Option<&Uid> {
243        self.effective_user_id.as_ref()
244    }
245
246    pub(crate) fn group_id(&self) -> Option<Gid> {
247        self.group_id
248    }
249
250    pub(crate) fn effective_group_id(&self) -> Option<Gid> {
251        self.effective_group_id
252    }
253
254    pub(crate) fn wait(&self) {
255        let mut status = 0;
256        // attempt waiting
257        unsafe {
258            if retry_eintr!(libc::waitpid(self.pid.0, &mut status, 0)) < 0 {
259                // attempt failed (non-child process) so loop until process ends
260                let duration = std::time::Duration::from_millis(10);
261                while kill(self.pid.0, 0) == 0 {
262                    std::thread::sleep(duration);
263                }
264            }
265        }
266    }
267
268    pub(crate) fn session_id(&self) -> Option<Pid> {
269        unsafe {
270            let session_id = libc::getsid(self.pid.0);
271            if session_id < 0 {
272                None
273            } else {
274                Some(Pid(session_id))
275            }
276        }
277    }
278
279    pub(crate) fn thread_kind(&self) -> Option<ThreadKind> {
280        self.thread_kind
281    }
282
283    pub(crate) fn switch_updated(&mut self) -> bool {
284        std::mem::replace(&mut self.updated, false)
285    }
286}
287
288pub(crate) fn compute_cpu_usage(p: &mut ProcessInner, total_time: f32, max_value: f32) {
289    // First time updating the values without reference, wait for a second cycle to update cpu_usage
290    if p.old_utime == 0 && p.old_stime == 0 {
291        return;
292    }
293
294    // We use `max_value` to ensure that the process CPU usage will never get bigger than:
295    // `"number of CPUs" * 100.`
296    p.cpu_usage = (p
297        .utime
298        .saturating_sub(p.old_utime)
299        .saturating_add(p.stime.saturating_sub(p.old_stime)) as f32
300        / total_time
301        * 100.)
302        .min(max_value);
303}
304
305pub(crate) fn set_time(p: &mut ProcessInner, utime: u64, stime: u64) {
306    p.old_utime = p.utime;
307    p.old_stime = p.stime;
308    p.utime = utime;
309    p.stime = stime;
310    p.updated = true;
311}
312
313pub(crate) fn update_process_disk_activity(p: &mut ProcessInner, path: &mut PathHandler) {
314    let data = match get_all_utf8_data(path.join("io"), 16_384) {
315        Ok(d) => d,
316        Err(_) => return,
317    };
318    let mut done = 0;
319    for line in data.split('\n') {
320        let mut parts = line.split(": ");
321        match parts.next() {
322            Some("read_bytes") => {
323                p.old_read_bytes = p.read_bytes;
324                p.read_bytes = parts
325                    .next()
326                    .and_then(|x| x.parse::<u64>().ok())
327                    .unwrap_or(p.old_read_bytes);
328            }
329            Some("write_bytes") => {
330                p.old_written_bytes = p.written_bytes;
331                p.written_bytes = parts
332                    .next()
333                    .and_then(|x| x.parse::<u64>().ok())
334                    .unwrap_or(p.old_written_bytes);
335            }
336            _ => continue,
337        }
338        done += 1;
339        if done > 1 {
340            // No need to continue the reading.
341            break;
342        }
343    }
344}
345
346struct Wrap<'a, T>(UnsafeCell<&'a mut T>);
347
348impl<'a, T> Wrap<'a, T> {
349    fn get(&self) -> &'a mut T {
350        unsafe { *(self.0.get()) }
351    }
352}
353
354#[allow(clippy::non_send_fields_in_send_ty)]
355unsafe impl<T> Send for Wrap<'_, T> {}
356unsafe impl<T> Sync for Wrap<'_, T> {}
357
358#[inline(always)]
359fn compute_start_time_without_boot_time(parts: &Parts<'_>, info: &SystemInfo) -> u64 {
360    // To be noted that the start time is invalid here, it still needs to be converted into
361    // "real" time.
362    u64::from_str(parts.str_parts[ProcIndex::StartTime as usize]).unwrap_or(0) / info.clock_cycle
363}
364
365fn _get_stat_data(path: &Path, stat_file: &mut Option<FileCounter>) -> Result<Vec<u8>, ()> {
366    let mut file = File::open(path.join("stat")).map_err(|_| ())?;
367    let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?;
368    *stat_file = FileCounter::new(file);
369    Ok(data)
370}
371
372#[inline(always)]
373fn get_status(p: &mut ProcessInner, part: &str) {
374    p.status = part
375        .chars()
376        .next()
377        .map(ProcessStatus::from)
378        .unwrap_or_else(|| ProcessStatus::Unknown(0));
379}
380
381fn refresh_user_group_ids(
382    p: &mut ProcessInner,
383    path: &mut PathHandler,
384    refresh_kind: ProcessRefreshKind,
385) {
386    if !refresh_kind.user().needs_update(|| p.user_id.is_none()) {
387        return;
388    }
389
390    if let Some(((user_id, effective_user_id), (group_id, effective_group_id))) =
391        get_uid_and_gid(path.join("status"))
392    {
393        p.user_id = Some(Uid(user_id));
394        p.effective_user_id = Some(Uid(effective_user_id));
395        p.group_id = Some(Gid(group_id));
396        p.effective_group_id = Some(Gid(effective_group_id));
397    }
398}
399
400#[allow(clippy::too_many_arguments)]
401fn update_proc_info(
402    p: &mut ProcessInner,
403    parent_pid: Option<Pid>,
404    refresh_kind: ProcessRefreshKind,
405    proc_path: &mut PathHandler,
406    str_parts: &[&str],
407    uptime: u64,
408    info: &SystemInfo,
409) {
410    update_parent_pid(p, parent_pid, str_parts);
411
412    get_status(p, str_parts[ProcIndex::State as usize]);
413    refresh_user_group_ids(p, proc_path, refresh_kind);
414
415    if refresh_kind.exe().needs_update(|| p.exe.is_none()) {
416        // Do not use cmd[0] because it is not the same thing.
417        // See https://github.com/GuillaumeGomez/sysinfo/issues/697.
418        p.exe = realpath(proc_path.join("exe"));
419    }
420
421    if refresh_kind.cmd().needs_update(|| p.cmd.is_empty()) {
422        p.cmd = copy_from_file(proc_path.join("cmdline"));
423    }
424    if refresh_kind.environ().needs_update(|| p.environ.is_empty()) {
425        p.environ = copy_from_file(proc_path.join("environ"));
426    }
427    if refresh_kind.cwd().needs_update(|| p.cwd.is_none()) {
428        p.cwd = realpath(proc_path.join("cwd"));
429    }
430    if refresh_kind.root().needs_update(|| p.root.is_none()) {
431        p.root = realpath(proc_path.join("root"));
432    }
433
434    update_time_and_memory(proc_path, p, str_parts, uptime, info, refresh_kind);
435    if refresh_kind.disk_usage() {
436        update_process_disk_activity(p, proc_path);
437    }
438}
439
440fn update_parent_pid(p: &mut ProcessInner, parent_pid: Option<Pid>, str_parts: &[&str]) {
441    p.parent = match parent_pid {
442        Some(parent_pid) if parent_pid.0 != 0 => Some(parent_pid),
443        _ => match Pid::from_str(str_parts[ProcIndex::ParentPid as usize]) {
444            Ok(p) if p.0 != 0 => Some(p),
445            _ => None,
446        },
447    };
448}
449
450fn retrieve_all_new_process_info(
451    pid: Pid,
452    parent_pid: Option<Pid>,
453    parts: &Parts<'_>,
454    path: &Path,
455    info: &SystemInfo,
456    refresh_kind: ProcessRefreshKind,
457    uptime: u64,
458) -> Process {
459    let mut p = ProcessInner::new(pid, path.to_owned());
460    let mut proc_path = PathHandler::new(path);
461    let name = parts.short_exe;
462
463    p.start_time_without_boot_time = compute_start_time_without_boot_time(parts, info);
464    p.start_time = p
465        .start_time_without_boot_time
466        .saturating_add(info.boot_time);
467
468    p.name = OsStr::from_bytes(name).to_os_string();
469    if c_ulong::from_str(parts.str_parts[ProcIndex::Flags as usize])
470        .map(|flags| flags & libc::PF_KTHREAD as c_ulong != 0)
471        .unwrap_or(false)
472    {
473        p.thread_kind = Some(ThreadKind::Kernel);
474    } else if parent_pid.is_some() {
475        p.thread_kind = Some(ThreadKind::Userland);
476    }
477
478    update_proc_info(
479        &mut p,
480        parent_pid,
481        refresh_kind,
482        &mut proc_path,
483        &parts.str_parts,
484        uptime,
485        info,
486    );
487
488    Process { inner: p }
489}
490
491pub(crate) fn _get_process_data(
492    path: &Path,
493    proc_list: &mut HashMap<Pid, Process>,
494    pid: Pid,
495    parent_pid: Option<Pid>,
496    uptime: u64,
497    info: &SystemInfo,
498    refresh_kind: ProcessRefreshKind,
499) -> Result<(Option<Process>, Pid), ()> {
500    let data;
501    let parts = if let Some(ref mut entry) = proc_list.get_mut(&pid) {
502        let entry = &mut entry.inner;
503        data = if let Some(mut f) = entry.stat_file.take() {
504            match get_all_data_from_file(&mut f, 1024) {
505                Ok(data) => {
506                    // Everything went fine, we put back the file descriptor.
507                    entry.stat_file = Some(f);
508                    data
509                }
510                Err(_) => {
511                    // It's possible that the file descriptor is no longer valid in case the
512                    // original process was terminated and another one took its place.
513                    _get_stat_data(&entry.proc_path, &mut entry.stat_file)?
514                }
515            }
516        } else {
517            _get_stat_data(path, &mut entry.stat_file)?
518        };
519        let parts = parse_stat_file(&data).ok_or(())?;
520        let start_time_without_boot_time = compute_start_time_without_boot_time(&parts, info);
521
522        // It's possible that a new process took this same PID when the "original one" terminated.
523        // If the start time differs, then it means it's not the same process anymore and that we
524        // need to get all its information, hence why we check it here.
525        if start_time_without_boot_time == entry.start_time_without_boot_time {
526            let mut proc_path = PathHandler::new(path);
527
528            update_proc_info(
529                entry,
530                parent_pid,
531                refresh_kind,
532                &mut proc_path,
533                &parts.str_parts,
534                uptime,
535                info,
536            );
537
538            refresh_user_group_ids(entry, &mut proc_path, refresh_kind);
539            return Ok((None, pid));
540        }
541        parts
542    } else {
543        let mut stat_file = None;
544        let data = _get_stat_data(path, &mut stat_file)?;
545        let parts = parse_stat_file(&data).ok_or(())?;
546
547        let mut p = retrieve_all_new_process_info(
548            pid,
549            parent_pid,
550            &parts,
551            path,
552            info,
553            refresh_kind,
554            uptime,
555        );
556        p.inner.stat_file = stat_file;
557        return Ok((Some(p), pid));
558    };
559
560    // If we're here, it means that the PID still exists but it's a different process.
561    let p =
562        retrieve_all_new_process_info(pid, parent_pid, &parts, path, info, refresh_kind, uptime);
563    match proc_list.get_mut(&pid) {
564        Some(ref mut entry) => **entry = p,
565        // If it ever enters this case, it means that the process was removed from the HashMap
566        // in-between with the usage of dark magic.
567        None => unreachable!(),
568    }
569    // Since this PID is already in the HashMap, no need to add it again.
570    Ok((None, pid))
571}
572
573fn old_get_memory(entry: &mut ProcessInner, str_parts: &[&str], info: &SystemInfo) {
574    // rss
575    entry.memory = u64::from_str(str_parts[ProcIndex::ResidentSetSize as usize])
576        .unwrap_or(0)
577        .saturating_mul(info.page_size_b);
578    // vsz correspond to the Virtual memory size in bytes.
579    // see: https://man7.org/linux/man-pages/man5/proc.5.html
580    entry.virtual_memory = u64::from_str(str_parts[ProcIndex::VirtualSize as usize]).unwrap_or(0);
581}
582
583fn slice_to_nb(s: &[u8]) -> u64 {
584    let mut nb: u64 = 0;
585
586    for c in s {
587        nb = nb * 10 + (c - b'0') as u64;
588    }
589    nb
590}
591
592fn get_memory(path: &Path, entry: &mut ProcessInner, info: &SystemInfo) -> bool {
593    let mut file = match File::open(path) {
594        Ok(f) => f,
595        Err(_e) => {
596            sysinfo_debug!(
597                "Using old memory information (failed to open {:?}: {_e:?})",
598                path
599            );
600            return false;
601        }
602    };
603    let mut buf = Vec::new();
604    if let Err(_e) = file.read_to_end(&mut buf) {
605        sysinfo_debug!(
606            "Using old memory information (failed to read {:?}: {_e:?})",
607            path
608        );
609        return false;
610    }
611    let mut parts = buf.split(|c| *c == b' ');
612    entry.virtual_memory = parts
613        .next()
614        .map(slice_to_nb)
615        .unwrap_or(0)
616        .saturating_mul(info.page_size_b);
617    entry.memory = parts
618        .next()
619        .map(slice_to_nb)
620        .unwrap_or(0)
621        .saturating_mul(info.page_size_b);
622    true
623}
624
625#[allow(clippy::too_many_arguments)]
626fn update_time_and_memory(
627    path: &mut PathHandler,
628    entry: &mut ProcessInner,
629    str_parts: &[&str],
630    uptime: u64,
631    info: &SystemInfo,
632    refresh_kind: ProcessRefreshKind,
633) {
634    {
635        #[allow(clippy::collapsible_if)]
636        if refresh_kind.memory() {
637            // Keeping this nested level for readability reasons.
638            if !get_memory(path.join("statm"), entry, info) {
639                old_get_memory(entry, str_parts, info);
640            }
641        }
642        set_time(
643            entry,
644            u64::from_str(str_parts[ProcIndex::UserTime as usize]).unwrap_or(0),
645            u64::from_str(str_parts[ProcIndex::SystemTime as usize]).unwrap_or(0),
646        );
647        entry.run_time = uptime.saturating_sub(entry.start_time_without_boot_time);
648    }
649}
650
651struct ProcAndTasks {
652    pid: Pid,
653    parent_pid: Option<Pid>,
654    path: PathBuf,
655    tasks: Option<HashSet<Pid>>,
656}
657
658fn get_all_pid_entries(
659    parent: Option<&OsStr>,
660    parent_pid: Option<Pid>,
661    entry: DirEntry,
662    data: &mut Vec<ProcAndTasks>,
663) -> Option<Pid> {
664    let Ok(file_type) = entry.file_type() else {
665        return None;
666    };
667    if !file_type.is_dir() {
668        return None;
669    }
670
671    let entry = entry.path();
672    let name = entry.file_name();
673
674    if name == parent {
675        // Needed because tasks have their own PID listed in the "task" folder.
676        return None;
677    }
678    let name = name?;
679    let pid = Pid::from(usize::from_str(name.to_str()?).ok()?);
680
681    let tasks_dir = Path::join(&entry, "task");
682
683    let tasks = if let Ok(entries) = fs::read_dir(tasks_dir) {
684        let mut tasks = HashSet::new();
685        for task in entries
686            .into_iter()
687            .filter_map(|entry| get_all_pid_entries(Some(name), Some(pid), entry.ok()?, data))
688        {
689            tasks.insert(task);
690        }
691        Some(tasks)
692    } else {
693        None
694    };
695
696    data.push(ProcAndTasks {
697        pid,
698        parent_pid,
699        path: entry,
700        tasks,
701    });
702    Some(pid)
703}
704
705#[cfg(feature = "multithread")]
706#[inline]
707pub(crate) fn iter<T>(val: T) -> rayon::iter::IterBridge<T>
708where
709    T: rayon::iter::ParallelBridge,
710{
711    val.par_bridge()
712}
713
714#[cfg(not(feature = "multithread"))]
715#[inline]
716pub(crate) fn iter<T>(val: T) -> T
717where
718    T: Iterator,
719{
720    val
721}
722
723pub(crate) fn refresh_procs(
724    proc_list: &mut HashMap<Pid, Process>,
725    path: &Path,
726    uptime: u64,
727    info: &SystemInfo,
728    processes_to_update: ProcessesToUpdate<'_>,
729    refresh_kind: ProcessRefreshKind,
730) -> usize {
731    #[cfg(feature = "multithread")]
732    use rayon::iter::ParallelIterator;
733
734    #[inline(always)]
735    fn real_filter(e: &ProcAndTasks, filter: &[Pid]) -> bool {
736        filter.contains(&e.pid)
737    }
738
739    #[inline(always)]
740    fn empty_filter(_e: &ProcAndTasks, _filter: &[Pid]) -> bool {
741        true
742    }
743
744    #[allow(clippy::type_complexity)]
745    let (filter, filter_callback): (
746        &[Pid],
747        &(dyn Fn(&ProcAndTasks, &[Pid]) -> bool + Sync + Send),
748    ) = match processes_to_update {
749        ProcessesToUpdate::All => (&[], &empty_filter),
750        ProcessesToUpdate::Some(pids) => {
751            if pids.is_empty() {
752                return 0;
753            }
754            (pids, &real_filter)
755        }
756    };
757
758    let nb_updated = AtomicUsize::new(0);
759
760    // FIXME: To prevent retrieving a task more than once (it can be listed in `/proc/[PID]/task`
761    // subfolder and directly in `/proc` at the same time), might be interesting to use a `HashSet`.
762    let procs = {
763        let d = match fs::read_dir(path) {
764            Ok(d) => d,
765            Err(_err) => {
766                sysinfo_debug!("Failed to read folder {path:?}: {_err:?}");
767                return 0;
768            }
769        };
770        let proc_list = Wrap(UnsafeCell::new(proc_list));
771
772        iter(d)
773            .map(|entry| {
774                let Ok(entry) = entry else { return Vec::new() };
775                let mut entries = Vec::new();
776                get_all_pid_entries(None, None, entry, &mut entries);
777                entries
778            })
779            .flatten()
780            .filter(|e| filter_callback(e, filter))
781            .filter_map(|e| {
782                let (mut p, _) = _get_process_data(
783                    e.path.as_path(),
784                    proc_list.get(),
785                    e.pid,
786                    e.parent_pid,
787                    uptime,
788                    info,
789                    refresh_kind,
790                )
791                .ok()?;
792                nb_updated.fetch_add(1, Ordering::Relaxed);
793                if let Some(ref mut p) = p {
794                    p.inner.tasks = e.tasks;
795                }
796                p
797            })
798            .collect::<Vec<_>>()
799    };
800    for proc_ in procs {
801        proc_list.insert(proc_.pid(), proc_);
802    }
803    nb_updated.into_inner()
804}
805
806// FIXME: To be removed once MSRV for this crate is 1.80 nd use the `trim_ascii()` method instead.
807fn trim_ascii(mut bytes: &[u8]) -> &[u8] {
808    // Code from Rust code library.
809    while let [rest @ .., last] = bytes {
810        if last.is_ascii_whitespace() {
811            bytes = rest;
812        } else {
813            break;
814        }
815    }
816    while let [first, rest @ ..] = bytes {
817        if first.is_ascii_whitespace() {
818            bytes = rest;
819        } else {
820            break;
821        }
822    }
823    bytes
824}
825
826fn copy_from_file(entry: &Path) -> Vec<OsString> {
827    match File::open(entry) {
828        Ok(mut f) => {
829            let mut data = Vec::with_capacity(16_384);
830
831            if let Err(_e) = f.read_to_end(&mut data) {
832                sysinfo_debug!("Failed to read file in `copy_from_file`: {:?}", _e);
833                Vec::new()
834            } else {
835                let mut out = Vec::with_capacity(10);
836                let mut data = data.as_slice();
837                while let Some(pos) = data.iter().position(|c| *c == 0) {
838                    let s = trim_ascii(&data[..pos]);
839                    if !s.is_empty() {
840                        out.push(OsStr::from_bytes(s).to_os_string());
841                    }
842                    data = &data[pos + 1..];
843                }
844                out
845            }
846        }
847        Err(_e) => {
848            sysinfo_debug!("Failed to open file in `copy_from_file`: {:?}", _e);
849            Vec::new()
850        }
851    }
852}
853
854// Fetch tuples of real and effective UID and GID.
855fn get_uid_and_gid(file_path: &Path) -> Option<((uid_t, uid_t), (gid_t, gid_t))> {
856    let status_data = get_all_utf8_data(file_path, 16_385).ok()?;
857
858    // We're only interested in the lines starting with Uid: and Gid:
859    // here. From these lines, we're looking at the first and second entries to get
860    // the real u/gid.
861
862    let f = |h: &str, n: &str| -> (Option<uid_t>, Option<uid_t>) {
863        if h.starts_with(n) {
864            let mut ids = h.split_whitespace();
865            let real = ids.nth(1).unwrap_or("0").parse().ok();
866            let effective = ids.next().unwrap_or("0").parse().ok();
867
868            (real, effective)
869        } else {
870            (None, None)
871        }
872    };
873    let mut uid = None;
874    let mut effective_uid = None;
875    let mut gid = None;
876    let mut effective_gid = None;
877    for line in status_data.lines() {
878        if let (Some(real), Some(effective)) = f(line, "Uid:") {
879            debug_assert!(uid.is_none() && effective_uid.is_none());
880            uid = Some(real);
881            effective_uid = Some(effective);
882        } else if let (Some(real), Some(effective)) = f(line, "Gid:") {
883            debug_assert!(gid.is_none() && effective_gid.is_none());
884            gid = Some(real);
885            effective_gid = Some(effective);
886        } else {
887            continue;
888        }
889        if uid.is_some() && gid.is_some() {
890            break;
891        }
892    }
893    match (uid, effective_uid, gid, effective_gid) {
894        (Some(uid), Some(effective_uid), Some(gid), Some(effective_gid)) => {
895            Some(((uid, effective_uid), (gid, effective_gid)))
896        }
897        _ => None,
898    }
899}
900
901struct Parts<'a> {
902    str_parts: Vec<&'a str>,
903    short_exe: &'a [u8],
904}
905
906fn parse_stat_file(data: &[u8]) -> Option<Parts<'_>> {
907    // The stat file is "interesting" to parse, because spaces cannot
908    // be used as delimiters. The second field stores the command name
909    // surrounded by parentheses. Unfortunately, whitespace and
910    // parentheses are legal parts of the command, so parsing has to
911    // proceed like this: The first field is delimited by the first
912    // whitespace, the second field is everything until the last ')'
913    // in the entire string. All other fields are delimited by
914    // whitespace.
915
916    let mut str_parts = Vec::with_capacity(51);
917    let mut data_it = data.splitn(2, |&b| b == b' ');
918    str_parts.push(str::from_utf8(data_it.next()?).ok()?);
919    let mut data_it = data_it.next()?.rsplitn(2, |&b| b == b')');
920    let data = str::from_utf8(data_it.next()?).ok()?;
921    let short_exe = data_it.next()?;
922    str_parts.extend(data.split_whitespace());
923    Some(Parts {
924        str_parts,
925        short_exe: short_exe.strip_prefix(b"(").unwrap_or(short_exe),
926    })
927}
928
929/// Type used to correctly handle the `REMAINING_FILES` global.
930struct FileCounter(File);
931
932impl FileCounter {
933    fn new(f: File) -> Option<Self> {
934        let any_remaining =
935            remaining_files().fetch_update(Ordering::SeqCst, Ordering::SeqCst, |remaining| {
936                if remaining > 0 {
937                    Some(remaining - 1)
938                } else {
939                    // All file descriptors we were allowed are being used.
940                    None
941                }
942            });
943
944        any_remaining.ok().map(|_| Self(f))
945    }
946}
947
948impl std::ops::Deref for FileCounter {
949    type Target = File;
950
951    fn deref(&self) -> &Self::Target {
952        &self.0
953    }
954}
955impl std::ops::DerefMut for FileCounter {
956    fn deref_mut(&mut self) -> &mut Self::Target {
957        &mut self.0
958    }
959}
960
961impl Drop for FileCounter {
962    fn drop(&mut self) {
963        remaining_files().fetch_add(1, Ordering::Relaxed);
964    }
965}