summaryrefslogtreecommitdiffstats
path: root/vendor/sysinfo-0.26.7/src/linux/process.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-30 03:59:35 +0000
commitd1b2d29528b7794b41e66fc2136e395a02f8529b (patch)
treea4a17504b260206dec3cf55b2dca82929a348ac2 /vendor/sysinfo-0.26.7/src/linux/process.rs
parentReleasing progress-linux version 1.72.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.tar.xz
rustc-d1b2d29528b7794b41e66fc2136e395a02f8529b.zip
Merging upstream version 1.73.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/sysinfo-0.26.7/src/linux/process.rs')
-rw-r--r--vendor/sysinfo-0.26.7/src/linux/process.rs711
1 files changed, 711 insertions, 0 deletions
diff --git a/vendor/sysinfo-0.26.7/src/linux/process.rs b/vendor/sysinfo-0.26.7/src/linux/process.rs
new file mode 100644
index 000000000..d7d61b5bc
--- /dev/null
+++ b/vendor/sysinfo-0.26.7/src/linux/process.rs
@@ -0,0 +1,711 @@
+// Take a look at the license at the top of the repository in the LICENSE file.
+
+use std::cell::UnsafeCell;
+use std::collections::HashMap;
+use std::fmt;
+use std::fs::{self, File};
+use std::io::Read;
+use std::mem::MaybeUninit;
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+
+use libc::{gid_t, kill, uid_t};
+
+use crate::sys::system::SystemInfo;
+use crate::sys::utils::{
+ get_all_data, get_all_data_from_file, realpath, FileCounter, PathHandler, PathPush,
+};
+use crate::utils::into_iter;
+use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid};
+
+#[doc(hidden)]
+impl From<u32> for ProcessStatus {
+ fn from(status: u32) -> ProcessStatus {
+ match status {
+ 1 => ProcessStatus::Idle,
+ 2 => ProcessStatus::Run,
+ 3 => ProcessStatus::Sleep,
+ 4 => ProcessStatus::Stop,
+ 5 => ProcessStatus::Zombie,
+ x => ProcessStatus::Unknown(x),
+ }
+ }
+}
+
+#[doc(hidden)]
+impl From<char> for ProcessStatus {
+ fn from(status: char) -> ProcessStatus {
+ match status {
+ 'R' => ProcessStatus::Run,
+ 'S' => ProcessStatus::Sleep,
+ 'D' => ProcessStatus::Idle,
+ 'Z' => ProcessStatus::Zombie,
+ 'T' => ProcessStatus::Stop,
+ 't' => ProcessStatus::Tracing,
+ 'X' | 'x' => ProcessStatus::Dead,
+ 'K' => ProcessStatus::Wakekill,
+ 'W' => ProcessStatus::Waking,
+ 'P' => ProcessStatus::Parked,
+ x => ProcessStatus::Unknown(x as u32),
+ }
+ }
+}
+
+impl fmt::Display for ProcessStatus {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.write_str(match *self {
+ ProcessStatus::Idle => "Idle",
+ ProcessStatus::Run => "Runnable",
+ ProcessStatus::Sleep => "Sleeping",
+ ProcessStatus::Stop => "Stopped",
+ ProcessStatus::Zombie => "Zombie",
+ ProcessStatus::Tracing => "Tracing",
+ ProcessStatus::Dead => "Dead",
+ ProcessStatus::Wakekill => "Wakekill",
+ ProcessStatus::Waking => "Waking",
+ ProcessStatus::Parked => "Parked",
+ _ => "Unknown",
+ })
+ }
+}
+
+#[doc = include_str!("../../md_doc/process.md")]
+pub struct Process {
+ pub(crate) name: String,
+ pub(crate) cmd: Vec<String>,
+ pub(crate) exe: PathBuf,
+ pub(crate) pid: Pid,
+ parent: Option<Pid>,
+ pub(crate) environ: Vec<String>,
+ pub(crate) cwd: PathBuf,
+ pub(crate) root: PathBuf,
+ pub(crate) memory: u64,
+ pub(crate) virtual_memory: u64,
+ utime: u64,
+ stime: u64,
+ old_utime: u64,
+ old_stime: u64,
+ start_time_without_boot_time: u64,
+ start_time: u64,
+ run_time: u64,
+ pub(crate) updated: bool,
+ cpu_usage: f32,
+ user_id: Option<Uid>,
+ group_id: Option<Gid>,
+ pub(crate) status: ProcessStatus,
+ /// Tasks run by this process.
+ pub tasks: HashMap<Pid, Process>,
+ pub(crate) stat_file: Option<FileCounter>,
+ old_read_bytes: u64,
+ old_written_bytes: u64,
+ read_bytes: u64,
+ written_bytes: u64,
+}
+
+impl Process {
+ pub(crate) fn new(pid: Pid) -> Process {
+ Process {
+ name: String::with_capacity(20),
+ pid,
+ parent: None,
+ cmd: Vec::with_capacity(2),
+ environ: Vec::with_capacity(10),
+ exe: PathBuf::new(),
+ cwd: PathBuf::new(),
+ root: PathBuf::new(),
+ memory: 0,
+ virtual_memory: 0,
+ cpu_usage: 0.,
+ utime: 0,
+ stime: 0,
+ old_utime: 0,
+ old_stime: 0,
+ updated: true,
+ start_time_without_boot_time: 0,
+ start_time: 0,
+ run_time: 0,
+ user_id: None,
+ group_id: None,
+ status: ProcessStatus::Unknown(0),
+ tasks: if pid.0 == 0 {
+ HashMap::with_capacity(1000)
+ } else {
+ HashMap::new()
+ },
+ stat_file: None,
+ old_read_bytes: 0,
+ old_written_bytes: 0,
+ read_bytes: 0,
+ written_bytes: 0,
+ }
+ }
+}
+
+impl ProcessExt for Process {
+ fn kill_with(&self, signal: Signal) -> Option<bool> {
+ let c_signal = super::system::convert_signal(signal)?;
+ unsafe { Some(kill(self.pid.0, c_signal) == 0) }
+ }
+
+ fn name(&self) -> &str {
+ &self.name
+ }
+
+ fn cmd(&self) -> &[String] {
+ &self.cmd
+ }
+
+ fn exe(&self) -> &Path {
+ self.exe.as_path()
+ }
+
+ fn pid(&self) -> Pid {
+ self.pid
+ }
+
+ fn environ(&self) -> &[String] {
+ &self.environ
+ }
+
+ fn cwd(&self) -> &Path {
+ self.cwd.as_path()
+ }
+
+ fn root(&self) -> &Path {
+ self.root.as_path()
+ }
+
+ fn memory(&self) -> u64 {
+ self.memory
+ }
+
+ fn virtual_memory(&self) -> u64 {
+ self.virtual_memory
+ }
+
+ fn parent(&self) -> Option<Pid> {
+ self.parent
+ }
+
+ fn status(&self) -> ProcessStatus {
+ self.status
+ }
+
+ fn start_time(&self) -> u64 {
+ self.start_time
+ }
+
+ fn run_time(&self) -> u64 {
+ self.run_time
+ }
+
+ fn cpu_usage(&self) -> f32 {
+ self.cpu_usage
+ }
+
+ fn disk_usage(&self) -> DiskUsage {
+ DiskUsage {
+ written_bytes: self.written_bytes.saturating_sub(self.old_written_bytes),
+ total_written_bytes: self.written_bytes,
+ read_bytes: self.read_bytes.saturating_sub(self.old_read_bytes),
+ total_read_bytes: self.read_bytes,
+ }
+ }
+
+ fn user_id(&self) -> Option<&Uid> {
+ self.user_id.as_ref()
+ }
+
+ fn group_id(&self) -> Option<Gid> {
+ self.group_id
+ }
+
+ fn wait(&self) {
+ let mut status = 0;
+ // attempt waiting
+ unsafe {
+ if libc::waitpid(self.pid.0, &mut status, 0) < 0 {
+ // attempt failed (non-child process) so loop until process ends
+ let duration = std::time::Duration::from_millis(10);
+ while kill(self.pid.0, 0) == 0 {
+ std::thread::sleep(duration);
+ }
+ }
+ }
+ }
+}
+
+pub(crate) fn compute_cpu_usage(p: &mut Process, total_time: f32, max_value: f32) {
+ // First time updating the values without reference, wait for a second cycle to update cpu_usage
+ if p.old_utime == 0 && p.old_stime == 0 {
+ return;
+ }
+
+ // We use `max_value` to ensure that the process CPU usage will never get bigger than:
+ // `"number of CPUs" * 100.`
+ p.cpu_usage = ((p.utime.saturating_sub(p.old_utime) + p.stime.saturating_sub(p.old_stime))
+ as f32
+ / total_time
+ * 100.)
+ .min(max_value);
+}
+
+pub(crate) fn set_time(p: &mut Process, utime: u64, stime: u64) {
+ p.old_utime = p.utime;
+ p.old_stime = p.stime;
+ p.utime = utime;
+ p.stime = stime;
+ p.updated = true;
+}
+
+pub(crate) fn update_process_disk_activity(p: &mut Process, path: &Path) {
+ let data = match get_all_data(path.join("io"), 16_384) {
+ Ok(d) => d,
+ Err(_) => return,
+ };
+ let mut done = 0;
+ for line in data.split('\n') {
+ let mut parts = line.split(": ");
+ match parts.next() {
+ Some("read_bytes") => {
+ p.old_read_bytes = p.read_bytes;
+ p.read_bytes = parts
+ .next()
+ .and_then(|x| x.parse::<u64>().ok())
+ .unwrap_or(p.old_read_bytes);
+ }
+ Some("write_bytes") => {
+ p.old_written_bytes = p.written_bytes;
+ p.written_bytes = parts
+ .next()
+ .and_then(|x| x.parse::<u64>().ok())
+ .unwrap_or(p.old_written_bytes);
+ }
+ _ => continue,
+ }
+ done += 1;
+ if done > 1 {
+ // No need to continue the reading.
+ break;
+ }
+ }
+}
+
+struct Wrap<'a, T>(UnsafeCell<&'a mut T>);
+
+impl<'a, T> Wrap<'a, T> {
+ fn get(&self) -> &'a mut T {
+ unsafe { *(self.0.get()) }
+ }
+}
+
+#[allow(clippy::non_send_fields_in_send_ty)]
+unsafe impl<'a, T> Send for Wrap<'a, T> {}
+unsafe impl<'a, T> Sync for Wrap<'a, T> {}
+
+#[inline(always)]
+fn compute_start_time_without_boot_time(parts: &[&str], info: &SystemInfo) -> u64 {
+ // To be noted that the start time is invalid here, it still needs to be converted into
+ // "real" time.
+ u64::from_str(parts[21]).unwrap_or(0) / info.clock_cycle
+}
+
+fn _get_stat_data(path: &Path, stat_file: &mut Option<FileCounter>) -> Result<String, ()> {
+ let mut file = File::open(path.join("stat")).map_err(|_| ())?;
+ let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?;
+ *stat_file = FileCounter::new(file);
+ Ok(data)
+}
+
+#[inline(always)]
+fn get_status(p: &mut Process, part: &str) {
+ p.status = part
+ .chars()
+ .next()
+ .map(ProcessStatus::from)
+ .unwrap_or_else(|| ProcessStatus::Unknown(0));
+}
+
+fn refresh_user_group_ids<P: PathPush>(p: &mut Process, path: &mut P) {
+ if let Some((user_id, group_id)) = get_uid_and_gid(path.join("status")) {
+ p.user_id = Some(Uid(user_id));
+ p.group_id = Some(Gid(group_id));
+ }
+}
+
+fn retrieve_all_new_process_info(
+ pid: Pid,
+ proc_list: &Process,
+ parts: &[&str],
+ path: &Path,
+ info: &SystemInfo,
+ refresh_kind: ProcessRefreshKind,
+ uptime: u64,
+) -> Process {
+ let mut p = Process::new(pid);
+ let mut tmp = PathHandler::new(path);
+ let name = parts[1];
+
+ p.parent = if proc_list.pid.0 != 0 {
+ Some(proc_list.pid)
+ } else {
+ match Pid::from_str(parts[3]) {
+ Ok(p) if p.0 != 0 => Some(p),
+ _ => None,
+ }
+ };
+
+ p.start_time_without_boot_time = compute_start_time_without_boot_time(parts, info);
+ p.start_time = p
+ .start_time_without_boot_time
+ .saturating_add(info.boot_time);
+
+ get_status(&mut p, parts[2]);
+
+ if refresh_kind.user() {
+ refresh_user_group_ids(&mut p, &mut tmp);
+ }
+
+ if proc_list.pid.0 != 0 {
+ // If we're getting information for a child, no need to get those info since we
+ // already have them...
+ p.cmd = proc_list.cmd.clone();
+ p.name = proc_list.name.clone();
+ p.environ = proc_list.environ.clone();
+ p.exe = proc_list.exe.clone();
+ p.cwd = proc_list.cwd.clone();
+ p.root = proc_list.root.clone();
+ } else {
+ p.name = name.into();
+
+ match tmp.join("exe").read_link() {
+ Ok(exe_path) => {
+ p.exe = exe_path;
+ }
+ Err(_) => {
+ // Do not use cmd[0] because it is not the same thing.
+ // See https://github.com/GuillaumeGomez/sysinfo/issues/697.
+ p.exe = PathBuf::new()
+ }
+ }
+
+ p.cmd = copy_from_file(tmp.join("cmdline"));
+ p.environ = copy_from_file(tmp.join("environ"));
+ p.cwd = realpath(tmp.join("cwd"));
+ p.root = realpath(tmp.join("root"));
+ }
+
+ update_time_and_memory(
+ path,
+ &mut p,
+ parts,
+ proc_list.memory,
+ proc_list.virtual_memory,
+ uptime,
+ info,
+ refresh_kind,
+ );
+ if refresh_kind.disk_usage() {
+ update_process_disk_activity(&mut p, path);
+ }
+ p
+}
+
+pub(crate) fn _get_process_data(
+ path: &Path,
+ proc_list: &mut Process,
+ pid: Pid,
+ uptime: u64,
+ info: &SystemInfo,
+ refresh_kind: ProcessRefreshKind,
+) -> Result<(Option<Process>, Pid), ()> {
+ let pid = match path.file_name().and_then(|x| x.to_str()).map(Pid::from_str) {
+ Some(Ok(nb)) if nb != pid => nb,
+ _ => return Err(()),
+ };
+
+ let parent_memory = proc_list.memory;
+ let parent_virtual_memory = proc_list.virtual_memory;
+
+ let data;
+ let parts = if let Some(ref mut entry) = proc_list.tasks.get_mut(&pid) {
+ data = if let Some(mut f) = entry.stat_file.take() {
+ match get_all_data_from_file(&mut f, 1024) {
+ Ok(data) => {
+ // Everything went fine, we put back the file descriptor.
+ entry.stat_file = Some(f);
+ data
+ }
+ Err(_) => {
+ // It's possible that the file descriptor is no longer valid in case the
+ // original process was terminated and another one took its place.
+ _get_stat_data(path, &mut entry.stat_file)?
+ }
+ }
+ } else {
+ _get_stat_data(path, &mut entry.stat_file)?
+ };
+ let parts = parse_stat_file(&data).ok_or(())?;
+ let start_time_without_boot_time = compute_start_time_without_boot_time(&parts, info);
+
+ // It's possible that a new process took this same PID when the "original one" terminated.
+ // If the start time differs, then it means it's not the same process anymore and that we
+ // need to get all its information, hence why we check it here.
+ if start_time_without_boot_time == entry.start_time_without_boot_time {
+ get_status(entry, parts[2]);
+ update_time_and_memory(
+ path,
+ entry,
+ &parts,
+ parent_memory,
+ parent_virtual_memory,
+ uptime,
+ info,
+ refresh_kind,
+ );
+ if refresh_kind.disk_usage() {
+ update_process_disk_activity(entry, path);
+ }
+ if refresh_kind.user() && entry.user_id.is_none() {
+ refresh_user_group_ids(entry, &mut PathBuf::from(path));
+ }
+ return Ok((None, pid));
+ }
+ parts
+ } else {
+ let mut stat_file = None;
+ let data = _get_stat_data(path, &mut stat_file)?;
+ let parts = parse_stat_file(&data).ok_or(())?;
+
+ let mut p =
+ retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime);
+ p.stat_file = stat_file;
+ return Ok((Some(p), pid));
+ };
+
+ // If we're here, it means that the PID still exists but it's a different process.
+ let p = retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime);
+ match proc_list.tasks.get_mut(&pid) {
+ Some(ref mut entry) => **entry = p,
+ // If it ever enters this case, it means that the process was removed from the HashMap
+ // in-between with the usage of dark magic.
+ None => unreachable!(),
+ }
+ // Since this PID is already in the HashMap, no need to add it again.
+ Ok((None, pid))
+}
+
+#[allow(clippy::too_many_arguments)]
+fn update_time_and_memory(
+ path: &Path,
+ entry: &mut Process,
+ parts: &[&str],
+ parent_memory: u64,
+ parent_virtual_memory: u64,
+ uptime: u64,
+ info: &SystemInfo,
+ refresh_kind: ProcessRefreshKind,
+) {
+ {
+ // rss
+ entry.memory = u64::from_str(parts[23])
+ .unwrap_or(0)
+ .saturating_mul(info.page_size_kb);
+ if entry.memory >= parent_memory {
+ entry.memory -= parent_memory;
+ }
+ // vsz correspond to the Virtual memory size in bytes.
+ // see: https://man7.org/linux/man-pages/man5/proc.5.html
+ entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0);
+ if entry.virtual_memory >= parent_virtual_memory {
+ entry.virtual_memory -= parent_virtual_memory;
+ }
+ set_time(
+ entry,
+ u64::from_str(parts[13]).unwrap_or(0),
+ u64::from_str(parts[14]).unwrap_or(0),
+ );
+ entry.run_time = uptime.saturating_sub(entry.start_time_without_boot_time);
+ }
+ refresh_procs(
+ entry,
+ &path.join("task"),
+ entry.pid,
+ uptime,
+ info,
+ refresh_kind,
+ );
+}
+
+pub(crate) fn refresh_procs(
+ proc_list: &mut Process,
+ path: &Path,
+ pid: Pid,
+ uptime: u64,
+ info: &SystemInfo,
+ refresh_kind: ProcessRefreshKind,
+) -> bool {
+ let d = match fs::read_dir(path) {
+ Ok(d) => d,
+ Err(_) => return false,
+ };
+ let folders = d
+ .filter_map(|entry| {
+ let entry = entry.ok()?;
+ let entry = entry.path();
+
+ if entry.is_dir() {
+ Some(entry)
+ } else {
+ None
+ }
+ })
+ .collect::<Vec<_>>();
+ if pid.0 == 0 {
+ let proc_list = Wrap(UnsafeCell::new(proc_list));
+
+ #[cfg(feature = "multithread")]
+ use rayon::iter::ParallelIterator;
+
+ into_iter(folders)
+ .filter_map(|e| {
+ let (p, _) = _get_process_data(
+ e.as_path(),
+ proc_list.get(),
+ pid,
+ uptime,
+ info,
+ refresh_kind,
+ )
+ .ok()?;
+ p
+ })
+ .collect::<Vec<_>>()
+ } else {
+ let mut updated_pids = Vec::with_capacity(folders.len());
+ let new_tasks = folders
+ .iter()
+ .filter_map(|e| {
+ let (p, pid) =
+ _get_process_data(e.as_path(), proc_list, pid, uptime, info, refresh_kind)
+ .ok()?;
+ updated_pids.push(pid);
+ p
+ })
+ .collect::<Vec<_>>();
+ // Sub-tasks are not cleaned up outside so we do it here directly.
+ proc_list
+ .tasks
+ .retain(|&pid, _| updated_pids.iter().any(|&x| x == pid));
+ new_tasks
+ }
+ .into_iter()
+ .for_each(|e| {
+ proc_list.tasks.insert(e.pid(), e);
+ });
+ true
+}
+
+fn copy_from_file(entry: &Path) -> Vec<String> {
+ match File::open(entry) {
+ Ok(mut f) => {
+ let mut data = vec![0; 16_384];
+
+ if let Ok(size) = f.read(&mut data) {
+ data.truncate(size);
+ let mut out = Vec::with_capacity(20);
+ let mut start = 0;
+ for (pos, x) in data.iter().enumerate() {
+ if *x == 0 {
+ if pos - start >= 1 {
+ if let Ok(s) =
+ std::str::from_utf8(&data[start..pos]).map(|x| x.trim().to_owned())
+ {
+ out.push(s);
+ }
+ }
+ start = pos + 1; // to keeping prevent '\0'
+ }
+ }
+ out
+ } else {
+ Vec::new()
+ }
+ }
+ Err(_) => Vec::new(),
+ }
+}
+
+fn get_uid_and_gid(file_path: &Path) -> Option<(uid_t, gid_t)> {
+ use std::os::unix::ffi::OsStrExt;
+
+ unsafe {
+ let mut sstat: MaybeUninit<libc::stat> = MaybeUninit::uninit();
+
+ let mut file_path: Vec<u8> = file_path.as_os_str().as_bytes().to_vec();
+ file_path.push(0);
+ if libc::stat(file_path.as_ptr() as *const _, sstat.as_mut_ptr()) == 0 {
+ let sstat = sstat.assume_init();
+
+ return Some((sstat.st_uid, sstat.st_gid));
+ }
+ }
+
+ let status_data = get_all_data(file_path, 16_385).ok()?;
+
+ // We're only interested in the lines starting with Uid: and Gid:
+ // here. From these lines, we're looking at the second entry to get
+ // the effective u/gid.
+
+ let f = |h: &str, n: &str| -> Option<uid_t> {
+ if h.starts_with(n) {
+ h.split_whitespace().nth(2).unwrap_or("0").parse().ok()
+ } else {
+ None
+ }
+ };
+ let mut uid = None;
+ let mut gid = None;
+ for line in status_data.lines() {
+ if let Some(u) = f(line, "Uid:") {
+ assert!(uid.is_none());
+ uid = Some(u);
+ } else if let Some(g) = f(line, "Gid:") {
+ assert!(gid.is_none());
+ gid = Some(g);
+ } else {
+ continue;
+ }
+ if uid.is_some() && gid.is_some() {
+ break;
+ }
+ }
+ match (uid, gid) {
+ (Some(u), Some(g)) => Some((u, g)),
+ _ => None,
+ }
+}
+
+fn parse_stat_file(data: &str) -> Option<Vec<&str>> {
+ // The stat file is "interesting" to parse, because spaces cannot
+ // be used as delimiters. The second field stores the command name
+ // surrounded by parentheses. Unfortunately, whitespace and
+ // parentheses are legal parts of the command, so parsing has to
+ // proceed like this: The first field is delimited by the first
+ // whitespace, the second field is everything until the last ')'
+ // in the entire string. All other fields are delimited by
+ // whitespace.
+
+ let mut parts = Vec::with_capacity(52);
+ let mut data_it = data.splitn(2, ' ');
+ parts.push(data_it.next()?);
+ let mut data_it = data_it.next()?.rsplitn(2, ')');
+ let data = data_it.next()?;
+ parts.push(data_it.next()?);
+ parts.extend(data.split_whitespace());
+ // Remove command name '('
+ if let Some(name) = parts[1].strip_prefix('(') {
+ parts[1] = name;
+ }
+ Some(parts)
+}