diff --git a/.cirrus.yml b/.cirrus.yml index 36d17ed0d2..ac50b608d4 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,10 +1,10 @@ task: - name: rust 1.69 on freebsd 13 + name: rust 1.74 on freebsd 13 freebsd_instance: image: freebsd-13-1-release-amd64 setup_script: - curl https://sh.rustup.rs -sSf --output rustup.sh - - sh rustup.sh -y --profile=minimal --default-toolchain=1.69 + - sh rustup.sh -y --profile=minimal --default-toolchain=1.74 - . $HOME/.cargo/env - rustup --version - rustup component add clippy @@ -37,14 +37,14 @@ task: - FREEBSD_CI=1 cargo test --lib -j1 -- --ignored task: - name: rust 1.69 on mac m1 + name: rust 1.74 on mac m1 macos_instance: image: ghcr.io/cirruslabs/macos-monterey-base:latest setup_script: - brew update - brew install curl - curl https://sh.rustup.rs -sSf --output rustup.sh - - sh rustup.sh -y --profile=minimal --default-toolchain=1.69 + - sh rustup.sh -y --profile=minimal --default-toolchain=1.74 - source $HOME/.cargo/env - rustup --version - rustup component add clippy diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 4098a996dd..06978a2859 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -68,7 +68,7 @@ jobs: - { os: 'ubuntu-latest', target: 'x86_64-linux-android', cross: true } - { os: 'ubuntu-latest', target: 'i686-linux-android', cross: true } toolchain: - - "1.69.0" # minimum supported rust version + - "1.74.0" # minimum supported rust version - stable - nightly steps: @@ -139,7 +139,7 @@ jobs: - macos-latest - windows-latest toolchain: - - "1.69.0" # minimum supported rust version + - "1.74.0" # minimum supported rust version - stable - nightly steps: @@ -205,7 +205,7 @@ jobs: strategy: matrix: toolchain: - - "1.69.0" # minimum supported rust version + - "1.74.0" # minimum supported rust version - stable steps: - uses: actions/checkout@v2 diff --git a/Cargo.lock b/Cargo.lock index 88b0bd7440..67b8a13b0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,17 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bstr" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -104,6 +115,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + [[package]] name = "memoffset" version = "0.9.0" @@ -175,6 +192,12 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" + [[package]] name = "rustix" version = "0.38.28" @@ -240,9 +263,11 @@ dependencies = [ name = "sysinfo" version = "0.30.4" dependencies = [ + "bstr", "cfg-if", "core-foundation-sys", "libc", + "memchr", "ntapi", "once_cell", "rayon", diff --git a/Cargo.toml b/Cargo.toml index 8d7466a561..55fa1d99ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ description = "Library to get system information such as processes, CPUs, disks, repository = "https://github.com/GuillaumeGomez/sysinfo" license = "MIT" readme = "README.md" -rust-version = "1.69" +rust-version = "1.74" exclude = ["/test-unknown"] categories = ["filesystem", "os", "api-bindings"] edition = "2018" @@ -41,6 +41,7 @@ rustdoc-args = ["--generate-link-to-definition"] [dependencies] cfg-if = "1.0" +memchr = "2.7.1" rayon = { version = "^1.8", optional = true } serde = { version = "^1.0.190", optional = true } @@ -96,3 +97,4 @@ tempfile = "3.9" [dev-dependencies] serde_json = "1.0" # Used in documentation tests. +bstr = "1.9.0" # Used in example diff --git a/README.md b/README.md index 26244eea6c..2ad07847f6 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ You can still use `sysinfo` on non-supported OSes, it'll simply do nothing and a empty values. You can check in your program directly if an OS is supported by checking the [`IS_SUPPORTED_SYSTEM`] constant. -The minimum-supported version of `rustc` is **1.69**. +The minimum-supported version of `rustc` is **1.74**. ## Usage @@ -68,7 +68,7 @@ println!("NB CPUs: {}", sys.cpus().len()); // Display processes ID, name na disk usage: for (pid, process) in sys.processes() { - println!("[{pid}] {} {:?}", process.name(), process.disk_usage()); + println!("[{pid}] {:?} {:?}", process.name(), process.disk_usage()); } // We display all disks' information: @@ -176,8 +176,8 @@ virtual systems. Apple has restrictions as to which APIs can be linked into binaries that are distributed through the app store. By default, `sysinfo` is not compatible with these restrictions. You can use the `apple-app-store` -feature flag to disable the Apple prohibited features. This also enables the `apple-sandbox` feature. -In the case of applications using the sandbox outside of the app store, the `apple-sandbox` feature +feature flag to disable the Apple prohibited features. This also enables the `apple-sandbox` feature. +In the case of applications using the sandbox outside of the app store, the `apple-sandbox` feature can be used alone to avoid causing policy violations at runtime. ### How it works diff --git a/examples/simple.rs b/examples/simple.rs index 34634150a8..15ce7be45b 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -4,6 +4,7 @@ #![allow(unused_must_use, non_upper_case_globals)] #![allow(clippy::manual_range_contains)] +use bstr::ByteSlice; use std::io::{self, BufRead, Write}; use std::str::FromStr; use sysinfo::{Components, Disks, Networks, Pid, Signal, System, Users}; @@ -242,7 +243,7 @@ fn interpret_input( &mut io::stdout(), "{}:{} status={:?}", pid, - proc_.name(), + proc_.name().as_encoded_bytes().as_bstr(), proc_.status() ); } @@ -289,8 +290,12 @@ fn interpret_input( }; } else { let proc_name = tmp[1]; - for proc_ in sys.processes_by_name(proc_name) { - writeln!(&mut io::stdout(), "==== {} ====", proc_.name()); + for proc_ in sys.processes_by_name(proc_name.as_ref()) { + writeln!( + &mut io::stdout(), + "==== {} ====", + proc_.name().as_encoded_bytes().as_bstr() + ); writeln!(&mut io::stdout(), "{proc_:?}"); } } diff --git a/src/common.rs b/src/common.rs index 798ab1a934..057685fdfd 100644 --- a/src/common.rs +++ b/src/common.rs @@ -412,7 +412,7 @@ impl System { /// /// let s = System::new_all(); /// for (pid, process) in s.processes() { - /// println!("{} {}", pid, process.name()); + /// println!("{} {:?}", pid, process.name()); /// } /// ``` pub fn processes(&self) -> &HashMap { @@ -426,7 +426,7 @@ impl System { /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { - /// println!("{}", process.name()); + /// println!("{:?}", process.name()); /// } /// ``` pub fn process(&self, pid: Pid) -> Option<&Process> { @@ -448,17 +448,18 @@ impl System { /// use sysinfo::System; /// /// let s = System::new_all(); - /// for process in s.processes_by_name("htop") { - /// println!("{} {}", process.pid(), process.name()); + /// for process in s.processes_by_name("htop".as_ref()) { + /// println!("{} {:?}", process.pid(), process.name()); /// } /// ``` pub fn processes_by_name<'a: 'b, 'b>( &'a self, - name: &'b str, + name: &'b OsStr, ) -> impl Iterator + 'b { + let finder = memchr::memmem::Finder::new(name.as_encoded_bytes()); self.processes() .values() - .filter(move |val: &&Process| val.name().contains(name)) + .filter(move |val: &&Process| finder.find(val.name().as_encoded_bytes()).is_some()) } /// Returns an iterator of processes with exactly the given `name`. @@ -476,13 +477,13 @@ impl System { /// use sysinfo::System; /// /// let s = System::new_all(); - /// for process in s.processes_by_exact_name("htop") { - /// println!("{} {}", process.pid(), process.name()); + /// for process in s.processes_by_exact_name("htop".as_ref()) { + /// println!("{} {:?}", process.pid(), process.name()); /// } /// ``` pub fn processes_by_exact_name<'a: 'b, 'b>( &'a self, - name: &'b str, + name: &'b OsStr, ) -> impl Iterator + 'b { self.processes() .values() @@ -830,7 +831,7 @@ impl System { /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { -/// println!("{}", process.name()); +/// println!("{:?}", process.name()); /// } /// ``` pub struct Process { @@ -898,10 +899,10 @@ impl Process { /// /// let s = System::new_all(); /// if let Some(process) = s.process(Pid::from(1337)) { - /// println!("{}", process.name()); + /// println!("{:?}", process.name()); /// } /// ``` - pub fn name(&self) -> &str { + pub fn name(&self) -> &OsStr { self.inner.name() } diff --git a/src/debug.rs b/src/debug.rs index 3926fe8dc1..c9f5023060 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -73,14 +73,7 @@ impl fmt::Debug for Process { impl fmt::Debug for Components { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Components {{ {} }}", - self.iter() - .map(|x| format!("{x:?}")) - .collect::>() - .join(", ") - ) + f.debug_list().entries(self.iter()).finish() } } @@ -109,14 +102,7 @@ impl fmt::Debug for Component { impl fmt::Debug for Networks { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Networks {{ {} }}", - self.iter() - .map(|x| format!("{x:?}")) - .collect::>() - .join(", ") - ) + f.debug_list().entries(self.iter()).finish() } } @@ -141,27 +127,13 @@ impl fmt::Debug for NetworkData { impl fmt::Debug for Disks { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Disks {{ {} }}", - self.iter() - .map(|x| format!("{x:?}")) - .collect::>() - .join(", ") - ) + f.debug_list().entries(self.iter()).finish() } } impl fmt::Debug for Users { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Users {{ {} }}", - self.iter() - .map(|x| format!("{x:?}")) - .collect::>() - .join(", ") - ) + f.debug_list().entries(self.iter()).finish() } } diff --git a/src/unix/apple/app_store/process.rs b/src/unix/apple/app_store/process.rs index 0d90111da4..cfc2e979c1 100644 --- a/src/unix/apple/app_store/process.rs +++ b/src/unix/apple/app_store/process.rs @@ -1,6 +1,6 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use std::path::Path; +use std::{ffi::OsStr, path::Path}; use crate::{DiskUsage, Gid, Pid, ProcessStatus, Signal, Uid}; @@ -11,8 +11,8 @@ impl ProcessInner { None } - pub(crate) fn name(&self) -> &str { - "" + pub(crate) fn name(&self) -> &OsStr { + OsStr::new("") } pub(crate) fn cmd(&self) -> &[String] { diff --git a/src/unix/apple/cpu.rs b/src/unix/apple/cpu.rs index 5ccb0bf903..18c0e99250 100644 --- a/src/unix/apple/cpu.rs +++ b/src/unix/apple/cpu.rs @@ -186,7 +186,7 @@ pub(crate) unsafe fn get_cpu_frequency() -> u64 { #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] { - return 0; + 0 } #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] { diff --git a/src/unix/apple/disk.rs b/src/unix/apple/disk.rs index e5025390dd..048c852684 100644 --- a/src/unix/apple/disk.rs +++ b/src/unix/apple/disk.rs @@ -269,6 +269,7 @@ unsafe fn get_dict_value Option>( ) -> Option { #[cfg(target_os = "macos")] let _defined; + #[allow(clippy::infallible_destructuring_match)] let key = match key { DictKey::Extern(val) => val, #[cfg(target_os = "macos")] diff --git a/src/unix/apple/macos/process.rs b/src/unix/apple/macos/process.rs index 89355586cd..59e55ed55d 100644 --- a/src/unix/apple/macos/process.rs +++ b/src/unix/apple/macos/process.rs @@ -1,6 +1,8 @@ // Take a look at the license at the top of the repository in the LICENSE file. +use std::ffi::{OsStr, OsString}; use std::mem::{self, MaybeUninit}; +use std::os::unix::ffi::{OsStrExt, OsStringExt}; use std::path::{Path, PathBuf}; use libc::{c_int, c_void, kill}; @@ -12,7 +14,7 @@ use crate::sys::system::Wrap; use crate::unix::utils::cstr_to_rust_with_size; pub(crate) struct ProcessInner { - pub(crate) name: String, + pub(crate) name: OsString, pub(crate) cmd: Vec, pub(crate) exe: Option, pid: Pid, @@ -47,7 +49,7 @@ pub(crate) struct ProcessInner { impl ProcessInner { pub(crate) fn new_empty(pid: Pid) -> Self { Self { - name: String::new(), + name: OsString::new(), pid, parent: None, cmd: Vec::new(), @@ -78,7 +80,7 @@ impl ProcessInner { pub(crate) fn new(pid: Pid, parent: Option, start_time: u64, run_time: u64) -> Self { Self { - name: String::new(), + name: OsString::new(), pid, parent, cmd: Vec::new(), @@ -112,7 +114,7 @@ impl ProcessInner { unsafe { Some(kill(self.pid.0, c_signal) == 0) } } - pub(crate) fn name(&self) -> &str { + pub(crate) fn name(&self) -> &OsStr { &self.name } @@ -400,14 +402,10 @@ unsafe fn get_exe_and_name_backup( ) { x if x > 0 => { buffer.set_len(x as _); - let tmp = String::from_utf8_unchecked(buffer); + let tmp = OsString::from_vec(buffer); let exe = PathBuf::from(tmp); if process.name.is_empty() { - process.name = exe - .file_name() - .and_then(|x| x.to_str()) - .unwrap_or("") - .to_owned(); + process.name = exe.file_name().unwrap_or_default().to_owned(); } if exe_needs_update { process.exe = Some(exe); @@ -540,11 +538,7 @@ unsafe fn get_process_infos(process: &mut ProcessInner, refresh_kind: ProcessRef let (exe, proc_args) = get_exe(proc_args); if process.name.is_empty() { - process.name = exe - .file_name() - .and_then(|x| x.to_str()) - .unwrap_or("") - .to_owned(); + process.name = exe.file_name().unwrap_or_default().to_owned(); } if refresh_kind.exe().needs_update(|| process.exe.is_none()) { @@ -568,12 +562,10 @@ unsafe fn get_process_infos(process: &mut ProcessInner, refresh_kind: ProcessRef fn get_exe(data: &[u8]) -> (PathBuf, &[u8]) { let pos = data.iter().position(|c| *c == 0).unwrap_or(data.len()); - unsafe { - ( - Path::new(std::str::from_utf8_unchecked(&data[..pos])).to_path_buf(), - &data[pos..], - ) - } + ( + Path::new(OsStr::from_bytes(&data[..pos])).to_path_buf(), + &data[pos..], + ) } fn get_arguments<'a>( diff --git a/src/unix/freebsd/disk.rs b/src/unix/freebsd/disk.rs index a42bd7ca1c..aac1be0b3d 100644 --- a/src/unix/freebsd/disk.rs +++ b/src/unix/freebsd/disk.rs @@ -6,7 +6,7 @@ use std::ffi::{OsStr, OsString}; use std::os::unix::ffi::OsStringExt; use std::path::{Path, PathBuf}; -use super::utils::c_buf_to_str; +use super::utils::c_buf_to_utf8_str; pub(crate) struct DiskInner { name: OsString, @@ -134,7 +134,7 @@ pub unsafe fn get_all_list(container: &mut Vec) { continue; } - let mount_point = match c_buf_to_str(&fs_info.f_mntonname) { + let mount_point = match c_buf_to_utf8_str(&fs_info.f_mntonname) { Some(m) => m, None => { sysinfo_debug!("Cannot get disk mount point, ignoring it."); diff --git a/src/unix/freebsd/network.rs b/src/unix/freebsd/network.rs index 35e9da62bb..dfa44ca37b 100644 --- a/src/unix/freebsd/network.rs +++ b/src/unix/freebsd/network.rs @@ -79,7 +79,7 @@ impl NetworksInner { if !utils::get_sys_value(&mib, &mut data) { continue; } - if let Some(name) = utils::c_buf_to_string(&data.ifmd_name) { + if let Some(name) = utils::c_buf_to_utf8_string(&data.ifmd_name) { let data = &data.ifmd_data; match self.interfaces.entry(name) { hash_map::Entry::Occupied(mut e) => { diff --git a/src/unix/freebsd/process.rs b/src/unix/freebsd/process.rs index 1c42cd6ba8..bc9fa3a573 100644 --- a/src/unix/freebsd/process.rs +++ b/src/unix/freebsd/process.rs @@ -2,6 +2,7 @@ use crate::{DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Signal, Uid}; +use std::ffi::{OsStr, OsString}; use std::fmt; use std::path::{Path, PathBuf}; @@ -41,7 +42,7 @@ impl fmt::Display for ProcessStatus { } pub(crate) struct ProcessInner { - pub(crate) name: String, + pub(crate) name: OsString, pub(crate) cmd: Vec, pub(crate) exe: Option, pub(crate) pid: Pid, @@ -72,7 +73,7 @@ impl ProcessInner { unsafe { Some(libc::kill(self.pid.0, c_signal) == 0) } } - pub(crate) fn name(&self) -> &str { + pub(crate) fn name(&self) -> &OsStr { &self.name } @@ -280,7 +281,7 @@ pub(crate) unsafe fn get_process_data( cwd: None, exe: None, // kvm_getargv isn't thread-safe so we get it in the main thread. - name: String::new(), + name: OsString::new(), // kvm_getargv isn't thread-safe so we get it in the main thread. cmd: Vec::new(), // kvm_getargv isn't thread-safe so we get it in the main thread. diff --git a/src/unix/freebsd/system.rs b/src/unix/freebsd/system.rs index 8b8d66e9af..5a55fe80cb 100644 --- a/src/unix/freebsd/system.rs +++ b/src/unix/freebsd/system.rs @@ -14,7 +14,7 @@ use std::ptr::NonNull; use crate::sys::cpu::{physical_core_count, CpusWrapper}; use crate::sys::process::get_exe; use crate::sys::utils::{ - self, boot_time, c_buf_to_string, from_cstr_array, get_sys_value, get_sys_value_by_name, + self, boot_time, c_buf_to_os_string, from_cstr_array, get_sys_value, get_sys_value_by_name, get_system_info, init_mib, }; @@ -380,7 +380,7 @@ unsafe fn add_missing_proc_info( if !cmd.is_empty() { // First, we try to retrieve the name from the command line. let p = Path::new(&cmd[0]); - if let Some(name) = p.file_name().and_then(|s| s.to_str()) { + if let Some(name) = p.file_name() { proc_inner.name = name.to_owned(); } @@ -395,7 +395,7 @@ unsafe fn add_missing_proc_info( // The name can be cut short because the `ki_comm` field size is limited, // which is why we prefer to get the name from the command line as much as // possible. - proc_inner.name = c_buf_to_string(&kproc.ki_comm).unwrap_or_default(); + proc_inner.name = c_buf_to_os_string(&kproc.ki_comm); } if refresh_kind .environ() diff --git a/src/unix/freebsd/utils.rs b/src/unix/freebsd/utils.rs index 4f7ba5e5af..555f328829 100644 --- a/src/unix/freebsd/utils.rs +++ b/src/unix/freebsd/utils.rs @@ -4,8 +4,9 @@ use crate::{Pid, Process}; use libc::{c_char, c_int, timeval}; use std::cell::UnsafeCell; use std::collections::HashMap; -use std::ffi::CStr; +use std::ffi::{CStr, OsStr, OsString}; use std::mem; +use std::os::unix::ffi::OsStrExt; use std::time::SystemTime; /// This struct is used to switch between the "old" and "new" every time you use "get_mut". @@ -109,20 +110,37 @@ pub(crate) unsafe fn get_sys_value_array(mib: &[c_int], value: &mut [T ) == 0 } -pub(crate) fn c_buf_to_str(buf: &[libc::c_char]) -> Option<&str> { +pub(crate) fn c_buf_to_utf8_str(buf: &[libc::c_char]) -> Option<&str> { unsafe { let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()); - if let Some(pos) = buf.iter().position(|x| *x == 0) { + std::str::from_utf8(if let Some(pos) = buf.iter().position(|x| *x == 0) { // Shrink buffer to terminate the null bytes - std::str::from_utf8(&buf[..pos]).ok() + &buf[..pos] } else { - std::str::from_utf8(buf).ok() - } + buf + }) + .ok() + } +} + +pub(crate) fn c_buf_to_utf8_string(buf: &[libc::c_char]) -> Option { + c_buf_to_utf8_str(buf).map(|s| s.to_owned()) +} + +pub(crate) fn c_buf_to_os_str(buf: &[libc::c_char]) -> &OsStr { + unsafe { + let buf: &[u8] = std::slice::from_raw_parts(buf.as_ptr() as _, buf.len()); + OsStr::from_bytes(if let Some(pos) = buf.iter().position(|x| *x == 0) { + // Shrink buffer to terminate the null bytes + &buf[..pos] + } else { + buf + }) } } -pub(crate) fn c_buf_to_string(buf: &[libc::c_char]) -> Option { - c_buf_to_str(buf).map(|s| s.to_owned()) +pub(crate) fn c_buf_to_os_string(buf: &[libc::c_char]) -> OsString { + c_buf_to_os_str(buf).to_owned() } pub(crate) unsafe fn get_sys_value_str(mib: &[c_int], buf: &mut [libc::c_char]) -> Option { @@ -138,7 +156,7 @@ pub(crate) unsafe fn get_sys_value_str(mib: &[c_int], buf: &mut [libc::c_char]) { return None; } - c_buf_to_string(&buf[..len / mem::size_of::()]) + c_buf_to_utf8_string(&buf[..len / mem::size_of::()]) } pub(crate) unsafe fn get_sys_value_by_name(name: &[u8], value: &mut T) -> bool { @@ -180,7 +198,7 @@ pub(crate) fn get_sys_value_str_by_name(name: &[u8]) -> Option { ) == 0 && size > 0 { - c_buf_to_string(&buf) + c_buf_to_utf8_string(&buf) } else { // getting the system value failed None @@ -224,7 +242,7 @@ pub(crate) fn get_system_info(mib: &[c_int], default: Option<&str>) -> Option DiskKind { .join(trimmed) .join("queue/rotational"); // Normally, this file only contains '0' or '1' but just in case, we get 8 bytes... - match get_all_data(path, 8) + match get_all_utf8_data(path, 8) .unwrap_or_default() .trim() .parse() diff --git a/src/unix/linux/process.rs b/src/unix/linux/process.rs index 9c1d55834d..ae75c2fa1c 100644 --- a/src/unix/linux/process.rs +++ b/src/unix/linux/process.rs @@ -2,23 +2,24 @@ use std::cell::UnsafeCell; use std::collections::{HashMap, HashSet}; -use std::ffi::OsStr; -use std::fmt; +use std::ffi::{OsStr, OsString}; use std::fs::{self, DirEntry, File}; use std::io::Read; +use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; use std::str::FromStr; +use std::{fmt, str}; use libc::{c_ulong, 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::sys::utils::{get_all_data_from_file, realpath, FileCounter, PathHandler, PathPush}; use crate::{ DiskUsage, Gid, Pid, Process, ProcessRefreshKind, ProcessStatus, Signal, ThreadKind, Uid, }; +use super::utils::get_all_utf8_data; + #[doc(hidden)] impl From for ProcessStatus { fn from(status: char) -> ProcessStatus { @@ -62,7 +63,6 @@ impl fmt::Display for ProcessStatus { #[repr(usize)] enum ProcIndex { Pid = 0, - ShortExe, State, ParentPid, GroupId, @@ -92,7 +92,7 @@ enum ProcIndex { const PF_KTHREAD: c_ulong = 0x00200000; pub(crate) struct ProcessInner { - pub(crate) name: String, + pub(crate) name: OsString, pub(crate) cmd: Vec, pub(crate) exe: Option, pub(crate) pid: Pid, @@ -129,7 +129,7 @@ pub(crate) struct ProcessInner { impl ProcessInner { pub(crate) fn new(pid: Pid, proc_path: PathBuf) -> Self { Self { - name: String::new(), + name: OsString::new(), pid, parent: None, cmd: Vec::new(), @@ -169,7 +169,7 @@ impl ProcessInner { unsafe { Some(kill(self.pid.0, c_signal) == 0) } } - pub(crate) fn name(&self) -> &str { + pub(crate) fn name(&self) -> &OsStr { &self.name } @@ -310,7 +310,7 @@ pub(crate) fn set_time(p: &mut ProcessInner, utime: u64, stime: u64) { } pub(crate) fn update_process_disk_activity(p: &mut ProcessInner, path: &mut PathHandler) { - let data = match get_all_data(path.join("io"), 16_384) { + let data = match get_all_utf8_data(path.join("io"), 16_384) { Ok(d) => d, Err(_) => return, }; @@ -355,13 +355,13 @@ 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 { +fn compute_start_time_without_boot_time(parts: &Parts<'_>, 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[ProcIndex::StartTime as usize]).unwrap_or(0) / info.clock_cycle + u64::from_str(parts.str_parts[ProcIndex::StartTime as usize]).unwrap_or(0) / info.clock_cycle } -fn _get_stat_data(path: &Path, stat_file: &mut Option) -> Result { +fn _get_stat_data(path: &Path, stat_file: &mut Option) -> Result, ()> { 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); @@ -401,11 +401,11 @@ fn update_proc_info( p: &mut ProcessInner, refresh_kind: ProcessRefreshKind, proc_path: &mut PathHandler, - parts: &[&str], + str_parts: &[&str], uptime: u64, info: &SystemInfo, ) { - get_status(p, parts[ProcIndex::State as usize]); + get_status(p, str_parts[ProcIndex::State as usize]); refresh_user_group_ids(p, proc_path, refresh_kind); if refresh_kind.exe().needs_update(|| p.exe.is_none()) { @@ -427,7 +427,7 @@ fn update_proc_info( p.root = realpath(proc_path.join("root")); } - update_time_and_memory(proc_path, p, parts, uptime, info, refresh_kind); + update_time_and_memory(proc_path, p, str_parts, uptime, info, refresh_kind); if refresh_kind.disk_usage() { update_process_disk_activity(p, proc_path); } @@ -436,7 +436,7 @@ fn update_proc_info( fn retrieve_all_new_process_info( pid: Pid, parent_pid: Option, - parts: &[&str], + parts: &Parts<'_>, path: &Path, info: &SystemInfo, refresh_kind: ProcessRefreshKind, @@ -444,11 +444,11 @@ fn retrieve_all_new_process_info( ) -> Process { let mut p = ProcessInner::new(pid, path.to_owned()); let mut proc_path = PathHandler::new(path); - let name = parts[ProcIndex::ShortExe as usize]; + let name = parts.short_exe; p.parent = match parent_pid { Some(parent_pid) if parent_pid.0 != 0 => Some(parent_pid), - _ => match Pid::from_str(parts[ProcIndex::ParentPid as usize]) { + _ => match Pid::from_str(parts.str_parts[ProcIndex::ParentPid as usize]) { Ok(p) if p.0 != 0 => Some(p), _ => None, }, @@ -459,8 +459,8 @@ fn retrieve_all_new_process_info( .start_time_without_boot_time .saturating_add(info.boot_time); - p.name = name.into(); - if c_ulong::from_str(parts[ProcIndex::Flags as usize]) + p.name = OsStr::from_bytes(name).to_os_string(); + if c_ulong::from_str(parts.str_parts[ProcIndex::Flags as usize]) .map(|flags| flags & PF_KTHREAD != 0) .unwrap_or(false) { @@ -469,7 +469,14 @@ fn retrieve_all_new_process_info( p.thread_kind = Some(ThreadKind::Userland); } - update_proc_info(&mut p, refresh_kind, &mut proc_path, parts, uptime, info); + update_proc_info( + &mut p, + refresh_kind, + &mut proc_path, + &parts.str_parts, + uptime, + info, + ); Process { inner: p } } @@ -511,7 +518,14 @@ pub(crate) fn _get_process_data( if start_time_without_boot_time == entry.start_time_without_boot_time { let mut proc_path = PathHandler::new(path); - update_proc_info(entry, refresh_kind, &mut proc_path, &parts, uptime, info); + update_proc_info( + entry, + refresh_kind, + &mut proc_path, + &parts.str_parts, + uptime, + info, + ); refresh_user_group_ids(entry, &mut proc_path, refresh_kind); return Ok((None, pid)); @@ -548,14 +562,14 @@ pub(crate) fn _get_process_data( Ok((None, pid)) } -fn old_get_memory(entry: &mut ProcessInner, parts: &[&str], info: &SystemInfo) { +fn old_get_memory(entry: &mut ProcessInner, str_parts: &[&str], info: &SystemInfo) { // rss - entry.memory = u64::from_str(parts[ProcIndex::ResidentSetSize as usize]) + entry.memory = u64::from_str(str_parts[ProcIndex::ResidentSetSize as usize]) .unwrap_or(0) .saturating_mul(info.page_size_b); // 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[ProcIndex::VirtualSize as usize]).unwrap_or(0); + entry.virtual_memory = u64::from_str(str_parts[ProcIndex::VirtualSize as usize]).unwrap_or(0); } fn slice_to_nb(s: &[u8]) -> u64 { @@ -604,7 +618,7 @@ fn get_memory(path: &Path, entry: &mut ProcessInner, info: &SystemInfo) -> bool fn update_time_and_memory( path: &mut PathHandler, entry: &mut ProcessInner, - parts: &[&str], + str_parts: &[&str], uptime: u64, info: &SystemInfo, refresh_kind: ProcessRefreshKind, @@ -614,13 +628,13 @@ fn update_time_and_memory( if refresh_kind.memory() { // Keeping this nested level for readability reasons. if !get_memory(path.join("statm"), entry, info) { - old_get_memory(entry, parts, info); + old_get_memory(entry, str_parts, info); } } set_time( entry, - u64::from_str(parts[ProcIndex::UserTime as usize]).unwrap_or(0), - u64::from_str(parts[ProcIndex::SystemTime as usize]).unwrap_or(0), + u64::from_str(str_parts[ProcIndex::UserTime as usize]).unwrap_or(0), + u64::from_str(str_parts[ProcIndex::SystemTime as usize]).unwrap_or(0), ); entry.run_time = uptime.saturating_sub(entry.start_time_without_boot_time); } @@ -797,7 +811,7 @@ fn copy_from_file(entry: &Path) -> Vec { // Fetch tuples of real and effective UID and GID. fn get_uid_and_gid(file_path: &Path) -> Option<((uid_t, uid_t), (gid_t, gid_t))> { - let status_data = get_all_data(file_path, 16_385).ok()?; + let status_data = get_all_utf8_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 first and second entries to get @@ -842,7 +856,12 @@ fn get_uid_and_gid(file_path: &Path) -> Option<((uid_t, uid_t), (gid_t, gid_t))> } } -fn parse_stat_file(data: &str) -> Option> { +struct Parts<'a> { + str_parts: Vec<&'a str>, + short_exe: &'a [u8], +} + +fn parse_stat_file(data: &[u8]) -> Option> { // 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 @@ -852,16 +871,15 @@ fn parse_stat_file(data: &str) -> Option> { // 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[ProcIndex::ShortExe as usize].strip_prefix('(') { - parts[ProcIndex::ShortExe as usize] = name; - } - Some(parts) + let mut str_parts = Vec::with_capacity(51); + let mut data_it = data.splitn(2, |&b| b == b' '); + str_parts.push(str::from_utf8(data_it.next()?).ok()?); + let mut data_it = data_it.next()?.rsplitn(2, |&b| b == b')'); + let data = str::from_utf8(data_it.next()?).ok()?; + let short_exe = data_it.next()?; + str_parts.extend(data.split_whitespace()); + Some(Parts { + str_parts, + short_exe: short_exe.strip_prefix(&[b'(']).unwrap_or(short_exe), + }) } diff --git a/src/unix/linux/system.rs b/src/unix/linux/system.rs index 734e645470..55df6a6d21 100644 --- a/src/unix/linux/system.rs +++ b/src/unix/linux/system.rs @@ -2,7 +2,7 @@ use crate::sys::cpu::{get_physical_core_count, CpusWrapper}; use crate::sys::process::{_get_process_data, compute_cpu_usage, refresh_procs, unset_updated}; -use crate::sys::utils::{get_all_data, to_u64}; +use crate::sys::utils::{get_all_utf8_data, to_u64}; use crate::{Cpu, CpuRefreshKind, LoadAvg, MemoryRefreshKind, Pid, Process, ProcessRefreshKind}; use libc::{self, c_char, sysconf, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE}; @@ -352,7 +352,7 @@ impl SystemInner { } pub(crate) fn uptime() -> u64 { - let content = get_all_data("/proc/uptime", 50).unwrap_or_default(); + let content = get_all_utf8_data("/proc/uptime", 50).unwrap_or_default(); content .split('.') .next() @@ -508,7 +508,7 @@ impl SystemInner { } fn read_u64(filename: &str) -> Option { - get_all_data(filename, 16_635) + get_all_utf8_data(filename, 16_635) .ok() .and_then(|d| u64::from_str(d.trim()).ok()) } @@ -517,7 +517,7 @@ fn read_table(filename: &str, colsep: char, mut f: F) where F: FnMut(&str, u64), { - if let Ok(content) = get_all_data(filename, 16_635) { + if let Ok(content) = get_all_utf8_data(filename, 16_635) { content .split('\n') .flat_map(|line| { diff --git a/src/unix/linux/utils.rs b/src/unix/linux/utils.rs index 7d4292817d..987f4e18dc 100644 --- a/src/unix/linux/utils.rs +++ b/src/unix/linux/utils.rs @@ -7,16 +7,23 @@ use std::sync::atomic::Ordering; use crate::sys::system::REMAINING_FILES; -pub(crate) fn get_all_data_from_file(file: &mut File, size: usize) -> io::Result { +pub(crate) fn get_all_data_from_file(file: &mut File, size: usize) -> io::Result> { + let mut buf = Vec::with_capacity(size); + file.rewind()?; + file.read_to_end(&mut buf)?; + Ok(buf) +} + +pub(crate) fn get_all_utf8_data_from_file(file: &mut File, size: usize) -> io::Result { let mut buf = String::with_capacity(size); file.rewind()?; file.read_to_string(&mut buf)?; Ok(buf) } -pub(crate) fn get_all_data>(file_path: P, size: usize) -> io::Result { +pub(crate) fn get_all_utf8_data>(file_path: P, size: usize) -> io::Result { let mut file = File::open(file_path.as_ref())?; - get_all_data_from_file(&mut file, size) + get_all_utf8_data_from_file(&mut file, size) } #[allow(clippy::useless_conversion)] diff --git a/src/unknown/process.rs b/src/unknown/process.rs index 0bb48554b7..bca71e07fa 100644 --- a/src/unknown/process.rs +++ b/src/unknown/process.rs @@ -2,6 +2,7 @@ use crate::{DiskUsage, Gid, Pid, ProcessStatus, Signal, Uid}; +use std::ffi::OsStr; use std::fmt; use std::path::Path; @@ -21,8 +22,8 @@ impl ProcessInner { None } - pub(crate) fn name(&self) -> &str { - "" + pub(crate) fn name(&self) -> &OsStr { + OsStr::new("") } pub(crate) fn cmd(&self) -> &[String] { diff --git a/src/windows/process.rs b/src/windows/process.rs index 063189596b..17e00afcdd 100644 --- a/src/windows/process.rs +++ b/src/windows/process.rs @@ -4,7 +4,7 @@ use crate::sys::system::is_proc_running; use crate::windows::Sid; use crate::{DiskUsage, Gid, Pid, ProcessRefreshKind, ProcessStatus, Signal, Uid}; -use std::ffi::OsString; +use std::ffi::{OsStr, OsString}; use std::fmt; #[cfg(feature = "debug")] use std::io; @@ -199,7 +199,7 @@ unsafe impl Send for HandleWrapper {} unsafe impl Sync for HandleWrapper {} pub(crate) struct ProcessInner { - name: String, + name: OsString, cmd: Vec, exe: Option, pid: Pid, @@ -266,7 +266,7 @@ unsafe fn display_ntstatus_error(ntstatus: windows::core::HRESULT) { // Take a look at https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/query.htm // for explanations. -unsafe fn get_process_name(pid: Pid) -> Option { +unsafe fn get_process_name(pid: Pid) -> Option { let mut info = SYSTEM_PROCESS_ID_INFORMATION { ProcessId: pid.0 as _, ImageName: MaybeUninit::zeroed().assume_init(), @@ -333,9 +333,7 @@ unsafe fn get_process_name(pid: Pid) -> Option { info.ImageName.Length as usize / std::mem::size_of::(), ); let os_str = OsString::from_wide(s); - let name = Path::new(&os_str) - .file_name() - .map(|s| s.to_string_lossy().to_string()); + let name = Path::new(&os_str).file_name().map(|s| s.to_os_string()); let _err = LocalFree(HLOCAL(info.ImageName.Buffer.cast())); name } @@ -420,7 +418,7 @@ impl ProcessInner { parent: Option, memory: u64, virtual_memory: u64, - name: String, + name: OsString, now: u64, refresh_kind: ProcessRefreshKind, ) -> Self { @@ -544,7 +542,7 @@ impl ProcessInner { } } - pub(crate) fn name(&self) -> &str { + pub(crate) fn name(&self) -> &OsStr { &self.name } diff --git a/src/windows/sid.rs b/src/windows/sid.rs index 5ed5029124..85c5b4b452 100644 --- a/src/windows/sid.rs +++ b/src/windows/sid.rs @@ -9,7 +9,7 @@ use windows::Win32::Security::{ CopySid, GetLengthSid, IsValidSid, LookupAccountSidW, SidTypeUnknown, }; -use crate::sys::utils::to_str; +use crate::sys::utils::to_utf8_str; #[doc = include_str!("../../md_doc/sid.md")] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -102,7 +102,7 @@ impl Sid { return None; } - Some(to_str(PWSTR::from_raw(name.as_mut_ptr()))) + Some(to_utf8_str(PWSTR::from_raw(name.as_mut_ptr()))) } } } @@ -115,7 +115,7 @@ impl Display for Sid { sysinfo_debug!("ConvertSidToStringSidW failed: {:?}", _err); return None; } - let result = to_str(string_sid); + let result = to_utf8_str(string_sid); let _err = LocalFree(HLOCAL(string_sid.0 as _)); Some(result) } diff --git a/src/windows/system.rs b/src/windows/system.rs index b621e71679..46248368fe 100644 --- a/src/windows/system.rs +++ b/src/windows/system.rs @@ -12,7 +12,9 @@ use crate::utils::into_iter; use std::cell::UnsafeCell; use std::collections::HashMap; +use std::ffi::OsString; use std::mem::{size_of, zeroed}; +use std::os::windows::ffi::OsStringExt; use std::ptr; use std::time::SystemTime; @@ -434,14 +436,14 @@ impl SystemInner { if Self::is_windows_eleven() { return get_reg_string_value( HKEY_LOCAL_MACHINE, - "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", ) .map(|product_name| product_name.replace("Windows 10 ", "Windows 11 ")); } get_reg_string_value( HKEY_LOCAL_MACHINE, - "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName", ) } @@ -453,7 +455,7 @@ impl SystemInner { pub(crate) fn kernel_version() -> Option { get_reg_string_value( HKEY_LOCAL_MACHINE, - "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentBuildNumber", ) } @@ -461,7 +463,7 @@ impl SystemInner { pub(crate) fn os_version() -> Option { let build_number = get_reg_string_value( HKEY_LOCAL_MACHINE, - "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentBuildNumber", ) .unwrap_or_default(); @@ -471,7 +473,7 @@ impl SystemInner { u32::from_le_bytes( get_reg_value_u32( HKEY_LOCAL_MACHINE, - "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", ) .unwrap_or_default(), @@ -542,7 +544,7 @@ fn refresh_existing_process( #[allow(clippy::size_of_in_element_count)] //^ needed for "name.Length as usize / std::mem::size_of::()" -pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: Pid) -> String { +pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: Pid) -> OsString { let name = &process.ImageName; if name.Buffer.is_null() { match process_id.0 { @@ -550,6 +552,7 @@ pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: 4 => "System".to_owned(), _ => format!(" Process {process_id}"), } + .into() } else { unsafe { let slice = std::slice::from_raw_parts( @@ -558,7 +561,7 @@ pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: name.Length as usize / std::mem::size_of::(), ); - String::from_utf16_lossy(slice) + OsString::from_wide(slice) } } } diff --git a/src/windows/users.rs b/src/windows/users.rs index 6a3317fa48..f9e694564e 100644 --- a/src/windows/users.rs +++ b/src/windows/users.rs @@ -1,6 +1,6 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::sys::utils::to_str; +use crate::sys::utils::to_utf8_str; use crate::{ common::{Gid, Uid}, windows::sid::Sid, @@ -139,7 +139,7 @@ unsafe fn get_groups_for_user(username: PCWSTR) -> Vec { if !buf.0.is_null() { let entries = std::slice::from_raw_parts(buf.0, nb_entries as _); groups.extend(entries.iter().map(|entry| Group { - name: to_str(entry.lgrui0_name), + name: to_utf8_str(entry.lgrui0_name), id: Gid(0), })); } @@ -191,7 +191,7 @@ pub(crate) fn get_users(users: &mut Vec) { // if this fails. let name = sid .account_name() - .unwrap_or_else(|| to_str(entry.usri0_name)); + .unwrap_or_else(|| to_utf8_str(entry.usri0_name)); users.push(User { inner: UserInner::new( Uid(sid), diff --git a/src/windows/utils.rs b/src/windows/utils.rs index 56deb796d9..1510f08a10 100644 --- a/src/windows/utils.rs +++ b/src/windows/utils.rs @@ -23,7 +23,7 @@ pub(crate) fn get_now() -> u64 { .unwrap_or(0) } -pub(crate) unsafe fn to_str(p: PWSTR) -> String { +pub(crate) unsafe fn to_utf8_str(p: PWSTR) -> String { if p.is_null() { return String::new(); } diff --git a/tests/process.rs b/tests/process.rs index 02324726f4..8cd1f7aa65 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -404,7 +404,10 @@ fn test_refresh_tasks() { .map(|task| task.name() == task_name) .unwrap_or(false))) .unwrap_or(false)); - assert!(s.processes_by_exact_name(task_name).next().is_some()); + assert!(s + .processes_by_exact_name(task_name.as_ref()) + .next() + .is_some()); // Let's give some time to the system to clean up... std::thread::sleep(std::time::Duration::from_secs(2)); @@ -420,7 +423,10 @@ fn test_refresh_tasks() { .map(|task| task.name() == task_name) .unwrap_or(false))) .unwrap_or(false)); - assert!(s.processes_by_exact_name(task_name).next().is_none()); + assert!(s + .processes_by_exact_name(task_name.as_ref()) + .next() + .is_none()); } // Checks that `refresh_process` is NOT removing dead processes. @@ -564,14 +570,14 @@ fn test_process_iterator_lifetimes() { { let name = String::from(""); // errors before PR #904: name does not live long enough - process = s.processes_by_name(&name).next(); + process = s.processes_by_name(name.as_ref()).next(); } process.unwrap(); let process: Option<&sysinfo::Process>; { // worked fine before and after: &'static str lives longer than System, error couldn't appear - process = s.processes_by_name("").next(); + process = s.processes_by_name("".as_ref()).next(); } process.unwrap(); }