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