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;
5use std::fmt;
6use std::fs::{self, File};
7use std::io::Read;
8use std::path::{Path, PathBuf};
9use std::str::FromStr;
10
11use libc::{gid_t, kill, uid_t};
12
13use crate::sys::system::SystemInfo;
14use crate::sys::utils::{
15 get_all_data, get_all_data_from_file, realpath, FileCounter, PathHandler, PathPush,
16};
17use crate::utils::into_iter;
18use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid};
19
20#[doc(hidden)]
21impl From<char> for ProcessStatus {
22 fn from(status: char) -> ProcessStatus {
23 match status {
24 'R' => ProcessStatus::Run,
25 'S' => ProcessStatus::Sleep,
26 'I' => ProcessStatus::Idle,
27 'D' => ProcessStatus::UninterruptibleDiskSleep,
28 'Z' => ProcessStatus::Zombie,
29 'T' => ProcessStatus::Stop,
30 't' => ProcessStatus::Tracing,
31 'X' | 'x' => ProcessStatus::Dead,
32 'K' => ProcessStatus::Wakekill,
33 'W' => ProcessStatus::Waking,
34 'P' => ProcessStatus::Parked,
35 x: char => ProcessStatus::Unknown(x as u32),
36 }
37 }
38}
39
40impl fmt::Display for ProcessStatus {
41 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
42 f.write_str(data:match *self {
43 ProcessStatus::Idle => "Idle",
44 ProcessStatus::Run => "Runnable",
45 ProcessStatus::Sleep => "Sleeping",
46 ProcessStatus::Stop => "Stopped",
47 ProcessStatus::Zombie => "Zombie",
48 ProcessStatus::Tracing => "Tracing",
49 ProcessStatus::Dead => "Dead",
50 ProcessStatus::Wakekill => "Wakekill",
51 ProcessStatus::Waking => "Waking",
52 ProcessStatus::Parked => "Parked",
53 ProcessStatus::UninterruptibleDiskSleep => "UninterruptibleDiskSleep",
54 _ => "Unknown",
55 })
56 }
57}
58
59#[doc = include_str!("../../md_doc/process.md")]
60pub struct Process {
61 pub(crate) name: String,
62 pub(crate) cmd: Vec<String>,
63 pub(crate) exe: PathBuf,
64 pub(crate) pid: Pid,
65 parent: Option<Pid>,
66 pub(crate) environ: Vec<String>,
67 pub(crate) cwd: PathBuf,
68 pub(crate) root: PathBuf,
69 pub(crate) memory: u64,
70 pub(crate) virtual_memory: u64,
71 utime: u64,
72 stime: u64,
73 old_utime: u64,
74 old_stime: u64,
75 start_time_without_boot_time: u64,
76 start_time: u64,
77 run_time: u64,
78 pub(crate) updated: bool,
79 cpu_usage: f32,
80 user_id: Option<Uid>,
81 effective_user_id: Option<Uid>,
82 group_id: Option<Gid>,
83 effective_group_id: Option<Gid>,
84 pub(crate) status: ProcessStatus,
85 /// Tasks run by this process.
86 pub tasks: HashMap<Pid, Process>,
87 pub(crate) stat_file: Option<FileCounter>,
88 old_read_bytes: u64,
89 old_written_bytes: u64,
90 read_bytes: u64,
91 written_bytes: u64,
92}
93
94impl Process {
95 pub(crate) fn new(pid: Pid) -> Process {
96 Process {
97 name: String::with_capacity(20),
98 pid,
99 parent: None,
100 cmd: Vec::with_capacity(2),
101 environ: Vec::with_capacity(10),
102 exe: PathBuf::new(),
103 cwd: PathBuf::new(),
104 root: PathBuf::new(),
105 memory: 0,
106 virtual_memory: 0,
107 cpu_usage: 0.,
108 utime: 0,
109 stime: 0,
110 old_utime: 0,
111 old_stime: 0,
112 updated: true,
113 start_time_without_boot_time: 0,
114 start_time: 0,
115 run_time: 0,
116 user_id: None,
117 effective_user_id: None,
118 group_id: None,
119 effective_group_id: None,
120 status: ProcessStatus::Unknown(0),
121 tasks: if pid.0 == 0 {
122 HashMap::with_capacity(1000)
123 } else {
124 HashMap::new()
125 },
126 stat_file: None,
127 old_read_bytes: 0,
128 old_written_bytes: 0,
129 read_bytes: 0,
130 written_bytes: 0,
131 }
132 }
133}
134
135impl ProcessExt for Process {
136 fn kill_with(&self, signal: Signal) -> Option<bool> {
137 let c_signal = super::system::convert_signal(signal)?;
138 unsafe { Some(kill(self.pid.0, c_signal) == 0) }
139 }
140
141 fn name(&self) -> &str {
142 &self.name
143 }
144
145 fn cmd(&self) -> &[String] {
146 &self.cmd
147 }
148
149 fn exe(&self) -> &Path {
150 self.exe.as_path()
151 }
152
153 fn pid(&self) -> Pid {
154 self.pid
155 }
156
157 fn environ(&self) -> &[String] {
158 &self.environ
159 }
160
161 fn cwd(&self) -> &Path {
162 self.cwd.as_path()
163 }
164
165 fn root(&self) -> &Path {
166 self.root.as_path()
167 }
168
169 fn memory(&self) -> u64 {
170 self.memory
171 }
172
173 fn virtual_memory(&self) -> u64 {
174 self.virtual_memory
175 }
176
177 fn parent(&self) -> Option<Pid> {
178 self.parent
179 }
180
181 fn status(&self) -> ProcessStatus {
182 self.status
183 }
184
185 fn start_time(&self) -> u64 {
186 self.start_time
187 }
188
189 fn run_time(&self) -> u64 {
190 self.run_time
191 }
192
193 fn cpu_usage(&self) -> f32 {
194 self.cpu_usage
195 }
196
197 fn disk_usage(&self) -> DiskUsage {
198 DiskUsage {
199 written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
200 total_written_bytes: self.written_bytes,
201 read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
202 total_read_bytes: self.read_bytes,
203 }
204 }
205
206 fn user_id(&self) -> Option<&Uid> {
207 self.user_id.as_ref()
208 }
209
210 fn effective_user_id(&self) -> Option<&Uid> {
211 self.effective_user_id.as_ref()
212 }
213
214 fn group_id(&self) -> Option<Gid> {
215 self.group_id
216 }
217
218 fn effective_group_id(&self) -> Option<Gid> {
219 self.effective_group_id
220 }
221
222 fn wait(&self) {
223 let mut status = 0;
224 // attempt waiting
225 unsafe {
226 if retry_eintr!(libc::waitpid(self.pid.0, &mut status, 0)) < 0 {
227 // attempt failed (non-child process) so loop until process ends
228 let duration = std::time::Duration::from_millis(10);
229 while kill(self.pid.0, 0) == 0 {
230 std::thread::sleep(duration);
231 }
232 }
233 }
234 }
235
236 fn session_id(&self) -> Option<Pid> {
237 unsafe {
238 let session_id = libc::getsid(self.pid.0);
239 if session_id < 0 {
240 None
241 } else {
242 Some(Pid(session_id))
243 }
244 }
245 }
246}
247
248pub(crate) fn compute_cpu_usage(p: &mut Process, total_time: f32, max_value: f32) {
249 // First time updating the values without reference, wait for a second cycle to update cpu_usage
250 if p.old_utime == 0 && p.old_stime == 0 {
251 return;
252 }
253
254 // We use `max_value` to ensure that the process CPU usage will never get bigger than:
255 // `"number of CPUs" * 100.`
256 p.cpu_usage = (pu64
257 .utime
258 .saturating_sub(p.old_utime)
259 .saturating_add(p.stime.saturating_sub(p.old_stime)) as f32
260 / total_time
261 * 100.)
262 .min(max_value);
263
264 for task: &mut Process in p.tasks.values_mut() {
265 compute_cpu_usage(p:task, total_time, max_value);
266 }
267}
268
269pub(crate) fn unset_updated(p: &mut Process) {
270 p.updated = false;
271 for task: &mut Process in p.tasks.values_mut() {
272 unset_updated(task);
273 }
274}
275
276pub(crate) fn set_time(p: &mut Process, utime: u64, stime: u64) {
277 p.old_utime = p.utime;
278 p.old_stime = p.stime;
279 p.utime = utime;
280 p.stime = stime;
281 p.updated = true;
282}
283
284pub(crate) fn update_process_disk_activity(p: &mut Process, path: &Path) {
285 let data = match get_all_data(path.join("io"), 16_384) {
286 Ok(d) => d,
287 Err(_) => return,
288 };
289 let mut done = 0;
290 for line in data.split('\n') {
291 let mut parts = line.split(": ");
292 match parts.next() {
293 Some("read_bytes") => {
294 p.old_read_bytes = p.read_bytes;
295 p.read_bytes = parts
296 .next()
297 .and_then(|x| x.parse::<u64>().ok())
298 .unwrap_or(p.old_read_bytes);
299 }
300 Some("write_bytes") => {
301 p.old_written_bytes = p.written_bytes;
302 p.written_bytes = parts
303 .next()
304 .and_then(|x| x.parse::<u64>().ok())
305 .unwrap_or(p.old_written_bytes);
306 }
307 _ => continue,
308 }
309 done += 1;
310 if done > 1 {
311 // No need to continue the reading.
312 break;
313 }
314 }
315}
316
317struct Wrap<'a, T>(UnsafeCell<&'a mut T>);
318
319impl<'a, T> Wrap<'a, T> {
320 fn get(&self) -> &'a mut T {
321 unsafe { *(self.0.get()) }
322 }
323}
324
325#[allow(clippy::non_send_fields_in_send_ty)]
326unsafe impl<'a, T> Send for Wrap<'a, T> {}
327unsafe impl<'a, T> Sync for Wrap<'a, T> {}
328
329#[inline(always)]
330fn compute_start_time_without_boot_time(parts: &[&str], info: &SystemInfo) -> u64 {
331 // To be noted that the start time is invalid here, it still needs to be converted into
332 // "real" time.
333 u64::from_str(parts[21]).unwrap_or(default:0) / info.clock_cycle
334}
335
336fn _get_stat_data(path: &Path, stat_file: &mut Option<FileCounter>) -> Result<String, ()> {
337 let mut file: File = File::open(path.join("stat")).map_err(|_| ())?;
338 let data: String = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?;
339 *stat_file = FileCounter::new(file);
340 Ok(data)
341}
342
343#[inline(always)]
344fn get_status(p: &mut Process, part: &str) {
345 p.status = partOption
346 .chars()
347 .next()
348 .map(ProcessStatus::from)
349 .unwrap_or_else(|| ProcessStatus::Unknown(0));
350}
351
352fn refresh_user_group_ids<P: PathPush>(p: &mut Process, path: &mut P) {
353 if let Some(((user_id: u32, effective_user_id: u32), (group_id: u32, effective_group_id: u32))) =
354 get_uid_and_gid(file_path:path.join("status"))
355 {
356 p.user_id = Some(Uid(user_id));
357 p.effective_user_id = Some(Uid(effective_user_id));
358 p.group_id = Some(Gid(group_id));
359 p.effective_group_id = Some(Gid(effective_group_id));
360 }
361}
362
363fn retrieve_all_new_process_info(
364 pid: Pid,
365 proc_list: &Process,
366 parts: &[&str],
367 path: &Path,
368 info: &SystemInfo,
369 refresh_kind: ProcessRefreshKind,
370 uptime: u64,
371) -> Process {
372 let mut p = Process::new(pid);
373 let mut tmp = PathHandler::new(path);
374 let name = parts[1];
375
376 p.parent = if proc_list.pid.0 != 0 {
377 Some(proc_list.pid)
378 } else {
379 match Pid::from_str(parts[3]) {
380 Ok(p) if p.0 != 0 => Some(p),
381 _ => None,
382 }
383 };
384
385 p.start_time_without_boot_time = compute_start_time_without_boot_time(parts, info);
386 p.start_time = p
387 .start_time_without_boot_time
388 .saturating_add(info.boot_time);
389
390 get_status(&mut p, parts[2]);
391
392 if refresh_kind.user() {
393 refresh_user_group_ids(&mut p, &mut tmp);
394 }
395
396 p.name = name.into();
397
398 match tmp.join("exe").read_link() {
399 Ok(exe_path) => {
400 p.exe = exe_path;
401 }
402 Err(_) => {
403 // Do not use cmd[0] because it is not the same thing.
404 // See https://github.com/GuillaumeGomez/sysinfo/issues/697.
405 p.exe = PathBuf::new()
406 }
407 }
408
409 p.cmd = copy_from_file(tmp.join("cmdline"));
410 p.environ = copy_from_file(tmp.join("environ"));
411 p.cwd = realpath(tmp.join("cwd"));
412 p.root = realpath(tmp.join("root"));
413
414 update_time_and_memory(
415 path,
416 &mut p,
417 parts,
418 proc_list.memory,
419 proc_list.virtual_memory,
420 uptime,
421 info,
422 refresh_kind,
423 );
424 if refresh_kind.disk_usage() {
425 update_process_disk_activity(&mut p, path);
426 }
427 p
428}
429
430pub(crate) fn _get_process_data(
431 path: &Path,
432 proc_list: &mut Process,
433 pid: Pid,
434 uptime: u64,
435 info: &SystemInfo,
436 refresh_kind: ProcessRefreshKind,
437) -> Result<(Option<Process>, Pid), ()> {
438 let pid = match path.file_name().and_then(|x| x.to_str()).map(Pid::from_str) {
439 // If `pid` and `nb` are the same, it means the file is linking to itself so we skip it.
440 //
441 // It's because when reading `/proc/[PID]` folder, we then go through the folders inside it.
442 // Then, if we encounter a sub-folder with the same PID as the parent, then it's a link to
443 // the current folder we already did read so no need to do anything.
444 Some(Ok(nb)) if nb != pid => nb,
445 _ => return Err(()),
446 };
447
448 let parent_memory = proc_list.memory;
449 let parent_virtual_memory = proc_list.virtual_memory;
450
451 let data;
452 let parts = if let Some(ref mut entry) = proc_list.tasks.get_mut(&pid) {
453 data = if let Some(mut f) = entry.stat_file.take() {
454 match get_all_data_from_file(&mut f, 1024) {
455 Ok(data) => {
456 // Everything went fine, we put back the file descriptor.
457 entry.stat_file = Some(f);
458 data
459 }
460 Err(_) => {
461 // It's possible that the file descriptor is no longer valid in case the
462 // original process was terminated and another one took its place.
463 _get_stat_data(path, &mut entry.stat_file)?
464 }
465 }
466 } else {
467 _get_stat_data(path, &mut entry.stat_file)?
468 };
469 let parts = parse_stat_file(&data).ok_or(())?;
470 let start_time_without_boot_time = compute_start_time_without_boot_time(&parts, info);
471
472 // It's possible that a new process took this same PID when the "original one" terminated.
473 // If the start time differs, then it means it's not the same process anymore and that we
474 // need to get all its information, hence why we check it here.
475 if start_time_without_boot_time == entry.start_time_without_boot_time {
476 get_status(entry, parts[2]);
477 update_time_and_memory(
478 path,
479 entry,
480 &parts,
481 parent_memory,
482 parent_virtual_memory,
483 uptime,
484 info,
485 refresh_kind,
486 );
487 if refresh_kind.disk_usage() {
488 update_process_disk_activity(entry, path);
489 }
490 if refresh_kind.user() && entry.user_id.is_none() {
491 refresh_user_group_ids(entry, &mut PathBuf::from(path));
492 }
493 return Ok((None, pid));
494 }
495 parts
496 } else {
497 let mut stat_file = None;
498 let data = _get_stat_data(path, &mut stat_file)?;
499 let parts = parse_stat_file(&data).ok_or(())?;
500
501 let mut p =
502 retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime);
503 p.stat_file = stat_file;
504 return Ok((Some(p), pid));
505 };
506
507 // If we're here, it means that the PID still exists but it's a different process.
508 let p = retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime);
509 match proc_list.tasks.get_mut(&pid) {
510 Some(ref mut entry) => **entry = p,
511 // If it ever enters this case, it means that the process was removed from the HashMap
512 // in-between with the usage of dark magic.
513 None => unreachable!(),
514 }
515 // Since this PID is already in the HashMap, no need to add it again.
516 Ok((None, pid))
517}
518
519#[allow(clippy::too_many_arguments)]
520fn update_time_and_memory(
521 path: &Path,
522 entry: &mut Process,
523 parts: &[&str],
524 parent_memory: u64,
525 parent_virtual_memory: u64,
526 uptime: u64,
527 info: &SystemInfo,
528 refresh_kind: ProcessRefreshKind,
529) {
530 {
531 // rss
532 entry.memory = u64::from_str(parts[23])
533 .unwrap_or(0)
534 .saturating_mul(info.page_size_kb);
535 if entry.memory >= parent_memory {
536 entry.memory -= parent_memory;
537 }
538 // vsz correspond to the Virtual memory size in bytes.
539 // see: https://man7.org/linux/man-pages/man5/proc.5.html
540 entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0);
541 if entry.virtual_memory >= parent_virtual_memory {
542 entry.virtual_memory -= parent_virtual_memory;
543 }
544 set_time(
545 entry,
546 u64::from_str(parts[13]).unwrap_or(0),
547 u64::from_str(parts[14]).unwrap_or(0),
548 );
549 entry.run_time = uptime.saturating_sub(entry.start_time_without_boot_time);
550 }
551 refresh_procs(
552 entry,
553 &path.join("task"),
554 entry.pid,
555 uptime,
556 info,
557 refresh_kind,
558 );
559}
560
561pub(crate) fn refresh_procs(
562 proc_list: &mut Process,
563 path: &Path,
564 pid: Pid,
565 uptime: u64,
566 info: &SystemInfo,
567 refresh_kind: ProcessRefreshKind,
568) -> bool {
569 let d = match fs::read_dir(path) {
570 Ok(d) => d,
571 Err(_) => return false,
572 };
573 let folders = d
574 .filter_map(|entry| {
575 let entry = entry.ok()?;
576 let entry = entry.path();
577
578 if entry.is_dir() {
579 Some(entry)
580 } else {
581 None
582 }
583 })
584 .collect::<Vec<_>>();
585 if pid.0 == 0 {
586 let proc_list = Wrap(UnsafeCell::new(proc_list));
587
588 #[cfg(feature = "multithread")]
589 use rayon::iter::ParallelIterator;
590
591 into_iter(folders)
592 .filter_map(|e| {
593 let (p, _) = _get_process_data(
594 e.as_path(),
595 proc_list.get(),
596 pid,
597 uptime,
598 info,
599 refresh_kind,
600 )
601 .ok()?;
602 p
603 })
604 .collect::<Vec<_>>()
605 } else {
606 let mut updated_pids = Vec::with_capacity(folders.len());
607 let new_tasks = folders
608 .iter()
609 .filter_map(|e| {
610 let (p, pid) =
611 _get_process_data(e.as_path(), proc_list, pid, uptime, info, refresh_kind)
612 .ok()?;
613 updated_pids.push(pid);
614 p
615 })
616 .collect::<Vec<_>>();
617 // Sub-tasks are not cleaned up outside so we do it here directly.
618 proc_list
619 .tasks
620 .retain(|&pid, _| updated_pids.iter().any(|&x| x == pid));
621 new_tasks
622 }
623 .into_iter()
624 .for_each(|e| {
625 proc_list.tasks.insert(e.pid(), e);
626 });
627 true
628}
629
630fn copy_from_file(entry: &Path) -> Vec<String> {
631 match File::open(entry) {
632 Ok(mut f) => {
633 let mut data = Vec::with_capacity(16_384);
634
635 if let Err(_e) = f.read_to_end(&mut data) {
636 sysinfo_debug!("Failed to read file in `copy_from_file`: {:?}", _e);
637 Vec::new()
638 } else {
639 let mut out = Vec::with_capacity(20);
640 let mut start = 0;
641 for (pos, x) in data.iter().enumerate() {
642 if *x == 0 {
643 if pos - start >= 1 {
644 if let Ok(s) =
645 std::str::from_utf8(&data[start..pos]).map(|x| x.trim().to_owned())
646 {
647 out.push(s);
648 }
649 }
650 start = pos + 1; // to keeping prevent '\0'
651 }
652 }
653 out
654 }
655 }
656 Err(_e) => {
657 sysinfo_debug!("Failed to open file in `copy_from_file`: {:?}", _e);
658 Vec::new()
659 }
660 }
661}
662
663// Fetch tuples of real and effective UID and GID.
664fn get_uid_and_gid(file_path: &Path) -> Option<((uid_t, uid_t), (gid_t, gid_t))> {
665 let status_data = get_all_data(file_path, 16_385).ok()?;
666
667 // We're only interested in the lines starting with Uid: and Gid:
668 // here. From these lines, we're looking at the first and second entries to get
669 // the real u/gid.
670
671 let f = |h: &str, n: &str| -> (Option<uid_t>, Option<uid_t>) {
672 if h.starts_with(n) {
673 let mut ids = h.split_whitespace();
674 let real = ids.nth(1).unwrap_or("0").parse().ok();
675 let effective = ids.next().unwrap_or("0").parse().ok();
676
677 (real, effective)
678 } else {
679 (None, None)
680 }
681 };
682 let mut uid = None;
683 let mut effective_uid = None;
684 let mut gid = None;
685 let mut effective_gid = None;
686 for line in status_data.lines() {
687 if let (Some(real), Some(effective)) = f(line, "Uid:") {
688 debug_assert!(uid.is_none() && effective_uid.is_none());
689 uid = Some(real);
690 effective_uid = Some(effective);
691 } else if let (Some(real), Some(effective)) = f(line, "Gid:") {
692 debug_assert!(gid.is_none() && effective_gid.is_none());
693 gid = Some(real);
694 effective_gid = Some(effective);
695 } else {
696 continue;
697 }
698 if uid.is_some() && gid.is_some() {
699 break;
700 }
701 }
702 match (uid, effective_uid, gid, effective_gid) {
703 (Some(uid), Some(effective_uid), Some(gid), Some(effective_gid)) => {
704 Some(((uid, effective_uid), (gid, effective_gid)))
705 }
706 _ => None,
707 }
708}
709
710fn parse_stat_file(data: &str) -> Option<Vec<&str>> {
711 // The stat file is "interesting" to parse, because spaces cannot
712 // be used as delimiters. The second field stores the command name
713 // surrounded by parentheses. Unfortunately, whitespace and
714 // parentheses are legal parts of the command, so parsing has to
715 // proceed like this: The first field is delimited by the first
716 // whitespace, the second field is everything until the last ')'
717 // in the entire string. All other fields are delimited by
718 // whitespace.
719
720 let mut parts: Vec<&str> = Vec::with_capacity(52);
721 let mut data_it: SplitN<'_, char> = data.splitn(n:2, pat:' ');
722 parts.push(data_it.next()?);
723 let mut data_it: RSplitN<'_, char> = data_it.next()?.rsplitn(n:2, pat:')');
724 let data: &str = data_it.next()?;
725 parts.push(data_it.next()?);
726 parts.extend(iter:data.split_whitespace());
727 // Remove command name '('
728 if let Some(name: &str) = parts[1].strip_prefix('(') {
729 parts[1] = name;
730 }
731 Some(parts)
732}
733