diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /vendor/sysinfo/src | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/sysinfo/src')
64 files changed, 15654 insertions, 0 deletions
diff --git a/vendor/sysinfo/src/apple/app_store/component.rs b/vendor/sysinfo/src/apple/app_store/component.rs new file mode 100644 index 000000000..914fc9406 --- /dev/null +++ b/vendor/sysinfo/src/apple/app_store/component.rs @@ -0,0 +1,26 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::ComponentExt; + +#[doc = include_str!("../../../md_doc/component.md")] +pub struct Component {} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + 0.0 + } + + fn max(&self) -> f32 { + 0.0 + } + + fn critical(&self) -> Option<f32> { + None + } + + fn label(&self) -> &str { + "" + } + + fn refresh(&mut self) {} +} diff --git a/vendor/sysinfo/src/apple/app_store/mod.rs b/vendor/sysinfo/src/apple/app_store/mod.rs new file mode 100644 index 000000000..0df24cabf --- /dev/null +++ b/vendor/sysinfo/src/apple/app_store/mod.rs @@ -0,0 +1,4 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub mod component; +pub mod process; diff --git a/vendor/sysinfo/src/apple/app_store/process.rs b/vendor/sysinfo/src/apple/app_store/process.rs new file mode 100644 index 000000000..8c3348ee9 --- /dev/null +++ b/vendor/sysinfo/src/apple/app_store/process.rs @@ -0,0 +1,82 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::path::Path; + +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessStatus, Signal, Uid}; + +#[doc = include_str!("../../../md_doc/process.md")] +pub struct Process; + +impl ProcessExt for Process { + fn kill_with(&self, _signal: Signal) -> Option<bool> { + None + } + + fn name(&self) -> &str { + "" + } + + fn cmd(&self) -> &[String] { + &[] + } + + fn exe(&self) -> &Path { + Path::new("/") + } + + fn pid(&self) -> Pid { + Pid(0) + } + + fn environ(&self) -> &[String] { + &[] + } + + fn cwd(&self) -> &Path { + Path::new("/") + } + + fn root(&self) -> &Path { + Path::new("/") + } + + fn memory(&self) -> u64 { + 0 + } + + fn virtual_memory(&self) -> u64 { + 0 + } + + fn parent(&self) -> Option<Pid> { + None + } + + fn status(&self) -> ProcessStatus { + ProcessStatus::Unknown(0) + } + + fn start_time(&self) -> u64 { + 0 + } + + fn run_time(&self) -> u64 { + 0 + } + + fn cpu_usage(&self) -> f32 { + 0.0 + } + + fn disk_usage(&self) -> DiskUsage { + DiskUsage::default() + } + + fn user_id(&self) -> Option<&Uid> { + None + } + + fn group_id(&self) -> Option<Gid> { + None + } +} diff --git a/vendor/sysinfo/src/apple/component.rs b/vendor/sysinfo/src/apple/component.rs new file mode 100644 index 000000000..7c4196e72 --- /dev/null +++ b/vendor/sysinfo/src/apple/component.rs @@ -0,0 +1,3 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub use crate::sys::inner::component::*; diff --git a/vendor/sysinfo/src/apple/cpu.rs b/vendor/sysinfo/src/apple/cpu.rs new file mode 100644 index 000000000..b1068e971 --- /dev/null +++ b/vendor/sysinfo/src/apple/cpu.rs @@ -0,0 +1,331 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::system::get_sys_value; + +use crate::{CpuExt, CpuRefreshKind}; + +use libc::{c_char, host_processor_info, mach_task_self}; +use std::mem; +use std::ops::Deref; +use std::sync::Arc; + +pub(crate) struct UnsafePtr<T>(*mut T); + +unsafe impl<T> Send for UnsafePtr<T> {} +unsafe impl<T> Sync for UnsafePtr<T> {} + +impl<T> Deref for UnsafePtr<T> { + type Target = *mut T; + + fn deref(&self) -> &*mut T { + &self.0 + } +} + +pub(crate) struct CpuData { + pub cpu_info: UnsafePtr<i32>, + pub num_cpu_info: u32, +} + +impl CpuData { + pub fn new(cpu_info: *mut i32, num_cpu_info: u32) -> CpuData { + CpuData { + cpu_info: UnsafePtr(cpu_info), + num_cpu_info, + } + } +} + +impl Drop for CpuData { + fn drop(&mut self) { + if !self.cpu_info.0.is_null() { + let prev_cpu_info_size = std::mem::size_of::<i32>() as u32 * self.num_cpu_info; + unsafe { + libc::vm_deallocate( + mach_task_self(), + self.cpu_info.0 as _, + prev_cpu_info_size as _, + ); + } + self.cpu_info.0 = std::ptr::null_mut(); + } + } +} + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + name: String, + cpu_usage: f32, + cpu_data: Arc<CpuData>, + frequency: u64, + vendor_id: String, + brand: String, +} + +impl Cpu { + pub(crate) fn new( + name: String, + cpu_data: Arc<CpuData>, + frequency: u64, + vendor_id: String, + brand: String, + ) -> Cpu { + Cpu { + name, + cpu_usage: 0f32, + cpu_data, + frequency, + vendor_id, + brand, + } + } + + pub(crate) fn set_cpu_usage(&mut self, cpu_usage: f32) { + self.cpu_usage = cpu_usage; + } + + pub(crate) fn update(&mut self, cpu_usage: f32, cpu_data: Arc<CpuData>) { + self.cpu_usage = cpu_usage; + self.cpu_data = cpu_data; + } + + pub(crate) fn data(&self) -> Arc<CpuData> { + Arc::clone(&self.cpu_data) + } + + pub(crate) fn set_frequency(&mut self, frequency: u64) { + self.frequency = frequency; + } +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + /// Returns the CPU frequency in MHz. + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + fn brand(&self) -> &str { + &self.brand + } +} + +pub(crate) fn get_cpu_frequency() -> u64 { + let mut speed: u64 = 0; + let mut len = std::mem::size_of::<u64>(); + unsafe { + libc::sysctlbyname( + b"hw.cpufrequency\0".as_ptr() as *const c_char, + &mut speed as *mut _ as _, + &mut len, + std::ptr::null_mut(), + 0, + ); + speed / 1_000_000 + } +} + +#[inline] +fn get_in_use(cpu_info: *mut i32, offset: isize) -> i32 { + unsafe { + *cpu_info.offset(offset + libc::CPU_STATE_USER as isize) + + *cpu_info.offset(offset + libc::CPU_STATE_SYSTEM as isize) + + *cpu_info.offset(offset + libc::CPU_STATE_NICE as isize) + } +} + +#[inline] +fn get_idle(cpu_info: *mut i32, offset: isize) -> i32 { + unsafe { *cpu_info.offset(offset + libc::CPU_STATE_IDLE as isize) } +} + +pub(crate) fn compute_usage_of_cpu(proc_: &Cpu, cpu_info: *mut i32, offset: isize) -> f32 { + let old_cpu_info = proc_.data().cpu_info.0; + let in_use; + let total; + + // In case we are initializing cpus, there is no "old value" yet. + if old_cpu_info == cpu_info { + in_use = get_in_use(cpu_info, offset); + total = in_use + get_idle(cpu_info, offset); + } else { + in_use = get_in_use(cpu_info, offset) - get_in_use(old_cpu_info, offset); + total = in_use + (get_idle(cpu_info, offset) - get_idle(old_cpu_info, offset)); + } + in_use as f32 / total as f32 * 100. +} + +pub(crate) fn update_cpu_usage<F: FnOnce(Arc<CpuData>, *mut i32) -> (f32, usize)>( + port: libc::mach_port_t, + global_cpu: &mut Cpu, + f: F, +) { + let mut num_cpu_u = 0u32; + let mut cpu_info: *mut i32 = std::ptr::null_mut(); + let mut num_cpu_info = 0u32; + + let mut total_cpu_usage = 0f32; + + unsafe { + if host_processor_info( + port, + libc::PROCESSOR_CPU_LOAD_INFO, + &mut num_cpu_u as *mut u32, + &mut cpu_info as *mut *mut i32, + &mut num_cpu_info as *mut u32, + ) == libc::KERN_SUCCESS + { + let (total_percentage, len) = + f(Arc::new(CpuData::new(cpu_info, num_cpu_info)), cpu_info); + total_cpu_usage = total_percentage / len as f32; + } + global_cpu.set_cpu_usage(total_cpu_usage); + } +} + +pub(crate) fn init_cpus( + port: libc::mach_port_t, + cpus: &mut Vec<Cpu>, + global_cpu: &mut Cpu, + refresh_kind: CpuRefreshKind, +) { + let mut num_cpu = 0; + let mut mib = [0, 0]; + + let (vendor_id, brand) = get_vendor_id_and_brand(); + let frequency = if refresh_kind.frequency() { + get_cpu_frequency() + } else { + 0 + }; + + unsafe { + if !get_sys_value( + libc::CTL_HW as _, + libc::HW_NCPU as _, + mem::size_of::<u32>(), + &mut num_cpu as *mut _ as *mut _, + &mut mib, + ) { + num_cpu = 1; + } + } + update_cpu_usage(port, global_cpu, |proc_data, cpu_info| { + let mut percentage = 0f32; + let mut offset = 0; + for i in 0..num_cpu { + let mut p = Cpu::new( + format!("{}", i + 1), + Arc::clone(&proc_data), + frequency, + vendor_id.clone(), + brand.clone(), + ); + if refresh_kind.cpu_usage() { + let cpu_usage = compute_usage_of_cpu(&p, cpu_info, offset); + p.set_cpu_usage(cpu_usage); + percentage += p.cpu_usage(); + } + cpus.push(p); + + offset += libc::CPU_STATE_MAX as isize; + } + (percentage, cpus.len()) + }); + + // We didn't set them above to avoid cloning them unnecessarily. + global_cpu.brand = brand; + global_cpu.vendor_id = vendor_id; + global_cpu.frequency = frequency; +} + +fn get_sysctl_str(s: &[u8]) -> String { + let mut len = 0; + + unsafe { + libc::sysctlbyname( + s.as_ptr() as *const c_char, + std::ptr::null_mut(), + &mut len, + std::ptr::null_mut(), + 0, + ); + if len < 1 { + return String::new(); + } + + let mut buf = Vec::with_capacity(len); + libc::sysctlbyname( + s.as_ptr() as *const c_char, + buf.as_mut_ptr() as _, + &mut len, + std::ptr::null_mut(), + 0, + ); + if len > 0 { + buf.set_len(len); + while buf.last() == Some(&b'\0') { + buf.pop(); + } + String::from_utf8(buf).unwrap_or_else(|_| String::new()) + } else { + String::new() + } + } +} + +pub(crate) fn get_vendor_id_and_brand() -> (String, String) { + // On apple M1, `sysctl machdep.cpu.vendor` returns "", so fallback to "Apple" if the result + // is empty. + let mut vendor = get_sysctl_str(b"machdep.cpu.vendor\0"); + if vendor.is_empty() { + vendor = "Apple".to_string(); + } + + (vendor, get_sysctl_str(b"machdep.cpu.brand_string\0")) +} + +#[cfg(test)] +mod test { + use crate::*; + use std::process::Command; + + #[test] + fn check_vendor_and_brand() { + let child = Command::new("sysctl") + .arg("-a") + .output() + .expect("Failed to start command..."); + + assert!(child.status.success()); + let stdout = String::from_utf8(child.stdout).expect("Not valid UTF8"); + + let sys = System::new_with_specifics( + crate::RefreshKind::new().with_cpu(CpuRefreshKind::new().with_cpu_usage()), + ); + let cpus = sys.cpus(); + assert!(!cpus.is_empty(), "no CPU found"); + if let Some(line) = stdout.lines().find(|l| l.contains("machdep.cpu.vendor")) { + let sysctl_value = line.split(":").skip(1).next().unwrap(); + assert_eq!(cpus[0].vendor_id(), sysctl_value.trim()); + } + if let Some(line) = stdout + .lines() + .find(|l| l.contains("machdep.cpu.brand_string")) + { + let sysctl_value = line.split(":").skip(1).next().unwrap(); + assert_eq!(cpus[0].brand(), sysctl_value.trim()); + } + } +} diff --git a/vendor/sysinfo/src/apple/disk.rs b/vendor/sysinfo/src/apple/disk.rs new file mode 100644 index 000000000..9f0b4a3a3 --- /dev/null +++ b/vendor/sysinfo/src/apple/disk.rs @@ -0,0 +1,66 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::utils::to_cpath; +use crate::{DiskExt, DiskType}; + +#[cfg(target_os = "macos")] +pub(crate) use crate::sys::inner::disk::*; + +use libc::statfs; +use std::ffi::{OsStr, OsString}; +use std::mem; +use std::path::{Path, PathBuf}; + +#[doc = include_str!("../../md_doc/disk.md")] +pub struct Disk { + pub(crate) type_: DiskType, + pub(crate) name: OsString, + pub(crate) file_system: Vec<u8>, + pub(crate) mount_point: PathBuf, + pub(crate) total_space: u64, + pub(crate) available_space: u64, + pub(crate) is_removable: bool, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + self.type_ + } + + fn name(&self) -> &OsStr { + &self.name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + &self.mount_point + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + fn refresh(&mut self) -> bool { + unsafe { + let mut stat: statfs = mem::zeroed(); + let mount_point_cpath = to_cpath(&self.mount_point); + if statfs(mount_point_cpath.as_ptr() as *const i8, &mut stat) == 0 { + self.available_space = u64::from(stat.f_bsize).saturating_mul(stat.f_bavail); + true + } else { + false + } + } + } +} diff --git a/vendor/sysinfo/src/apple/ffi.rs b/vendor/sysinfo/src/apple/ffi.rs new file mode 100644 index 000000000..7a8248537 --- /dev/null +++ b/vendor/sysinfo/src/apple/ffi.rs @@ -0,0 +1,28 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use libc::c_void; + +// Reexport items defined in either macos or ios ffi module. +pub use crate::sys::inner::ffi::*; + +#[repr(C)] +pub struct __DADisk(c_void); +#[repr(C)] +pub struct __DASession(c_void); + +// #[allow(non_camel_case_types)] +// pub type io_name_t = [u8; 128]; +// #[allow(non_camel_case_types)] +// pub type io_registry_entry_t = io_object_t; + +// pub type IOOptionBits = u32; + +#[cfg_attr(feature = "debug", derive(Eq, Hash, PartialEq))] +#[derive(Clone)] +#[repr(C)] +pub struct Val_t { + pub key: [i8; 5], + pub data_size: u32, + pub data_type: [i8; 5], // UInt32Char_t + pub bytes: [i8; 32], // SMCBytes_t +} diff --git a/vendor/sysinfo/src/apple/ios.rs b/vendor/sysinfo/src/apple/ios.rs new file mode 100644 index 000000000..0393c5ec6 --- /dev/null +++ b/vendor/sysinfo/src/apple/ios.rs @@ -0,0 +1,5 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub mod ffi {} +pub use crate::sys::app_store::component; +pub use crate::sys::app_store::process; diff --git a/vendor/sysinfo/src/apple/macos/component.rs b/vendor/sysinfo/src/apple/macos/component.rs new file mode 100644 index 000000000..384efb950 --- /dev/null +++ b/vendor/sysinfo/src/apple/macos/component.rs @@ -0,0 +1,221 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::ffi; +use crate::ComponentExt; + +use libc::{c_char, c_int, c_void}; + +use std::mem; + +pub(crate) const COMPONENTS_TEMPERATURE_IDS: &[(&str, &[i8])] = &[ + ("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'C' as i8]), // PECI CPU "TCXC" + ("PECI CPU", &['T' as i8, 'C' as i8, 'X' as i8, 'c' as i8]), // PECI CPU "TCXc" + ( + "CPU Proximity", + &['T' as i8, 'C' as i8, '0' as i8, 'P' as i8], + ), // CPU Proximity (heat spreader) "TC0P" + ("GPU", &['T' as i8, 'G' as i8, '0' as i8, 'P' as i8]), // GPU "TG0P" + ("Battery", &['T' as i8, 'B' as i8, '0' as i8, 'T' as i8]), // Battery "TB0T" +]; + +pub(crate) struct ComponentFFI { + input_structure: ffi::KeyData_t, + val: ffi::Val_t, +} + +impl ComponentFFI { + fn new(key: &[i8], con: ffi::io_connect_t) -> Option<ComponentFFI> { + unsafe { + get_key_size(con, key) + .ok() + .map(|(input_structure, val)| ComponentFFI { + input_structure, + val, + }) + } + } + + fn temperature(&self, con: ffi::io_connect_t) -> Option<f32> { + get_temperature_inner(con, &self.input_structure, &self.val) + } +} + +#[doc = include_str!("../../../md_doc/component.md")] +pub struct Component { + temperature: f32, + max: f32, + critical: Option<f32>, + label: String, + ffi_part: ComponentFFI, + connection: ffi::io_connect_t, +} + +impl Component { + /// Creates a new `Component` with the given information. + pub(crate) fn new( + label: String, + max: Option<f32>, + critical: Option<f32>, + key: &[i8], + connection: ffi::io_connect_t, + ) -> Option<Component> { + let ffi_part = ComponentFFI::new(key, connection)?; + ffi_part + .temperature(connection) + .map(|temperature| Component { + temperature, + label, + max: max.unwrap_or(0.0), + critical, + ffi_part, + connection, + }) + } +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + self.critical + } + + fn label(&self) -> &str { + &self.label + } + + fn refresh(&mut self) { + if let Some(temp) = self.ffi_part.temperature(self.connection) { + self.temperature = temp; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } +} + +unsafe fn perform_call( + conn: ffi::io_connect_t, + index: c_int, + input_structure: *const ffi::KeyData_t, + output_structure: *mut ffi::KeyData_t, +) -> i32 { + let mut structure_output_size = mem::size_of::<ffi::KeyData_t>(); + + ffi::IOConnectCallStructMethod( + conn, + index as u32, + input_structure, + mem::size_of::<ffi::KeyData_t>(), + output_structure, + &mut structure_output_size, + ) +} + +// Adapted from https://github.com/lavoiesl/osx-cpu-temp/blob/master/smc.c#L28 +#[inline] +fn strtoul(s: &[i8]) -> u32 { + unsafe { + ((*s.get_unchecked(0) as u32) << (3u32 << 3)) + + ((*s.get_unchecked(1) as u32) << (2u32 << 3)) + + ((*s.get_unchecked(2) as u32) << (1u32 << 3)) + + (*s.get_unchecked(3) as u32) + } +} + +#[inline] +unsafe fn ultostr(s: *mut c_char, val: u32) { + *s.offset(0) = ((val >> 24) % 128) as i8; + *s.offset(1) = ((val >> 16) % 128) as i8; + *s.offset(2) = ((val >> 8) % 128) as i8; + *s.offset(3) = (val % 128) as i8; + *s.offset(4) = 0; +} + +unsafe fn get_key_size( + con: ffi::io_connect_t, + key: &[i8], +) -> Result<(ffi::KeyData_t, ffi::Val_t), i32> { + let mut input_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); + let mut output_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); + let mut val: ffi::Val_t = mem::zeroed::<ffi::Val_t>(); + + input_structure.key = strtoul(key); + input_structure.data8 = ffi::SMC_CMD_READ_KEYINFO; + + let result = perform_call( + con, + ffi::KERNEL_INDEX_SMC, + &input_structure, + &mut output_structure, + ); + if result != ffi::KIO_RETURN_SUCCESS { + return Err(result); + } + + val.data_size = output_structure.key_info.data_size; + ultostr( + val.data_type.as_mut_ptr(), + output_structure.key_info.data_type, + ); + input_structure.key_info.data_size = val.data_size; + input_structure.data8 = ffi::SMC_CMD_READ_BYTES; + Ok((input_structure, val)) +} + +unsafe fn read_key( + con: ffi::io_connect_t, + input_structure: &ffi::KeyData_t, + mut val: ffi::Val_t, +) -> Result<ffi::Val_t, i32> { + let mut output_structure: ffi::KeyData_t = mem::zeroed::<ffi::KeyData_t>(); + + match perform_call( + con, + ffi::KERNEL_INDEX_SMC, + input_structure, + &mut output_structure, + ) { + ffi::KIO_RETURN_SUCCESS => { + libc::memcpy( + val.bytes.as_mut_ptr() as *mut c_void, + output_structure.bytes.as_mut_ptr() as *mut c_void, + mem::size_of::<[u8; 32]>(), + ); + Ok(val) + } + result => Err(result), + } +} + +fn get_temperature_inner( + con: ffi::io_connect_t, + input_structure: &ffi::KeyData_t, + original_val: &ffi::Val_t, +) -> Option<f32> { + unsafe { + if let Ok(val) = read_key(con, input_structure, (*original_val).clone()) { + if val.data_size > 0 + && libc::strcmp(val.data_type.as_ptr(), b"sp78\0".as_ptr() as *const i8) == 0 + { + // convert sp78 value to temperature + let x = (i32::from(val.bytes[0]) << 6) + (i32::from(val.bytes[1]) >> 2); + return Some(x as f32 / 64f32); + } + } + } + None +} + +pub(crate) fn get_temperature(con: ffi::io_connect_t, key: &[i8]) -> Option<f32> { + unsafe { + let (input_structure, val) = get_key_size(con, key).ok()?; + get_temperature_inner(con, &input_structure, &val) + } +} diff --git a/vendor/sysinfo/src/apple/macos/disk.rs b/vendor/sysinfo/src/apple/macos/disk.rs new file mode 100644 index 000000000..7cc5a83ce --- /dev/null +++ b/vendor/sysinfo/src/apple/macos/disk.rs @@ -0,0 +1,199 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::{ffi, utils}; +use crate::utils::to_cpath; +use crate::{Disk, DiskType}; + +use core_foundation_sys::base::{kCFAllocatorDefault, kCFAllocatorNull, CFRelease}; +use core_foundation_sys::dictionary::{CFDictionaryGetValueIfPresent, CFDictionaryRef}; +use core_foundation_sys::number::{kCFBooleanTrue, CFBooleanRef}; +use core_foundation_sys::string as cfs; + +use libc::{c_char, c_int, c_void, statfs}; + +use std::ffi::{OsStr, OsString}; +use std::mem; +use std::os::unix::ffi::OsStrExt; +use std::path::PathBuf; +use std::ptr; + +fn to_path(mount_path: &[c_char]) -> Option<PathBuf> { + let mut tmp = Vec::with_capacity(mount_path.len()); + for &c in mount_path { + if c == 0 { + break; + } + tmp.push(c as u8); + } + if tmp.is_empty() { + None + } else { + let path = OsStr::from_bytes(&tmp); + Some(PathBuf::from(path)) + } +} + +pub(crate) fn get_disks(session: ffi::DASessionRef) -> Vec<Disk> { + if session.is_null() { + return Vec::new(); + } + unsafe { + let count = libc::getfsstat(ptr::null_mut(), 0, libc::MNT_NOWAIT); + if count < 1 { + return Vec::new(); + } + let bufsize = count * mem::size_of::<libc::statfs>() as c_int; + let mut disks = Vec::with_capacity(count as _); + let count = libc::getfsstat(disks.as_mut_ptr(), bufsize, libc::MNT_NOWAIT); + if count < 1 { + return Vec::new(); + } + disks.set_len(count as _); + disks + .into_iter() + .filter_map(|c_disk| { + let mount_point = to_path(&c_disk.f_mntonname)?; + let disk = ffi::DADiskCreateFromBSDName( + kCFAllocatorDefault as _, + session, + c_disk.f_mntfromname.as_ptr(), + ); + let dict = ffi::DADiskCopyDescription(disk); + if dict.is_null() { + return None; + } + // Keeping this around in case one might want the list of the available + // keys in "dict". + // core_foundation_sys::base::CFShow(dict as _); + let name = match get_str_value(dict, b"DAMediaName\0").map(OsString::from) { + Some(n) => n, + None => return None, + }; + let removable = get_bool_value(dict, b"DAMediaRemovable\0").unwrap_or(false); + let ejectable = get_bool_value(dict, b"DAMediaEjectable\0").unwrap_or(false); + // This is very hackish but still better than nothing... + let type_ = if let Some(model) = get_str_value(dict, b"DADeviceModel\0") { + if model.contains("SSD") { + DiskType::SSD + } else { + // We just assume by default that this is a HDD + DiskType::HDD + } + } else { + DiskType::Unknown(-1) + }; + + CFRelease(dict as _); + new_disk(name, mount_point, type_, removable || ejectable) + }) + .collect::<Vec<_>>() + } +} + +unsafe fn get_dict_value<T, F: FnOnce(*const c_void) -> Option<T>>( + dict: CFDictionaryRef, + key: &[u8], + callback: F, +) -> Option<T> { + let key = ffi::CFStringCreateWithCStringNoCopy( + ptr::null_mut(), + key.as_ptr() as *const c_char, + cfs::kCFStringEncodingUTF8, + kCFAllocatorNull as _, + ); + let mut value = std::ptr::null(); + let ret = if CFDictionaryGetValueIfPresent(dict, key as _, &mut value) != 0 { + callback(value) + } else { + None + }; + CFRelease(key as _); + ret +} + +unsafe fn get_str_value(dict: CFDictionaryRef, key: &[u8]) -> Option<String> { + get_dict_value(dict, key, |v| { + let v = v as cfs::CFStringRef; + + let len_utf16 = cfs::CFStringGetLength(v); + let len_bytes = len_utf16 as usize * 2; // Two bytes per UTF-16 codepoint. + + let v_ptr = cfs::CFStringGetCStringPtr(v, cfs::kCFStringEncodingUTF8); + if v_ptr.is_null() { + // Fallback on CFStringGetString to read the underlying bytes from the CFString. + let mut buf = vec![0; len_bytes]; + let success = cfs::CFStringGetCString( + v, + buf.as_mut_ptr(), + len_bytes as _, + cfs::kCFStringEncodingUTF8, + ); + + if success != 0 { + utils::vec_to_rust(buf) + } else { + None + } + } else { + utils::cstr_to_rust_with_size(v_ptr, Some(len_bytes)) + } + }) +} + +unsafe fn get_bool_value(dict: CFDictionaryRef, key: &[u8]) -> Option<bool> { + get_dict_value(dict, key, |v| Some(v as CFBooleanRef == kCFBooleanTrue)) +} + +fn new_disk( + name: OsString, + mount_point: PathBuf, + type_: DiskType, + is_removable: bool, +) -> Option<Disk> { + let mount_point_cpath = to_cpath(&mount_point); + let mut total_space = 0; + let mut available_space = 0; + let mut file_system = None; + unsafe { + let mut stat: statfs = mem::zeroed(); + if statfs(mount_point_cpath.as_ptr() as *const i8, &mut stat) == 0 { + // APFS is "special" because its a snapshot-based filesystem, and modern + // macOS devices take full advantage of this. + // + // By default, listing volumes with `statfs` can return both the root-level + // "data" partition and any snapshots that exist. However, other than some flags and + // reserved(undocumented) bytes, there is no difference between the OS boot snapshot + // and the "data" partition. + // + // To avoid duplicating the number of disks (and therefore available space, etc), only return + // a disk (which is really a partition with APFS) if it is the root of the filesystem. + let is_root = stat.f_flags & libc::MNT_ROOTFS as u32 == 0; + if !is_root { + return None; + } + + total_space = u64::from(stat.f_bsize).saturating_mul(stat.f_blocks); + available_space = u64::from(stat.f_bsize).saturating_mul(stat.f_bavail); + let mut vec = Vec::with_capacity(stat.f_fstypename.len()); + for x in &stat.f_fstypename { + if *x == 0 { + break; + } + vec.push(*x as u8); + } + file_system = Some(vec); + } + if total_space == 0 { + return None; + } + Some(Disk { + type_, + name, + file_system: file_system.unwrap_or_else(|| b"<Unknown>".to_vec()), + mount_point, + total_space, + available_space, + is_removable, + }) + } +} diff --git a/vendor/sysinfo/src/apple/macos/ffi.rs b/vendor/sysinfo/src/apple/macos/ffi.rs new file mode 100644 index 000000000..f884701d9 --- /dev/null +++ b/vendor/sysinfo/src/apple/macos/ffi.rs @@ -0,0 +1,156 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use core_foundation_sys::base::CFAllocatorRef; +use core_foundation_sys::dictionary::CFMutableDictionaryRef; +use core_foundation_sys::string::{CFStringEncoding, CFStringRef}; + +use libc::{c_char, c_void}; +#[cfg(not(feature = "apple-sandbox"))] +use libc::{mach_port_t, size_t}; + +pub(crate) use crate::sys::ffi::*; + +#[cfg(not(feature = "apple-sandbox"))] +extern "C" { + // The proc_* PID functions are internal Apple APIs which are not + // allowed in App store releases as Apple blocks any binary using them. + + // IOKit is only available on MacOS: https://developer.apple.com/documentation/iokit, and when not running inside + // of the default macOS sandbox. + pub fn IOMasterPort(a: i32, b: *mut mach_port_t) -> i32; + + pub fn IOServiceMatching(a: *const c_char) -> *mut c_void; + + pub fn IOServiceGetMatchingServices( + a: mach_port_t, + b: *mut c_void, + c: *mut io_iterator_t, + ) -> i32; + + pub fn IOIteratorNext(iterator: io_iterator_t) -> io_object_t; + + pub fn IOObjectRelease(obj: io_object_t) -> i32; + + pub fn IOServiceOpen(device: io_object_t, a: u32, t: u32, x: *mut io_connect_t) -> i32; + + pub fn IOServiceClose(a: io_connect_t) -> i32; + + pub fn IOConnectCallStructMethod( + connection: mach_port_t, + selector: u32, + inputStruct: *const KeyData_t, + inputStructCnt: size_t, + outputStruct: *mut KeyData_t, + outputStructCnt: *mut size_t, + ) -> i32; + // pub fn IORegistryEntryCreateCFProperties( + // entry: io_registry_entry_t, + // properties: *mut CFMutableDictionaryRef, + // allocator: CFAllocatorRef, + // options: IOOptionBits, + // ) -> kern_return_t; + // pub fn IORegistryEntryGetName(entry: io_registry_entry_t, name: *mut c_char) -> kern_return_t; +} + +extern "C" { + pub fn CFStringCreateWithCStringNoCopy( + alloc: *mut c_void, + cStr: *const c_char, + encoding: CFStringEncoding, + contentsDeallocator: *mut c_void, + ) -> CFStringRef; + + // Disk information functions are non-operational on iOS because of the sandboxing + // restrictions of apps, so they don't can't filesystem information. This results in + // mountedVolumeURLs and similar returning `nil`. Hence, they are MacOS specific here. + + pub fn DASessionCreate(allocator: CFAllocatorRef) -> DASessionRef; + + // pub fn DADiskCreateFromVolumePath( + // allocator: CFAllocatorRef, + // session: DASessionRef, + // path: CFURLRef, + // ) -> DADiskRef; + pub fn DADiskCreateFromBSDName( + allocator: CFAllocatorRef, + session: DASessionRef, + path: *const c_char, + ) -> DADiskRef; + // pub fn DADiskGetBSDName(disk: DADiskRef) -> *const c_char; + + pub fn DADiskCopyDescription(disk: DADiskRef) -> CFMutableDictionaryRef; +} + +pub type DADiskRef = *const __DADisk; +pub type DASessionRef = *const __DASession; + +// We need to wrap `DASessionRef` to be sure `System` remains Send+Sync. +pub struct SessionWrap(pub DASessionRef); + +unsafe impl Send for SessionWrap {} +unsafe impl Sync for SessionWrap {} + +#[cfg(not(feature = "apple-sandbox"))] +mod io_service { + use super::mach_port_t; + + #[allow(non_camel_case_types)] + pub type io_object_t = mach_port_t; + #[allow(non_camel_case_types)] + pub type io_connect_t = io_object_t; + #[allow(non_camel_case_types)] + pub type io_iterator_t = io_object_t; + + #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] + #[repr(C)] + pub struct KeyData_vers_t { + pub major: u8, + pub minor: u8, + pub build: u8, + pub reserved: [u8; 1], + pub release: u16, + } + + #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] + #[repr(C)] + pub struct KeyData_pLimitData_t { + pub version: u16, + pub length: u16, + pub cpu_plimit: u32, + pub gpu_plimit: u32, + pub mem_plimit: u32, + } + + #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] + #[repr(C)] + pub struct KeyData_keyInfo_t { + pub data_size: u32, + pub data_type: u32, + pub data_attributes: u8, + } + + #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] + #[repr(C)] + pub struct KeyData_t { + pub key: u32, + pub vers: KeyData_vers_t, + pub p_limit_data: KeyData_pLimitData_t, + pub key_info: KeyData_keyInfo_t, + pub result: u8, + pub status: u8, + pub data8: u8, + pub data32: u32, + pub bytes: [i8; 32], // SMCBytes_t + } + + pub const KERNEL_INDEX_SMC: i32 = 2; + pub const SMC_CMD_READ_KEYINFO: u8 = 9; + pub const SMC_CMD_READ_BYTES: u8 = 5; + + pub const KIO_RETURN_SUCCESS: i32 = 0; +} + +#[cfg(feature = "apple-sandbox")] +mod io_service {} + +pub use io_service::*; diff --git a/vendor/sysinfo/src/apple/macos/mod.rs b/vendor/sysinfo/src/apple/macos/mod.rs new file mode 100644 index 000000000..172bbfddc --- /dev/null +++ b/vendor/sysinfo/src/apple/macos/mod.rs @@ -0,0 +1,17 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub mod disk; +pub mod ffi; + +#[cfg(not(feature = "apple-sandbox"))] +pub mod system; + +#[cfg(not(feature = "apple-sandbox"))] +pub mod component; +#[cfg(not(feature = "apple-sandbox"))] +pub mod process; + +#[cfg(feature = "apple-sandbox")] +pub use crate::sys::app_store::component; +#[cfg(feature = "apple-sandbox")] +pub use crate::sys::app_store::process; diff --git a/vendor/sysinfo/src/apple/macos/process.rs b/vendor/sysinfo/src/apple/macos/process.rs new file mode 100644 index 000000000..f146126cf --- /dev/null +++ b/vendor/sysinfo/src/apple/macos/process.rs @@ -0,0 +1,643 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::ffi::CStr; +use std::mem::{self, MaybeUninit}; +use std::ops::Deref; +use std::path::{Path, PathBuf}; + +use std::borrow::Borrow; + +use libc::{c_int, c_void, kill, size_t}; + +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; + +use crate::sys::process::ThreadStatus; +use crate::sys::system::Wrap; + +#[doc = include_str!("../../../md_doc/process.md")] +pub struct Process { + pub(crate) name: String, + pub(crate) cmd: Vec<String>, + pub(crate) exe: PathBuf, + pid: Pid, + parent: Option<Pid>, + pub(crate) environ: Vec<String>, + cwd: PathBuf, + pub(crate) root: PathBuf, + pub(crate) memory: u64, + pub(crate) virtual_memory: u64, + old_utime: u64, + old_stime: u64, + start_time: u64, + run_time: u64, + updated: bool, + cpu_usage: f32, + user_id: Option<Uid>, + group_id: Option<Gid>, + pub(crate) process_status: ProcessStatus, + /// Status of process (running, stopped, waiting, etc). `None` means `sysinfo` doesn't have + /// enough rights to get this information. + /// + /// This is very likely this one that you want instead of `process_status`. + pub status: Option<ThreadStatus>, + pub(crate) old_read_bytes: u64, + pub(crate) old_written_bytes: u64, + pub(crate) read_bytes: u64, + pub(crate) written_bytes: u64, +} + +impl Process { + pub(crate) fn new_empty(pid: Pid, exe: PathBuf, name: String, cwd: PathBuf) -> Process { + Process { + name, + pid, + parent: None, + cmd: Vec::new(), + environ: Vec::new(), + exe, + cwd, + root: PathBuf::new(), + memory: 0, + virtual_memory: 0, + cpu_usage: 0., + old_utime: 0, + old_stime: 0, + updated: true, + start_time: 0, + run_time: 0, + user_id: None, + group_id: None, + process_status: ProcessStatus::Unknown(0), + status: None, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + } + } + + pub(crate) fn new(pid: Pid, parent: Option<Pid>, start_time: u64, run_time: u64) -> Process { + Process { + name: String::new(), + pid, + parent, + cmd: Vec::new(), + environ: Vec::new(), + exe: PathBuf::new(), + cwd: PathBuf::new(), + root: PathBuf::new(), + memory: 0, + virtual_memory: 0, + cpu_usage: 0., + old_utime: 0, + old_stime: 0, + updated: true, + start_time, + run_time, + user_id: None, + group_id: None, + process_status: ProcessStatus::Unknown(0), + status: 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 = crate::sys::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.process_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 { + read_bytes: self.read_bytes - self.old_read_bytes, + total_read_bytes: self.read_bytes, + written_bytes: self.written_bytes - self.old_written_bytes, + total_written_bytes: self.written_bytes, + } + } + + fn user_id(&self) -> Option<&Uid> { + self.user_id.as_ref() + } + + fn group_id(&self) -> Option<Gid> { + self.group_id + } +} + +#[allow(deprecated)] // Because of libc::mach_absolute_time. +pub(crate) fn compute_cpu_usage( + p: &mut Process, + task_info: libc::proc_taskinfo, + system_time: u64, + user_time: u64, + time_interval: Option<f64>, +) { + if let Some(time_interval) = time_interval { + let total_existing_time = p.old_stime.saturating_add(p.old_utime); + if time_interval > 0.000001 && total_existing_time > 0 { + let total_current_time = task_info + .pti_total_system + .saturating_add(task_info.pti_total_user); + + let total_time_diff = total_current_time.saturating_sub(total_existing_time); + p.cpu_usage = (total_time_diff as f64 / time_interval * 100.) as f32; + } else { + p.cpu_usage = 0.; + } + p.old_stime = task_info.pti_total_system; + p.old_utime = task_info.pti_total_user; + } else { + unsafe { + // This is the "backup way" of CPU computation. + let time = libc::mach_absolute_time(); + let task_time = user_time + .saturating_add(system_time) + .saturating_add(task_info.pti_total_user) + .saturating_add(task_info.pti_total_system); + + let system_time_delta = if task_time < p.old_utime { + task_time + } else { + task_time.saturating_sub(p.old_utime) + }; + let time_delta = if time < p.old_stime { + time + } else { + time.saturating_sub(p.old_stime) + }; + p.old_utime = task_time; + p.old_stime = time; + p.cpu_usage = if time_delta == 0 { + 0f32 + } else { + (system_time_delta as f64 * 100f64 / time_delta as f64) as f32 + }; + } + } + p.updated = true; +} + +/*pub 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; +}*/ + +#[inline] +pub(crate) fn has_been_updated(p: &mut Process) -> bool { + let old = p.updated; + p.updated = false; + old +} + +#[inline] +pub(crate) fn force_update(p: &mut Process) { + p.updated = true; +} + +unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo { + let mut task_info = mem::zeroed::<libc::proc_taskinfo>(); + // If it doesn't work, we just don't have memory information for this process + // so it's "fine". + libc::proc_pidinfo( + pid.0, + libc::PROC_PIDTASKINFO, + 0, + &mut task_info as *mut libc::proc_taskinfo as *mut c_void, + mem::size_of::<libc::proc_taskinfo>() as _, + ); + task_info +} + +#[inline] +fn check_if_pid_is_alive(pid: Pid) -> bool { + unsafe { kill(pid.0, 0) == 0 } + // For the full complete check, it'd need to be (but that seems unneeded): + // unsafe { + // *libc::__errno_location() == libc::ESRCH + // } +} + +#[inline] +fn do_not_get_env_path(_: &str, _: &mut PathBuf, _: &mut bool) {} + +#[inline] +fn do_get_env_path(env: &str, root: &mut PathBuf, check: &mut bool) { + if *check && env.starts_with("PATH=") { + *check = false; + *root = Path::new(&env[5..]).to_path_buf(); + } +} + +pub(crate) fn update_process( + wrap: &Wrap, + pid: Pid, + mut size: size_t, + time_interval: Option<f64>, + now: u64, + refresh_kind: ProcessRefreshKind, +) -> Result<Option<Process>, ()> { + let mut proc_args = Vec::with_capacity(size as usize); + + unsafe { + if let Some(ref mut p) = (*wrap.0.get()).get_mut(&pid) { + if p.memory == 0 { + // We don't have access to this process' information. + force_update(p); + return if check_if_pid_is_alive(pid) { + Ok(None) + } else { + Err(()) + }; + } + let task_info = get_task_info(pid); + let mut thread_info = mem::zeroed::<libc::proc_threadinfo>(); + let (user_time, system_time, thread_status) = if libc::proc_pidinfo( + pid.0, + libc::PROC_PIDTHREADINFO, + 0, + &mut thread_info as *mut libc::proc_threadinfo as *mut c_void, + mem::size_of::<libc::proc_threadinfo>() as _, + ) != 0 + { + ( + thread_info.pth_user_time, + thread_info.pth_system_time, + Some(ThreadStatus::from(thread_info.pth_run_state)), + ) + } else { + // It very likely means that the process is dead... + return if check_if_pid_is_alive(pid) { + Ok(None) + } else { + Err(()) + }; + }; + p.status = thread_status; + if refresh_kind.cpu() { + compute_cpu_usage(p, task_info, system_time, user_time, time_interval); + } + + p.memory = task_info.pti_resident_size / 1_000; + p.virtual_memory = task_info.pti_virtual_size / 1_000; + if refresh_kind.disk_usage() { + update_proc_disk_activity(p); + } + return Ok(None); + } + + let mut vnodepathinfo = mem::zeroed::<libc::proc_vnodepathinfo>(); + let result = libc::proc_pidinfo( + pid.0, + libc::PROC_PIDVNODEPATHINFO, + 0, + &mut vnodepathinfo as *mut _ as *mut _, + mem::size_of::<libc::proc_vnodepathinfo>() as _, + ); + let cwd = if result > 0 { + let buffer = vnodepathinfo.pvi_cdir.vip_path; + let buffer = CStr::from_ptr(buffer.as_ptr() as _); + buffer + .to_str() + .map(PathBuf::from) + .unwrap_or_else(|_| PathBuf::new()) + } else { + PathBuf::new() + }; + + let mut info = mem::zeroed::<libc::proc_bsdinfo>(); + if libc::proc_pidinfo( + pid.0, + libc::PROC_PIDTBSDINFO, + 0, + &mut info as *mut _ as *mut _, + mem::size_of::<libc::proc_bsdinfo>() as _, + ) != mem::size_of::<libc::proc_bsdinfo>() as _ + { + let mut buffer: Vec<u8> = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _); + match libc::proc_pidpath( + pid.0, + buffer.as_mut_ptr() as *mut _, + libc::PROC_PIDPATHINFO_MAXSIZE as _, + ) { + x if x > 0 => { + buffer.set_len(x as _); + let tmp = String::from_utf8_unchecked(buffer); + let exe = PathBuf::from(tmp); + let name = exe + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .to_owned(); + return Ok(Some(Process::new_empty(pid, exe, name, cwd))); + } + _ => {} + } + return Err(()); + } + let parent = match info.pbi_ppid as i32 { + 0 => None, + p => Some(Pid(p)), + }; + + let ptr: *mut u8 = proc_args.as_mut_slice().as_mut_ptr(); + let mut mib = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid.0 as _]; + /* + * /---------------\ 0x00000000 + * | ::::::::::::: | + * |---------------| <-- Beginning of data returned by sysctl() is here. + * | argc | + * |---------------| + * | exec_path | + * |---------------| + * | 0 | + * |---------------| + * | arg[0] | + * |---------------| + * | 0 | + * |---------------| + * | arg[n] | + * |---------------| + * | 0 | + * |---------------| + * | env[0] | + * |---------------| + * | 0 | + * |---------------| + * | env[n] | + * |---------------| + * | ::::::::::::: | + * |---------------| <-- Top of stack. + * : : + * : : + * \---------------/ 0xffffffff + */ + if libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + ptr as *mut c_void, + &mut size, + std::ptr::null_mut(), + 0, + ) == -1 + { + return Err(()); // not enough rights I assume? + } + let mut n_args: c_int = 0; + libc::memcpy( + (&mut n_args) as *mut c_int as *mut c_void, + ptr as *const c_void, + mem::size_of::<c_int>(), + ); + + let mut cp = ptr.add(mem::size_of::<c_int>()); + let mut start = cp; + + let start_time = info.pbi_start_tvsec; + let run_time = now.saturating_sub(start_time); + + let mut p = if cp < ptr.add(size) { + while cp < ptr.add(size) && *cp != 0 { + cp = cp.offset(1); + } + let exe = Path::new(get_unchecked_str(cp, start).as_str()).to_path_buf(); + let name = exe + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .to_owned(); + while cp < ptr.add(size) && *cp == 0 { + cp = cp.offset(1); + } + start = cp; + let mut c = 0; + let mut cmd = Vec::with_capacity(n_args as usize); + while c < n_args && cp < ptr.add(size) { + if *cp == 0 { + c += 1; + cmd.push(get_unchecked_str(cp, start)); + start = cp.offset(1); + } + cp = cp.offset(1); + } + + #[inline] + unsafe fn get_environ<F: Fn(&str, &mut PathBuf, &mut bool)>( + ptr: *mut u8, + mut cp: *mut u8, + size: size_t, + mut root: PathBuf, + callback: F, + ) -> (Vec<String>, PathBuf) { + let mut environ = Vec::with_capacity(10); + let mut start = cp; + let mut check = true; + while cp < ptr.add(size) { + if *cp == 0 { + if cp == start { + break; + } + let e = get_unchecked_str(cp, start); + callback(&e, &mut root, &mut check); + environ.push(e); + start = cp.offset(1); + } + cp = cp.offset(1); + } + (environ, root) + } + + let (environ, root) = if exe.is_absolute() { + if let Some(parent_path) = exe.parent() { + get_environ( + ptr, + cp, + size, + parent_path.to_path_buf(), + do_not_get_env_path, + ) + } else { + get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) + } + } else { + get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) + }; + let mut p = Process::new(pid, parent, start_time, run_time); + + p.exe = exe; + p.name = name; + p.cwd = cwd; + p.cmd = parse_command_line(&cmd); + p.environ = environ; + p.root = root; + p + } else { + Process::new(pid, parent, start_time, run_time) + }; + + let task_info = get_task_info(pid); + + p.memory = task_info.pti_resident_size / 1_000; + p.virtual_memory = task_info.pti_virtual_size / 1_000; + + p.user_id = Some(Uid(info.pbi_uid)); + p.group_id = Some(Gid(info.pbi_gid)); + p.process_status = ProcessStatus::from(info.pbi_status); + if refresh_kind.disk_usage() { + update_proc_disk_activity(&mut p); + } + Ok(Some(p)) + } +} + +fn update_proc_disk_activity(p: &mut Process) { + p.old_read_bytes = p.read_bytes; + p.old_written_bytes = p.written_bytes; + + let mut pidrusage = MaybeUninit::<libc::rusage_info_v2>::uninit(); + + unsafe { + let retval = libc::proc_pid_rusage( + p.pid().0 as _, + libc::RUSAGE_INFO_V2, + pidrusage.as_mut_ptr() as _, + ); + + if retval < 0 { + sysinfo_debug!("proc_pid_rusage failed: {:?}", retval); + } else { + let pidrusage = pidrusage.assume_init(); + p.read_bytes = pidrusage.ri_diskio_bytesread; + p.written_bytes = pidrusage.ri_diskio_byteswritten; + } + } +} + +#[allow(clippy::uninit_vec)] +pub(crate) fn get_proc_list() -> Option<Vec<Pid>> { + unsafe { + let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); + if count < 1 { + return None; + } + let mut pids: Vec<Pid> = Vec::with_capacity(count as usize); + pids.set_len(count as usize); + let count = count * mem::size_of::<Pid>() as i32; + let x = libc::proc_listallpids(pids.as_mut_ptr() as *mut c_void, count); + + if x < 1 || x as usize >= pids.len() { + None + } else { + pids.set_len(x as usize); + Some(pids) + } + } +} + +unsafe fn get_unchecked_str(cp: *mut u8, start: *mut u8) -> String { + let len = cp as usize - start as usize; + let part = Vec::from_raw_parts(start, len, len); + let tmp = String::from_utf8_unchecked(part.clone()); + mem::forget(part); + tmp +} + +fn parse_command_line<T: Deref<Target = str> + Borrow<str>>(cmd: &[T]) -> Vec<String> { + let mut x = 0; + let mut command = Vec::with_capacity(cmd.len()); + while x < cmd.len() { + let mut y = x; + if cmd[y].starts_with('\'') || cmd[y].starts_with('"') { + let c = if cmd[y].starts_with('\'') { '\'' } else { '"' }; + while y < cmd.len() && !cmd[y].ends_with(c) { + y += 1; + } + command.push(cmd[x..y].join(" ")); + x = y; + } else { + command.push(cmd[x].to_owned()); + } + x += 1; + } + command +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_get_path() { + let mut path = PathBuf::new(); + let mut check = true; + + do_get_env_path("PATH=tadam", &mut path, &mut check); + + assert!(!check); + assert_eq!(path, PathBuf::from("tadam")); + } +} diff --git a/vendor/sysinfo/src/apple/macos/system.rs b/vendor/sysinfo/src/apple/macos/system.rs new file mode 100644 index 000000000..949532234 --- /dev/null +++ b/vendor/sysinfo/src/apple/macos/system.rs @@ -0,0 +1,136 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#[allow(deprecated)] +use libc::{mach_timebase_info, mach_timebase_info_data_t}; + +use libc::{ + host_processor_info, mach_port_t, munmap, natural_t, processor_cpu_load_info, + processor_cpu_load_info_t, sysconf, vm_page_size, PROCESSOR_CPU_LOAD_INFO, _SC_CLK_TCK, +}; +use std::ptr::null_mut; + +unsafe fn free_cpu_load_info(cpu_load: &mut processor_cpu_load_info_t) { + if !cpu_load.is_null() { + munmap(*cpu_load as _, vm_page_size); + *cpu_load = null_mut(); + } +} + +pub(crate) struct SystemTimeInfo { + timebase_to_ns: f64, + clock_per_sec: f64, + old_cpu_load: processor_cpu_load_info_t, + old_cpu_count: natural_t, +} + +unsafe impl Send for SystemTimeInfo {} +unsafe impl Sync for SystemTimeInfo {} + +impl SystemTimeInfo { + #[allow(deprecated)] // Everything related to mach_timebase_info_data_t + pub fn new(port: mach_port_t) -> Option<Self> { + unsafe { + let clock_ticks_per_sec = sysconf(_SC_CLK_TCK); + + // FIXME: Maybe check errno here? Problem is that if errno is not 0 before this call, + // we will get an error which isn't related... + // if let Some(er) = std::io::Error::last_os_error().raw_os_error() { + // if err != 0 { + // println!("==> {:?}", er); + // sysinfo_debug!("Failed to get _SC_CLK_TCK value, using old CPU tick measure system"); + // return None; + // } + // } + + let mut info = mach_timebase_info_data_t { numer: 0, denom: 0 }; + if mach_timebase_info(&mut info) != libc::KERN_SUCCESS { + sysinfo_debug!("mach_timebase_info failed, using default value of 1"); + info.numer = 1; + info.denom = 1; + } + + let mut old_cpu_load = null_mut(); + let old_cpu_count = match Self::update_ticks(port, &mut old_cpu_load) { + Some(c) => c, + None => { + sysinfo_debug!("host_processor_info failed, using old CPU tick measure system"); + return None; + } + }; + + let nano_per_seconds = 1_000_000_000.; + sysinfo_debug!(""); + Some(Self { + timebase_to_ns: info.numer as f64 / info.denom as f64, + clock_per_sec: nano_per_seconds / clock_ticks_per_sec as f64, + old_cpu_load, + old_cpu_count, + }) + } + } + + fn update_ticks( + port: mach_port_t, + cpu_load: &mut processor_cpu_load_info_t, + ) -> Option<natural_t> { + let mut info_size = std::mem::size_of::<processor_cpu_load_info_t>() as _; + let mut cpu_count = 0; + + unsafe { + free_cpu_load_info(cpu_load); + + if host_processor_info( + port, + PROCESSOR_CPU_LOAD_INFO, + &mut cpu_count, + cpu_load as *mut _ as *mut _, + &mut info_size, + ) != 0 + { + sysinfo_debug!("host_processor_info failed, not updating CPU ticks usage..."); + None + } else if cpu_count < 1 || cpu_load.is_null() { + None + } else { + Some(cpu_count) + } + } + } + + pub fn get_time_interval(&mut self, port: mach_port_t) -> f64 { + let mut total = 0; + let mut new_cpu_load = null_mut(); + + let new_cpu_count = match Self::update_ticks(port, &mut new_cpu_load) { + Some(c) => c, + None => return 0., + }; + let cpu_count = std::cmp::min(self.old_cpu_count, new_cpu_count); + unsafe { + for i in 0..cpu_count { + let new_load: &processor_cpu_load_info = &*new_cpu_load.offset(i as _); + let old_load: &processor_cpu_load_info = &*self.old_cpu_load.offset(i as _); + for (new, old) in new_load.cpu_ticks.iter().zip(old_load.cpu_ticks.iter()) { + if new > old { + total += new - old; + } + } + } + + free_cpu_load_info(&mut self.old_cpu_load); + self.old_cpu_load = new_cpu_load; + self.old_cpu_count = new_cpu_count; + + // Now we convert the ticks to nanoseconds: + total as f64 / self.timebase_to_ns * self.clock_per_sec / cpu_count as f64 + } + } +} + +impl Drop for SystemTimeInfo { + fn drop(&mut self) { + unsafe { + free_cpu_load_info(&mut self.old_cpu_load); + } + } +} diff --git a/vendor/sysinfo/src/apple/mod.rs b/vendor/sysinfo/src/apple/mod.rs new file mode 100644 index 000000000..daff800a2 --- /dev/null +++ b/vendor/sysinfo/src/apple/mod.rs @@ -0,0 +1,31 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#[cfg(target_os = "macos")] +pub(crate) mod macos; +#[cfg(target_os = "macos")] +pub(crate) use self::macos as inner; + +#[cfg(target_os = "ios")] +pub(crate) mod ios; +#[cfg(target_os = "ios")] +pub(crate) use self::ios as inner; + +#[cfg(any(target_os = "ios", feature = "apple-sandbox"))] +pub(crate) mod app_store; + +pub mod component; +pub mod cpu; +pub mod disk; +mod ffi; +pub mod network; +pub mod process; +pub mod system; +pub mod users; +mod utils; + +pub use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo/src/apple/network.rs b/vendor/sysinfo/src/apple/network.rs new file mode 100644 index 000000000..f1316920e --- /dev/null +++ b/vendor/sysinfo/src/apple/network.rs @@ -0,0 +1,239 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use libc::{self, c_char, if_msghdr2, CTL_NET, NET_RT_IFLIST2, PF_ROUTE, RTM_IFINFO2}; + +use std::collections::{hash_map, HashMap}; +use std::ptr::null_mut; + +use crate::{NetworkExt, NetworksExt, NetworksIter}; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $new_val; + }}; +} + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Self { + Networks { + interfaces: HashMap::new(), + } + } + + #[allow(clippy::cast_ptr_alignment)] + #[allow(clippy::uninit_vec)] + fn update_networks(&mut self) { + let mib = &mut [CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0]; + let mut len = 0; + unsafe { + if libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + null_mut(), + &mut len, + null_mut(), + 0, + ) < 0 + { + // TODO: might be nice to put an error in here... + return; + } + let mut buf = Vec::with_capacity(len); + buf.set_len(len); + if libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + buf.as_mut_ptr(), + &mut len, + null_mut(), + 0, + ) < 0 + { + // TODO: might be nice to put an error in here... + return; + } + let buf = buf.as_ptr() as *const c_char; + let lim = buf.add(len); + let mut next = buf; + while next < lim { + let ifm = next as *const libc::if_msghdr; + next = next.offset((*ifm).ifm_msglen as isize); + if (*ifm).ifm_type == RTM_IFINFO2 as u8 { + // The interface (line description) name stored at ifname will be returned in + // the default coded character set identifier (CCSID) currently in effect for + // the job. If this is not a single byte CCSID, then storage greater than + // IFNAMSIZ (16) bytes may be needed. 22 bytes is large enough for all CCSIDs. + let mut name = vec![0u8; libc::IFNAMSIZ + 6]; + + let if2m: *const if_msghdr2 = ifm as *const if_msghdr2; + let pname = + libc::if_indextoname((*if2m).ifm_index as _, name.as_mut_ptr() as _); + if pname.is_null() { + continue; + } + name.set_len(libc::strlen(pname)); + let name = String::from_utf8_unchecked(name); + match self.interfaces.entry(name) { + hash_map::Entry::Occupied(mut e) => { + let mut interface = e.get_mut(); + old_and_new!( + interface, + current_out, + old_out, + (*if2m).ifm_data.ifi_obytes + ); + old_and_new!( + interface, + current_in, + old_in, + (*if2m).ifm_data.ifi_ibytes + ); + old_and_new!( + interface, + packets_in, + old_packets_in, + (*if2m).ifm_data.ifi_ipackets + ); + old_and_new!( + interface, + packets_out, + old_packets_out, + (*if2m).ifm_data.ifi_opackets + ); + old_and_new!( + interface, + errors_in, + old_errors_in, + (*if2m).ifm_data.ifi_ierrors + ); + old_and_new!( + interface, + errors_out, + old_errors_out, + (*if2m).ifm_data.ifi_oerrors + ); + interface.updated = true; + } + hash_map::Entry::Vacant(e) => { + let current_in = (*if2m).ifm_data.ifi_ibytes; + let current_out = (*if2m).ifm_data.ifi_obytes; + let packets_in = (*if2m).ifm_data.ifi_ipackets; + let packets_out = (*if2m).ifm_data.ifi_opackets; + let errors_in = (*if2m).ifm_data.ifi_ierrors; + let errors_out = (*if2m).ifm_data.ifi_oerrors; + + e.insert(NetworkData { + current_in, + old_in: current_in, + current_out, + old_out: current_out, + packets_in, + old_packets_in: packets_in, + packets_out, + old_packets_out: packets_out, + errors_in, + old_errors_in: errors_in, + errors_out, + old_errors_out: errors_out, + updated: true, + }); + } + } + } + } + } + } +} + +impl NetworksExt for Networks { + #[allow(clippy::needless_lifetimes)] + fn iter<'a>(&'a self) -> NetworksIter<'a> { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + for (_, data) in self.interfaces.iter_mut() { + data.updated = false; + } + self.update_networks(); + self.interfaces.retain(|_, data| data.updated); + } + + fn refresh(&mut self) { + self.update_networks(); + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +#[derive(PartialEq, Eq)] +pub struct NetworkData { + current_in: u64, + old_in: u64, + current_out: u64, + old_out: u64, + packets_in: u64, + old_packets_in: u64, + packets_out: u64, + old_packets_out: u64, + errors_in: u64, + old_errors_in: u64, + errors_out: u64, + old_errors_out: u64, + updated: bool, +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.current_in.saturating_sub(self.old_in) + } + + fn total_received(&self) -> u64 { + self.current_in + } + + fn transmitted(&self) -> u64 { + self.current_out.saturating_sub(self.old_out) + } + + fn total_transmitted(&self) -> u64 { + self.current_out + } + + fn packets_received(&self) -> u64 { + self.packets_in.saturating_sub(self.old_packets_in) + } + + fn total_packets_received(&self) -> u64 { + self.packets_in + } + + fn packets_transmitted(&self) -> u64 { + self.packets_out.saturating_sub(self.old_packets_out) + } + + fn total_packets_transmitted(&self) -> u64 { + self.packets_out + } + + fn errors_on_received(&self) -> u64 { + self.errors_in.saturating_sub(self.old_errors_in) + } + + fn total_errors_on_received(&self) -> u64 { + self.errors_in + } + + fn errors_on_transmitted(&self) -> u64 { + self.errors_out.saturating_sub(self.old_errors_out) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.errors_out + } +} diff --git a/vendor/sysinfo/src/apple/process.rs b/vendor/sysinfo/src/apple/process.rs new file mode 100644 index 000000000..e0f005bdc --- /dev/null +++ b/vendor/sysinfo/src/apple/process.rs @@ -0,0 +1,83 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::fmt; + +pub use crate::sys::inner::process::*; +use crate::ProcessStatus; + +#[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), + } + } +} + +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", + _ => "Unknown", + }) + } +} + +/// Enum describing the different status of a thread. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ThreadStatus { + /// Thread is running normally. + Running, + /// Thread is stopped. + Stopped, + /// Thread is waiting normally. + Waiting, + /// Thread is in an uninterruptible wait + Uninterruptible, + /// Thread is halted at a clean point. + Halted, + /// Unknown. + Unknown(i32), +} + +impl From<i32> for ThreadStatus { + fn from(status: i32) -> ThreadStatus { + match status { + 1 => ThreadStatus::Running, + 2 => ThreadStatus::Stopped, + 3 => ThreadStatus::Waiting, + 4 => ThreadStatus::Uninterruptible, + 5 => ThreadStatus::Halted, + x => ThreadStatus::Unknown(x), + } + } +} + +impl ThreadStatus { + /// Used to display `ThreadStatus`. + pub fn to_string(&self) -> &str { + match *self { + ThreadStatus::Running => "Running", + ThreadStatus::Stopped => "Stopped", + ThreadStatus::Waiting => "Waiting", + ThreadStatus::Uninterruptible => "Uninterruptible", + ThreadStatus::Halted => "Halted", + ThreadStatus::Unknown(_) => "Unknown", + } + } +} + +impl fmt::Display for ThreadStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} diff --git a/vendor/sysinfo/src/apple/system.rs b/vendor/sysinfo/src/apple/system.rs new file mode 100644 index 000000000..abe617dee --- /dev/null +++ b/vendor/sysinfo/src/apple/system.rs @@ -0,0 +1,763 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::component::Component; +use crate::sys::cpu::*; +use crate::sys::disk::*; +#[cfg(target_os = "macos")] +use crate::sys::ffi; +use crate::sys::network::Networks; +use crate::sys::process::*; +#[cfg(target_os = "macos")] +use core_foundation_sys::base::{kCFAllocatorDefault, CFRelease}; + +use crate::{ + CpuExt, CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, +}; + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +use crate::ProcessExt; + +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::mem; +use std::sync::Arc; +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +use std::time::SystemTime; + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +use libc::size_t; + +use libc::{ + c_char, c_int, c_void, host_statistics64, mach_port_t, sysconf, sysctl, sysctlbyname, timeval, + vm_statistics64, _SC_PAGESIZE, +}; + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +declare_signals! { + c_int, + Signal::Hangup => libc::SIGHUP, + Signal::Interrupt => libc::SIGINT, + Signal::Quit => libc::SIGQUIT, + Signal::Illegal => libc::SIGILL, + Signal::Trap => libc::SIGTRAP, + Signal::Abort => libc::SIGABRT, + Signal::IOT => libc::SIGIOT, + Signal::Bus => libc::SIGBUS, + Signal::FloatingPointException => libc::SIGFPE, + Signal::Kill => libc::SIGKILL, + Signal::User1 => libc::SIGUSR1, + Signal::Segv => libc::SIGSEGV, + Signal::User2 => libc::SIGUSR2, + Signal::Pipe => libc::SIGPIPE, + Signal::Alarm => libc::SIGALRM, + Signal::Term => libc::SIGTERM, + Signal::Child => libc::SIGCHLD, + Signal::Continue => libc::SIGCONT, + Signal::Stop => libc::SIGSTOP, + Signal::TSTP => libc::SIGTSTP, + Signal::TTIN => libc::SIGTTIN, + Signal::TTOU => libc::SIGTTOU, + Signal::Urgent => libc::SIGURG, + Signal::XCPU => libc::SIGXCPU, + Signal::XFSZ => libc::SIGXFSZ, + Signal::VirtualAlarm => libc::SIGVTALRM, + Signal::Profiling => libc::SIGPROF, + Signal::Winch => libc::SIGWINCH, + Signal::IO => libc::SIGIO, + // SIGPOLL doesn't exist on apple targets but since it's an equivalent of SIGIO on unix, + // we simply use the SIGIO constant. + Signal::Poll => libc::SIGIO, + Signal::Sys => libc::SIGSYS, + _ => None, +} +#[cfg(any(target_os = "ios", feature = "apple-sandbox"))] +declare_signals! { + c_int, + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: HashMap<Pid, Process>, + mem_total: u64, + mem_free: u64, + mem_available: u64, + swap_total: u64, + swap_free: u64, + global_cpu: Cpu, + cpus: Vec<Cpu>, + page_size_kb: u64, + components: Vec<Component>, + // Used to get CPU information, not supported on iOS, or inside the default macOS sandbox. + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + connection: Option<ffi::io_connect_t>, + disks: Vec<Disk>, + networks: Networks, + port: mach_port_t, + users: Vec<User>, + boot_time: u64, + // Used to get disk information, to be more specific, it's needed by the + // DADiskCreateFromVolumePath function. Not supported on iOS. + #[cfg(target_os = "macos")] + session: ffi::SessionWrap, + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + clock_info: Option<crate::sys::macos::system::SystemTimeInfo>, + got_cpu_frequency: bool, +} + +impl Drop for System { + fn drop(&mut self) { + #[cfg(target_os = "macos")] + unsafe { + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + if let Some(conn) = self.connection { + ffi::IOServiceClose(conn); + } + + if !self.session.0.is_null() { + CFRelease(self.session.0 as _); + } + } + } +} + +pub(crate) struct Wrap<'a>(pub UnsafeCell<&'a mut HashMap<Pid, Process>>); + +unsafe impl<'a> Send for Wrap<'a> {} +unsafe impl<'a> Sync for Wrap<'a> {} + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +impl System { + fn clear_procs(&mut self) { + use crate::sys::macos::process; + + self.process_list + .retain(|_, proc_| process::has_been_updated(proc_)); + } +} + +fn boot_time() -> u64 { + let mut boot_time = timeval { + tv_sec: 0, + tv_usec: 0, + }; + let mut len = std::mem::size_of::<timeval>(); + let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME]; + + unsafe { + if sysctl( + mib.as_mut_ptr(), + mib.len() as _, + &mut boot_time as *mut timeval as *mut _, + &mut len, + std::ptr::null_mut(), + 0, + ) < 0 + { + 0 + } else { + boot_time.tv_sec as _ + } + } +} + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +fn get_now() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|n| n.as_secs()) + .unwrap_or(0) +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + fn new_with_specifics(refreshes: RefreshKind) -> System { + unsafe { + let port = libc::mach_host_self(); + + let mut s = System { + process_list: HashMap::with_capacity(200), + mem_total: 0, + mem_free: 0, + mem_available: 0, + swap_total: 0, + swap_free: 0, + global_cpu: Cpu::new( + "0".to_owned(), + Arc::new(CpuData::new(std::ptr::null_mut(), 0)), + 0, + String::new(), + String::new(), + ), + cpus: Vec::new(), + page_size_kb: sysconf(_SC_PAGESIZE) as u64 / 1_000, + components: Vec::with_capacity(2), + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + connection: get_io_service_connection(), + disks: Vec::with_capacity(1), + networks: Networks::new(), + port, + users: Vec::new(), + boot_time: boot_time(), + #[cfg(target_os = "macos")] + session: ffi::SessionWrap(::std::ptr::null_mut()), + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + clock_info: crate::sys::macos::system::SystemTimeInfo::new(port), + got_cpu_frequency: false, + }; + s.refresh_specifics(refreshes); + s + } + } + + fn refresh_memory(&mut self) { + let mut mib = [0, 0]; + + unsafe { + // get system values + // get swap info + let mut xs: libc::xsw_usage = mem::zeroed::<libc::xsw_usage>(); + if get_sys_value( + libc::CTL_VM as _, + libc::VM_SWAPUSAGE as _, + mem::size_of::<libc::xsw_usage>(), + &mut xs as *mut _ as *mut c_void, + &mut mib, + ) { + self.swap_total = xs.xsu_total / 1_000; + self.swap_free = xs.xsu_avail / 1_000; + } + // get ram info + if self.mem_total < 1 { + get_sys_value( + libc::CTL_HW as _, + libc::HW_MEMSIZE as _, + mem::size_of::<u64>(), + &mut self.mem_total as *mut u64 as *mut c_void, + &mut mib, + ); + self.mem_total /= 1_000; + } + let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _; + let mut stat = mem::zeroed::<vm_statistics64>(); + if host_statistics64( + self.port, + libc::HOST_VM_INFO64, + &mut stat as *mut vm_statistics64 as *mut _, + &mut count, + ) == libc::KERN_SUCCESS + { + // From the apple documentation: + // + // /* + // * NB: speculative pages are already accounted for in "free_count", + // * so "speculative_count" is the number of "free" pages that are + // * used to hold data that was read speculatively from disk but + // * haven't actually been used by anyone so far. + // */ + self.mem_available = self + .mem_total + .saturating_sub( + u64::from(stat.active_count) + .saturating_add(u64::from(stat.inactive_count)) + .saturating_add(u64::from(stat.wire_count)) + .saturating_add(u64::from(stat.speculative_count)) + .saturating_sub(u64::from(stat.purgeable_count)), + ) + .saturating_mul(self.page_size_kb); + self.mem_free = u64::from(stat.free_count).saturating_mul(self.page_size_kb); + } + } + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn refresh_components_list(&mut self) {} + + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + fn refresh_components_list(&mut self) { + if let Some(con) = self.connection { + self.components.clear(); + // getting CPU critical temperature + let critical_temp = crate::apple::component::get_temperature( + con, + &['T' as i8, 'C' as i8, '0' as i8, 'D' as i8, 0], + ); + + for (id, v) in crate::apple::component::COMPONENTS_TEMPERATURE_IDS.iter() { + if let Some(c) = Component::new((*id).to_owned(), None, critical_temp, v, con) { + self.components.push(c); + } + } + } + } + + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { + let cpus = &mut self.cpus; + if cpus.is_empty() { + init_cpus(self.port, cpus, &mut self.global_cpu, refresh_kind); + self.got_cpu_frequency = refresh_kind.frequency(); + return; + } + if refresh_kind.frequency() && !self.got_cpu_frequency { + let frequency = get_cpu_frequency(); + for proc_ in cpus.iter_mut() { + proc_.set_frequency(frequency); + } + self.got_cpu_frequency = true; + } + if refresh_kind.cpu_usage() { + update_cpu_usage(self.port, &mut self.global_cpu, |proc_data, cpu_info| { + let mut percentage = 0f32; + let mut offset = 0; + for proc_ in cpus.iter_mut() { + let cpu_usage = compute_usage_of_cpu(proc_, cpu_info, offset); + proc_.update(cpu_usage, Arc::clone(&proc_data)); + percentage += proc_.cpu_usage(); + + offset += libc::CPU_STATE_MAX as isize; + } + (percentage, cpus.len()) + }); + } + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn refresh_processes_specifics(&mut self, _refresh_kind: ProcessRefreshKind) {} + + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { + use crate::utils::into_iter; + + unsafe { + let count = libc::proc_listallpids(::std::ptr::null_mut(), 0); + if count < 1 { + return; + } + } + if let Some(pids) = get_proc_list() { + let now = get_now(); + let arg_max = get_arg_max(); + let port = self.port; + let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); + let entries: Vec<Process> = { + let wrap = &Wrap(UnsafeCell::new(&mut self.process_list)); + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + into_iter(pids) + .flat_map(|pid| { + match update_process( + wrap, + pid, + arg_max as size_t, + time_interval, + now, + refresh_kind, + ) { + Ok(x) => x, + _ => None, + } + }) + .collect() + }; + entries.into_iter().for_each(|entry| { + self.process_list.insert(entry.pid(), entry); + }); + self.clear_procs(); + } + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn refresh_process_specifics(&mut self, _pid: Pid, _refresh_kind: ProcessRefreshKind) -> bool { + false + } + + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + let now = get_now(); + let arg_max = get_arg_max(); + let port = self.port; + let time_interval = self.clock_info.as_mut().map(|c| c.get_time_interval(port)); + match { + let wrap = Wrap(UnsafeCell::new(&mut self.process_list)); + update_process( + &wrap, + pid, + arg_max as size_t, + time_interval, + now, + refresh_kind, + ) + } { + Ok(Some(p)) => { + self.process_list.insert(p.pid(), p); + true + } + Ok(_) => true, + Err(_) => false, + } + } + + #[cfg(target_os = "ios")] + fn refresh_disks_list(&mut self) {} + + #[cfg(target_os = "macos")] + fn refresh_disks_list(&mut self) { + unsafe { + if self.session.0.is_null() { + self.session.0 = ffi::DASessionCreate(kCFAllocatorDefault as _); + } + self.disks = get_disks(self.session.0); + } + } + + fn refresh_users_list(&mut self) { + self.users = crate::apple::users::get_users_list(); + } + + // COMMON PART + // + // Need to be moved into a "common" file to avoid duplication. + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.get(&pid) + } + + fn global_cpu_info(&self) -> &Cpu { + &self.global_cpu + } + + fn cpus(&self) -> &[Cpu] { + &self.cpus + } + + fn physical_core_count(&self) -> Option<usize> { + let mut physical_core_count = 0; + + unsafe { + if get_sys_value_by_name( + b"hw.physicalcpu\0", + &mut mem::size_of::<u32>(), + &mut physical_core_count as *mut usize as *mut c_void, + ) { + Some(physical_core_count) + } else { + None + } + } + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + self.mem_free + } + + fn available_memory(&self) -> u64 { + self.mem_available + } + + fn used_memory(&self) -> u64 { + self.mem_total - self.mem_free + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_free + } + + // TODO: need to be checked + fn used_swap(&self) -> u64 { + self.swap_total - self.swap_free + } + + fn components(&self) -> &[Component] { + &self.components + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut self.components + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks + } + + fn uptime(&self) -> u64 { + unsafe { + let csec = libc::time(::std::ptr::null_mut()); + + libc::difftime(csec, self.boot_time as _) as u64 + } + } + + fn load_average(&self) -> LoadAvg { + let mut loads = vec![0f64; 3]; + + unsafe { + libc::getloadavg(loads.as_mut_ptr(), 3); + LoadAvg { + one: loads[0], + five: loads[1], + fifteen: loads[2], + } + } + } + + fn users(&self) -> &[User] { + &self.users + } + + fn boot_time(&self) -> u64 { + self.boot_time + } + + fn name(&self) -> Option<String> { + get_system_info(libc::KERN_OSTYPE, Some("Darwin")) + } + + fn long_os_version(&self) -> Option<String> { + #[cfg(target_os = "macos")] + let friendly_name = match self.os_version().unwrap_or_default() { + f_n if f_n.starts_with("10.16") + | f_n.starts_with("11.0") + | f_n.starts_with("11.1") + | f_n.starts_with("11.2") => + { + "Big Sur" + } + f_n if f_n.starts_with("10.15") => "Catalina", + f_n if f_n.starts_with("10.14") => "Mojave", + f_n if f_n.starts_with("10.13") => "High Sierra", + f_n if f_n.starts_with("10.12") => "Sierra", + f_n if f_n.starts_with("10.11") => "El Capitan", + f_n if f_n.starts_with("10.10") => "Yosemite", + f_n if f_n.starts_with("10.9") => "Mavericks", + f_n if f_n.starts_with("10.8") => "Mountain Lion", + f_n if f_n.starts_with("10.7") => "Lion", + f_n if f_n.starts_with("10.6") => "Snow Leopard", + f_n if f_n.starts_with("10.5") => "Leopard", + f_n if f_n.starts_with("10.4") => "Tiger", + f_n if f_n.starts_with("10.3") => "Panther", + f_n if f_n.starts_with("10.2") => "Jaguar", + f_n if f_n.starts_with("10.1") => "Puma", + f_n if f_n.starts_with("10.0") => "Cheetah", + _ => "", + }; + + #[cfg(target_os = "macos")] + let long_name = Some(format!( + "MacOS {} {}", + self.os_version().unwrap_or_default(), + friendly_name + )); + + #[cfg(target_os = "ios")] + let long_name = Some(format!("iOS {}", self.os_version().unwrap_or_default())); + + long_name + } + + fn host_name(&self) -> Option<String> { + get_system_info(libc::KERN_HOSTNAME, None) + } + + fn kernel_version(&self) -> Option<String> { + get_system_info(libc::KERN_OSRELEASE, None) + } + + fn os_version(&self) -> Option<String> { + unsafe { + // get the size for the buffer first + let mut size = 0; + if get_sys_value_by_name(b"kern.osproductversion\0", &mut size, std::ptr::null_mut()) + && size > 0 + { + // now create a buffer with the size and get the real value + let mut buf = vec![0_u8; size as usize]; + + if get_sys_value_by_name( + b"kern.osproductversion\0", + &mut size, + buf.as_mut_ptr() as *mut c_void, + ) { + if let Some(pos) = buf.iter().position(|x| *x == 0) { + // Shrink buffer to terminate the null bytes + buf.resize(pos, 0); + } + + String::from_utf8(buf).ok() + } else { + // getting the system value failed + None + } + } else { + // getting the system value failed, or did not return a buffer size + None + } + } + } +} + +impl Default for System { + fn default() -> System { + System::new() + } +} + +// code from https://github.com/Chris911/iStats +// Not supported on iOS, or in the default macOS +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +fn get_io_service_connection() -> Option<ffi::io_connect_t> { + let mut master_port: mach_port_t = 0; + let mut iterator: ffi::io_iterator_t = 0; + + unsafe { + ffi::IOMasterPort(libc::MACH_PORT_NULL, &mut master_port); + + let matching_dictionary = ffi::IOServiceMatching(b"AppleSMC\0".as_ptr() as *const i8); + let result = + ffi::IOServiceGetMatchingServices(master_port, matching_dictionary, &mut iterator); + if result != ffi::KIO_RETURN_SUCCESS { + sysinfo_debug!("Error: IOServiceGetMatchingServices() = {}", result); + return None; + } + + let device = ffi::IOIteratorNext(iterator); + ffi::IOObjectRelease(iterator); + if device == 0 { + sysinfo_debug!("Error: no SMC found"); + return None; + } + + let mut conn = 0; + let result = ffi::IOServiceOpen(device, libc::mach_task_self(), 0, &mut conn); + ffi::IOObjectRelease(device); + if result != ffi::KIO_RETURN_SUCCESS { + sysinfo_debug!("Error: IOServiceOpen() = {}", result); + return None; + } + + Some(conn) + } +} + +#[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] +fn get_arg_max() -> usize { + let mut mib = [libc::CTL_KERN, libc::KERN_ARGMAX]; + let mut arg_max = 0i32; + let mut size = mem::size_of::<c_int>(); + unsafe { + if sysctl( + mib.as_mut_ptr(), + mib.len() as _, + (&mut arg_max) as *mut i32 as *mut c_void, + &mut size, + std::ptr::null_mut(), + 0, + ) == -1 + { + 4096 // We default to this value + } else { + arg_max as usize + } + } +} + +pub(crate) unsafe fn get_sys_value( + high: u32, + low: u32, + mut len: usize, + value: *mut c_void, + mib: &mut [i32; 2], +) -> bool { + mib[0] = high as i32; + mib[1] = low as i32; + sysctl( + mib.as_mut_ptr(), + mib.len() as _, + value, + &mut len as *mut usize, + std::ptr::null_mut(), + 0, + ) == 0 +} + +unsafe fn get_sys_value_by_name(name: &[u8], len: &mut usize, value: *mut c_void) -> bool { + sysctlbyname( + name.as_ptr() as *const c_char, + value, + len, + std::ptr::null_mut(), + 0, + ) == 0 +} + +fn get_system_info(value: c_int, default: Option<&str>) -> Option<String> { + let mut mib: [c_int; 2] = [libc::CTL_KERN, value]; + let mut size = 0; + + unsafe { + // Call first to get size + sysctl( + mib.as_mut_ptr(), + mib.len() as _, + std::ptr::null_mut(), + &mut size, + std::ptr::null_mut(), + 0, + ); + + // exit early if we did not update the size + if size == 0 { + default.map(|s| s.to_owned()) + } else { + // set the buffer to the correct size + let mut buf = vec![0_u8; size as usize]; + + if sysctl( + mib.as_mut_ptr(), + mib.len() as _, + buf.as_mut_ptr() as _, + &mut size, + std::ptr::null_mut(), + 0, + ) == -1 + { + // If command fails return default + default.map(|s| s.to_owned()) + } else { + if let Some(pos) = buf.iter().position(|x| *x == 0) { + // Shrink buffer to terminate the null bytes + buf.resize(pos, 0); + } + + String::from_utf8(buf).ok() + } + } + } +} diff --git a/vendor/sysinfo/src/apple/users.rs b/vendor/sysinfo/src/apple/users.rs new file mode 100644 index 000000000..690aceee1 --- /dev/null +++ b/vendor/sysinfo/src/apple/users.rs @@ -0,0 +1,178 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + common::{Gid, Uid}, + User, +}; + +use crate::sys::utils; +use libc::{c_char, endpwent, getgrgid, getgrouplist, getpwent, gid_t, setpwent, strlen}; + +fn get_user_groups(name: *const c_char, group_id: gid_t) -> Vec<String> { + let mut add = 0; + + loop { + let mut nb_groups = 256 + add; + let mut groups = Vec::with_capacity(nb_groups as _); + unsafe { + if getgrouplist(name, group_id as _, groups.as_mut_ptr(), &mut nb_groups) == -1 { + add += 100; + continue; + } + groups.set_len(nb_groups as _); + return groups + .into_iter() + .filter_map(|g| { + let group = getgrgid(g as _); + if group.is_null() { + return None; + } + utils::cstr_to_rust((*group).gr_name) + }) + .collect(); + } + } +} + +fn endswith(s1: *const c_char, s2: &[u8]) -> bool { + if s1.is_null() { + return false; + } + unsafe { + let mut len = strlen(s1) as isize - 1; + let mut i = s2.len() as isize - 1; + while len >= 0 && i >= 0 && *s1.offset(len) == s2[i as usize] as _ { + i -= 1; + len -= 1; + } + i == -1 + } +} + +fn users_list<F>(filter: F) -> Vec<User> +where + F: Fn(*const c_char, u32) -> bool, +{ + let mut users = Vec::new(); + + unsafe { + setpwent(); + loop { + let pw = getpwent(); + if pw.is_null() { + break; + } + + if !filter((*pw).pw_shell, (*pw).pw_uid) { + // This is not a "real" or "local" user. + continue; + } + + let groups = get_user_groups((*pw).pw_name, (*pw).pw_gid); + let uid = (*pw).pw_uid; + let gid = (*pw).pw_gid; + if let Some(name) = utils::cstr_to_rust((*pw).pw_name) { + users.push(User { + uid: Uid(uid), + gid: Gid(gid), + name, + groups, + }); + } + } + endpwent(); + } + users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap()); + users.dedup_by(|a, b| a.name == b.name); + users +} + +pub(crate) fn get_users_list() -> Vec<User> { + users_list(|shell, uid| { + !endswith(shell, b"/false") && !endswith(shell, b"/uucico") && uid < 65536 + }) +} + +// This was the OSX-based solution. It provides enough information, but what a mess! +// pub fn get_users_list() -> Vec<User> { +// let mut users = Vec::new(); +// let node_name = b"/Local/Default\0"; + +// unsafe { +// let node_name = ffi::CFStringCreateWithCStringNoCopy( +// std::ptr::null_mut(), +// node_name.as_ptr() as *const c_char, +// ffi::kCFStringEncodingMacRoman, +// ffi::kCFAllocatorNull as *mut c_void, +// ); +// let node_ref = ffi::ODNodeCreateWithName( +// ffi::kCFAllocatorDefault, +// ffi::kODSessionDefault, +// node_name, +// std::ptr::null_mut(), +// ); +// let query = ffi::ODQueryCreateWithNode( +// ffi::kCFAllocatorDefault, +// node_ref, +// ffi::kODRecordTypeUsers as _, // kODRecordTypeGroups +// std::ptr::null(), +// 0, +// std::ptr::null(), +// std::ptr::null(), +// 0, +// std::ptr::null_mut(), +// ); +// if query.is_null() { +// return users; +// } +// let results = ffi::ODQueryCopyResults( +// query, +// false as _, +// std::ptr::null_mut(), +// ); +// let len = ffi::CFArrayGetCount(results); +// for i in 0..len { +// let name = match get_user_name(ffi::CFArrayGetValueAtIndex(results, i)) { +// Some(n) => n, +// None => continue, +// }; +// let groups = get_user_groups(&name); +// users.push(User { name }); +// } + +// ffi::CFRelease(results as *const c_void); +// ffi::CFRelease(query as *const c_void); +// ffi::CFRelease(node_ref as *const c_void); +// ffi::CFRelease(node_name as *const c_void); +// } +// users.sort_unstable_by(|x, y| x.name.partial_cmp(&y.name).unwrap()); +// return users; +// } + +// fn get_user_name(result: *const c_void) -> Option<String> { +// let user_name = ffi::ODRecordGetRecordName(result as _); +// let ptr = ffi::CFStringGetCharactersPtr(user_name); +// String::from_utf16(&if ptr.is_null() { +// let len = ffi::CFStringGetLength(user_name); // It returns the len in UTF-16 code pairs. +// if len == 0 { +// continue; +// } +// let mut v = Vec::with_capacity(len as _); +// for x in 0..len { +// v.push(ffi::CFStringGetCharacterAtIndex(user_name, x)); +// } +// v +// } else { +// let mut v: Vec<u16> = Vec::new(); +// let mut x = 0; +// loop { +// let letter = *ptr.offset(x); +// if letter == 0 { +// break; +// } +// v.push(letter); +// x += 1; +// } +// v +// }.ok() +// } diff --git a/vendor/sysinfo/src/apple/utils.rs b/vendor/sysinfo/src/apple/utils.rs new file mode 100644 index 000000000..019295b95 --- /dev/null +++ b/vendor/sysinfo/src/apple/utils.rs @@ -0,0 +1,39 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use libc::c_char; + +pub(crate) fn cstr_to_rust(c: *const c_char) -> Option<String> { + cstr_to_rust_with_size(c, None) +} + +pub(crate) fn cstr_to_rust_with_size(c: *const c_char, size: Option<usize>) -> Option<String> { + if c.is_null() { + return None; + } + let mut s = match size { + Some(len) => Vec::with_capacity(len), + None => Vec::new(), + }; + let mut i = 0; + unsafe { + loop { + let value = *c.offset(i) as u8; + if value == 0 { + break; + } + s.push(value); + i += 1; + } + String::from_utf8(s).ok() + } +} + +#[cfg(target_os = "macos")] +pub(crate) fn vec_to_rust(buf: Vec<i8>) -> Option<String> { + String::from_utf8( + buf.into_iter() + .flat_map(|b| if b > 0 { Some(b as u8) } else { None }) + .collect(), + ) + .ok() +} diff --git a/vendor/sysinfo/src/c_interface.rs b/vendor/sysinfo/src/c_interface.rs new file mode 100644 index 000000000..e7cc5359b --- /dev/null +++ b/vendor/sysinfo/src/c_interface.rs @@ -0,0 +1,468 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{CpuExt, NetworkExt, NetworksExt, Pid, Process, ProcessExt, System, SystemExt}; +use libc::{self, c_char, c_float, c_uint, c_void, pid_t, size_t}; +use std::borrow::BorrowMut; +use std::ffi::CString; + +/// Equivalent of [`System`][crate::System] struct. +pub type CSystem = *mut c_void; +/// Equivalent of [`Process`][crate::Process] struct. +pub type CProcess = *const c_void; +/// C string returned from `CString::into_raw`. +pub type RString = *const c_char; +/// Callback used by [`processes`][crate::System#method.processes]. +pub type ProcessLoop = extern "C" fn(pid: pid_t, process: CProcess, data: *mut c_void) -> bool; + +/// Equivalent of [`System::new()`][crate::System#method.new]. +#[no_mangle] +pub extern "C" fn sysinfo_init() -> CSystem { + let system = Box::new(System::new()); + Box::into_raw(system) as CSystem +} + +/// Equivalent of `System::drop()`. Important in C to cleanup memory. +#[no_mangle] +pub extern "C" fn sysinfo_destroy(system: CSystem) { + assert!(!system.is_null()); + unsafe { + Box::from_raw(system as *mut System); + } +} + +/// Equivalent of [`System::refresh_system()`][crate::System#method.refresh_system]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_system(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_system(); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::refresh_memory()`][crate::System#method.refresh_memory]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_memory(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_memory(); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::refresh_cpu()`][crate::System#method.refresh_cpu]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_cpu(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_cpu(); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::refresh_components()`][crate::System#method.refresh_temperatures]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_components(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_components(); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::refresh_all()`][crate::System#method.refresh_all]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_all(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_all(); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::refresh_processes()`][crate::System#method.refresh_processes]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_processes(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_processes(); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::refresh_process()`][crate::System#method.refresh_process]. +#[cfg(target_os = "linux")] +#[no_mangle] +pub extern "C" fn sysinfo_refresh_process(system: CSystem, pid: pid_t) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_process(Pid(pid)); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::refresh_disks()`][crate::System#method.refresh_disks]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_disks(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_disks(); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::refresh_disks_list()`][crate::System#method.refresh_disks_list]. +#[no_mangle] +pub extern "C" fn sysinfo_refresh_disks_list(system: CSystem) { + assert!(!system.is_null()); + unsafe { + let mut system: Box<System> = Box::from_raw(system as *mut System); + { + let system: &mut System = system.borrow_mut(); + system.refresh_disks_list(); + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::total_memory()`][crate::System#method.total_memory]. +#[no_mangle] +pub extern "C" fn sysinfo_total_memory(system: CSystem) -> size_t { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let ret = system.total_memory() as size_t; + Box::into_raw(system); + ret + } +} + +/// Equivalent of [`System::free_memory()`][crate::System#method.free_memory]. +#[no_mangle] +pub extern "C" fn sysinfo_free_memory(system: CSystem) -> size_t { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let ret = system.free_memory() as size_t; + Box::into_raw(system); + ret + } +} + +/// Equivalent of [`System::used_memory()`][crate::System#method.used_memory]. +#[no_mangle] +pub extern "C" fn sysinfo_used_memory(system: CSystem) -> size_t { + assert!(!system.is_null()); + let system: Box<System> = unsafe { Box::from_raw(system as *mut System) }; + let ret = system.used_memory() as size_t; + Box::into_raw(system); + ret +} + +/// Equivalent of [`System::total_swap()`][crate::System#method.total_swap]. +#[no_mangle] +pub extern "C" fn sysinfo_total_swap(system: CSystem) -> size_t { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let ret = system.total_swap() as size_t; + Box::into_raw(system); + ret + } +} + +/// Equivalent of [`System::free_swap()`][crate::System#method.free_swap]. +#[no_mangle] +pub extern "C" fn sysinfo_free_swap(system: CSystem) -> size_t { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let ret = system.free_swap() as size_t; + Box::into_raw(system); + ret + } +} + +/// Equivalent of [`System::used_swap()`][crate::System#method.used_swap]. +#[no_mangle] +pub extern "C" fn sysinfo_used_swap(system: CSystem) -> size_t { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let ret = system.used_swap() as size_t; + Box::into_raw(system); + ret + } +} + +/// Equivalent of +/// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.received() as size_t)`. +#[no_mangle] +pub extern "C" fn sysinfo_networks_received(system: CSystem) -> size_t { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let ret = system.networks().iter().fold(0, |acc: size_t, (_, data)| { + acc.saturating_add(data.received() as size_t) + }); + Box::into_raw(system); + ret + } +} + +/// Equivalent of +/// `system::networks().iter().fold(0, |acc, (_, data)| acc + data.transmitted() as size_t)`. +#[no_mangle] +pub extern "C" fn sysinfo_networks_transmitted(system: CSystem) -> size_t { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let ret = system.networks().iter().fold(0, |acc: size_t, (_, data)| { + acc.saturating_add(data.transmitted() as size_t) + }); + Box::into_raw(system); + ret + } +} + +/// Equivalent of [`System::cpus_usage()`][crate::System#method.cpus_usage]. +/// +/// * `length` will contain the number of cpu usage added into `procs`. +/// * `procs` will be allocated if it's null and will contain of cpu usage. +#[no_mangle] +pub extern "C" fn sysinfo_cpus_usage( + system: CSystem, + length: *mut c_uint, + procs: *mut *mut c_float, +) { + assert!(!system.is_null()); + if procs.is_null() || length.is_null() { + return; + } + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + { + let cpus = system.cpus(); + if (*procs).is_null() { + (*procs) = + libc::malloc(::std::mem::size_of::<c_float>() * cpus.len()) as *mut c_float; + } + for (pos, cpu) in cpus.iter().skip(1).enumerate() { + (*(*procs).offset(pos as isize)) = cpu.cpu_usage(); + } + *length = cpus.len() as c_uint - 1; + } + Box::into_raw(system); + } +} + +/// Equivalent of [`System::processes()`][crate::System#method.processes]. Returns an +/// array ended by a null pointer. Must be freed. +/// +/// # /!\ WARNING /!\ +/// +/// While having this method returned processes, you should *never* call any refresh method! +#[no_mangle] +pub extern "C" fn sysinfo_processes( + system: CSystem, + fn_pointer: Option<ProcessLoop>, + data: *mut c_void, +) -> size_t { + assert!(!system.is_null()); + if let Some(fn_pointer) = fn_pointer { + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let len = { + let entries = system.processes(); + for (pid, process) in entries { + if !fn_pointer(pid.0, process as *const Process as CProcess, data) { + break; + } + } + entries.len() as size_t + }; + Box::into_raw(system); + len + } + } else { + 0 + } +} + +/// Equivalent of [`System::process()`][crate::System#method.process]. +/// +/// # /!\ WARNING /!\ +/// +/// While having this method returned process, you should *never* call any +/// refresh method! +#[no_mangle] +pub extern "C" fn sysinfo_process_by_pid(system: CSystem, pid: pid_t) -> CProcess { + assert!(!system.is_null()); + unsafe { + let system: Box<System> = Box::from_raw(system as *mut System); + let ret = if let Some(process) = system.process(Pid(pid)) { + process as *const Process as CProcess + } else { + std::ptr::null() + }; + Box::into_raw(system); + ret + } +} + +/// Equivalent of iterating over [`Process::tasks()`][crate::Process#method.tasks]. +/// +/// # /!\ WARNING /!\ +/// +/// While having this method processes, you should *never* call any refresh method! +#[cfg(target_os = "linux")] +#[no_mangle] +pub extern "C" fn sysinfo_process_tasks( + process: CProcess, + fn_pointer: Option<ProcessLoop>, + data: *mut c_void, +) -> size_t { + assert!(!process.is_null()); + if let Some(fn_pointer) = fn_pointer { + unsafe { + let process = process as *const Process; + for (pid, process) in (*process).tasks.iter() { + if !fn_pointer(pid.0, process as *const Process as CProcess, data) { + break; + } + } + (*process).tasks.len() as size_t + } + } else { + 0 + } +} + +/// Equivalent of [`Process::pid()`][crate::Process#method.pid]. +#[no_mangle] +pub extern "C" fn sysinfo_process_pid(process: CProcess) -> pid_t { + assert!(!process.is_null()); + let process = process as *const Process; + unsafe { (*process).pid().0 } +} + +/// Equivalent of [`Process::parent()`][crate::Process#method.parent]. +/// +/// In case there is no known parent, it returns `0`. +#[no_mangle] +pub extern "C" fn sysinfo_process_parent_pid(process: CProcess) -> pid_t { + assert!(!process.is_null()); + let process = process as *const Process; + unsafe { (*process).parent().unwrap_or(Pid(0)).0 } +} + +/// Equivalent of [`Process::cpu_usage()`][crate::Process#method.cpu_usage]. +#[no_mangle] +pub extern "C" fn sysinfo_process_cpu_usage(process: CProcess) -> c_float { + assert!(!process.is_null()); + let process = process as *const Process; + unsafe { (*process).cpu_usage() } +} + +/// Equivalent of [`Process::memory()`][crate::Process#method.memory]. +#[no_mangle] +pub extern "C" fn sysinfo_process_memory(process: CProcess) -> size_t { + assert!(!process.is_null()); + let process = process as *const Process; + unsafe { (*process).memory() as usize } +} + +/// Equivalent of [`Process::virtual_memory()`][crate::Process#method.virtual_memory]. +#[no_mangle] +pub extern "C" fn sysinfo_process_virtual_memory(process: CProcess) -> size_t { + assert!(!process.is_null()); + let process = process as *const Process; + unsafe { (*process).virtual_memory() as usize } +} + +/// Equivalent of [`Process::exe()`][crate::Process#method.exe]. +#[no_mangle] +pub extern "C" fn sysinfo_process_executable_path(process: CProcess) -> RString { + assert!(!process.is_null()); + let process = process as *const Process; + unsafe { + if let Some(p) = (*process).exe().to_str() { + if let Ok(c) = CString::new(p) { + return c.into_raw() as _; + } + } + std::ptr::null() + } +} + +/// Equivalent of [`Process::root()`][crate::Process#method.root]. +#[no_mangle] +pub extern "C" fn sysinfo_process_root_directory(process: CProcess) -> RString { + assert!(!process.is_null()); + let process = process as *const Process; + unsafe { + if let Some(p) = (*process).root().to_str() { + if let Ok(c) = CString::new(p) { + return c.into_raw() as _; + } + } + std::ptr::null() + } +} + +/// Equivalent of [`Process::cwd()`][crate::Process#method.cwd]. +#[no_mangle] +pub extern "C" fn sysinfo_process_current_directory(process: CProcess) -> RString { + assert!(!process.is_null()); + let process = process as *const Process; + unsafe { + if let Some(p) = (*process).cwd().to_str() { + if let Ok(c) = CString::new(p) { + return c.into_raw() as _; + } + } + std::ptr::null() + } +} + +/// Frees a C string created with `CString::into_raw()`. +#[no_mangle] +pub extern "C" fn sysinfo_rstring_free(s: RString) { + if !s.is_null() { + unsafe { + let _ = CString::from_raw(s as usize as *mut i8); + } + } +} diff --git a/vendor/sysinfo/src/common.rs b/vendor/sysinfo/src/common.rs new file mode 100644 index 000000000..15cbe87be --- /dev/null +++ b/vendor/sysinfo/src/common.rs @@ -0,0 +1,965 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{NetworkData, Networks, NetworksExt, UserExt}; + +use std::convert::From; +use std::fmt; +use std::str::FromStr; + +/// Trait to have a common conversions for the [`Pid`][crate::Pid] type. +/// +/// ``` +/// use sysinfo::{Pid, PidExt}; +/// +/// let p = Pid::from_u32(0); +/// let value: u32 = p.as_u32(); +/// ``` +pub trait PidExt<T>: Copy + From<T> + FromStr + fmt::Display { + /// Allows to convert [`Pid`][crate::Pid] into [`u32`]. + /// + /// ``` + /// use sysinfo::{Pid, PidExt}; + /// + /// let p = Pid::from_u32(0); + /// let value: u32 = p.as_u32(); + /// ``` + fn as_u32(self) -> u32; + /// Allows to convert a [`u32`] into [`Pid`][crate::Pid]. + /// + /// ``` + /// use sysinfo::{Pid, PidExt}; + /// + /// let p = Pid::from_u32(0); + /// ``` + fn from_u32(v: u32) -> Self; +} + +macro_rules! pid_decl { + ($typ:ty) => { + #[doc = include_str!("../md_doc/pid.md")] + #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] + #[repr(transparent)] + pub struct Pid(pub(crate) $typ); + + impl From<$typ> for Pid { + fn from(v: $typ) -> Self { + Self(v) + } + } + impl From<Pid> for $typ { + fn from(v: Pid) -> Self { + v.0 + } + } + impl PidExt<$typ> for Pid { + fn as_u32(self) -> u32 { + self.0 as u32 + } + fn from_u32(v: u32) -> Self { + Self(v as _) + } + } + impl FromStr for Pid { + type Err = <$typ as FromStr>::Err; + fn from_str(s: &str) -> Result<Self, Self::Err> { + Ok(Self(<$typ>::from_str(s)?)) + } + } + impl fmt::Display for Pid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } + } + }; +} + +cfg_if::cfg_if! { + if #[cfg(all( + not(feature = "unknown-ci"), + any( + target_os = "freebsd", + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "ios", + ) + ))] { + use libc::pid_t; + + pid_decl!(pid_t); + } else { + pid_decl!(usize); + } +} + +macro_rules! impl_get_set { + ($ty_name:ident, $name:ident, $with:ident, $without:ident $(, $extra_doc:literal)? $(,)?) => { + #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind.")] + $(#[doc = concat!(" +", $extra_doc, " +")])? + #[doc = concat!(" +``` +use sysinfo::", stringify!($ty_name), "; + +let r = ", stringify!($ty_name), "::new(); +assert_eq!(r.", stringify!($name), "(), false); + +let r = r.with_", stringify!($name), "(); +assert_eq!(r.", stringify!($name), "(), true); + +let r = r.without_", stringify!($name), "(); +assert_eq!(r.", stringify!($name), "(), false); +```")] + pub fn $name(&self) -> bool { + self.$name + } + + #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `true`. + +``` +use sysinfo::", stringify!($ty_name), "; + +let r = ", stringify!($ty_name), "::new(); +assert_eq!(r.", stringify!($name), "(), false); + +let r = r.with_", stringify!($name), "(); +assert_eq!(r.", stringify!($name), "(), true); +```")] + #[must_use] + pub fn $with(mut self) -> Self { + self.$name = true; + self + } + + #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `false`. + +``` +use sysinfo::", stringify!($ty_name), "; + +let r = ", stringify!($ty_name), "::everything(); +assert_eq!(r.", stringify!($name), "(), true); + +let r = r.without_", stringify!($name), "(); +assert_eq!(r.", stringify!($name), "(), false); +```")] + #[must_use] + pub fn $without(mut self) -> Self { + self.$name = false; + self + } + }; + + ($ty_name:ident, $name:ident, $with:ident, $without:ident, $typ:ty $(,)?) => { + #[doc = concat!("Returns the value of the \"", stringify!($name), "\" refresh kind. + +``` +use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "}; + +let r = ", stringify!($ty_name), "::new(); +assert_eq!(r.", stringify!($name), "().is_some(), false); + +let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything()); +assert_eq!(r.", stringify!($name), "().is_some(), true); + +let r = r.without_", stringify!($name), "(); +assert_eq!(r.", stringify!($name), "().is_some(), false); +```")] + pub fn $name(&self) -> Option<$typ> { + self.$name + } + + #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `true`. + +``` +use sysinfo::{", stringify!($ty_name), ", ", stringify!($typ), "}; + +let r = ", stringify!($ty_name), "::new(); +assert_eq!(r.", stringify!($name), "().is_some(), false); + +let r = r.with_", stringify!($name), "(", stringify!($typ), "::everything()); +assert_eq!(r.", stringify!($name), "().is_some(), true); +```")] + #[must_use] + pub fn $with(mut self, kind: $typ) -> Self { + self.$name = Some(kind); + self + } + + #[doc = concat!("Sets the value of the \"", stringify!($name), "\" refresh kind to `false`. + +``` +use sysinfo::", stringify!($ty_name), "; + +let r = ", stringify!($ty_name), "::everything(); +assert_eq!(r.", stringify!($name), "().is_some(), true); + +let r = r.without_", stringify!($name), "(); +assert_eq!(r.", stringify!($name), "().is_some(), false); +```")] + #[must_use] + pub fn $without(mut self) -> Self { + self.$name = None; + self + } + }; +} + +/// Used to determine what you want to refresh specifically on the [`Process`] type. +/// +/// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that +/// the information won't be retrieved if the information is accessible without needing +/// extra computation. +/// +/// ``` +/// use sysinfo::{ProcessExt, ProcessRefreshKind, System, SystemExt}; +/// +/// let mut system = System::new(); +/// +/// // We don't want to update the CPU information. +/// system.refresh_processes_specifics(ProcessRefreshKind::everything().without_cpu()); +/// +/// for (_, proc_) in system.processes() { +/// // We use a `==` comparison on float only because we know it's set to 0 here. +/// assert_eq!(proc_.cpu_usage(), 0.); +/// } +/// ``` +/// +/// [`Process`]: crate::Process +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct ProcessRefreshKind { + cpu: bool, + disk_usage: bool, + user: bool, +} + +impl ProcessRefreshKind { + /// Creates a new `ProcessRefreshKind` with every refresh set to `false`. + /// + /// ``` + /// use sysinfo::ProcessRefreshKind; + /// + /// let r = ProcessRefreshKind::new(); + /// + /// assert_eq!(r.cpu(), false); + /// assert_eq!(r.disk_usage(), false); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Creates a new `ProcessRefreshKind` with every refresh set to `true`. + /// + /// ``` + /// use sysinfo::ProcessRefreshKind; + /// + /// let r = ProcessRefreshKind::everything(); + /// + /// assert_eq!(r.cpu(), true); + /// assert_eq!(r.disk_usage(), true); + /// ``` + pub fn everything() -> Self { + Self { + cpu: true, + disk_usage: true, + user: true, + } + } + + impl_get_set!(ProcessRefreshKind, cpu, with_cpu, without_cpu); + impl_get_set!( + ProcessRefreshKind, + disk_usage, + with_disk_usage, + without_disk_usage + ); + impl_get_set!( + ProcessRefreshKind, + user, + with_user, + without_user, + r#"This refresh is about `user_id` and `group_id`. Please note that it has an effect mostly +on Windows as other platforms get this information alongside the Process information directly."#, + ); +} + +/// Used to determine what you want to refresh specifically on the [`Process`] type. +/// +/// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that +/// the information won't be retrieved if the information is accessible without needing +/// extra computation. +/// +/// ``` +/// use sysinfo::{CpuExt, CpuRefreshKind, System, SystemExt}; +/// +/// let mut system = System::new(); +/// +/// // We don't want to update all the CPU information. +/// system.refresh_cpu_specifics(CpuRefreshKind::everything().without_frequency()); +/// +/// for cpu in system.cpus() { +/// assert_eq!(cpu.frequency(), 0); +/// } +/// ``` +/// +/// [`Process`]: crate::Process +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct CpuRefreshKind { + cpu_usage: bool, + frequency: bool, +} + +impl CpuRefreshKind { + /// Creates a new `CpuRefreshKind` with every refresh set to `false`. + /// + /// ``` + /// use sysinfo::CpuRefreshKind; + /// + /// let r = CpuRefreshKind::new(); + /// + /// assert_eq!(r.frequency(), false); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Creates a new `CpuRefreshKind` with every refresh set to `true`. + /// + /// ``` + /// use sysinfo::CpuRefreshKind; + /// + /// let r = CpuRefreshKind::everything(); + /// + /// assert_eq!(r.frequency(), true); + /// ``` + pub fn everything() -> Self { + Self { + cpu_usage: true, + frequency: true, + } + } + + impl_get_set!(CpuRefreshKind, cpu_usage, with_cpu_usage, without_cpu_usage); + impl_get_set!(CpuRefreshKind, frequency, with_frequency, without_frequency); +} + +/// Used to determine what you want to refresh specifically on the [`System`] type. +/// +/// ⚠️ Just like all other refresh types, ruling out a refresh doesn't assure you that +/// the information won't be retrieved if the information is accessible without needing +/// extra computation. +/// +/// ``` +/// use sysinfo::{RefreshKind, System, SystemExt}; +/// +/// // We want everything except disks. +/// let mut system = System::new_with_specifics(RefreshKind::everything().without_disks_list()); +/// +/// assert_eq!(system.disks().len(), 0); +/// # if System::IS_SUPPORTED && !cfg!(feature = "apple-sandbox") { +/// assert!(system.processes().len() > 0); +/// # } +/// ``` +/// +/// [`System`]: crate::System +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct RefreshKind { + networks: bool, + networks_list: bool, + processes: Option<ProcessRefreshKind>, + disks_list: bool, + disks: bool, + memory: bool, + cpu: Option<CpuRefreshKind>, + components: bool, + components_list: bool, + users_list: bool, +} + +impl RefreshKind { + /// Creates a new `RefreshKind` with every refresh set to `false`/`None`. + /// + /// ``` + /// use sysinfo::RefreshKind; + /// + /// let r = RefreshKind::new(); + /// + /// assert_eq!(r.networks(), false); + /// assert_eq!(r.networks_list(), false); + /// assert_eq!(r.processes().is_some(), false); + /// assert_eq!(r.disks_list(), false); + /// assert_eq!(r.disks(), false); + /// assert_eq!(r.memory(), false); + /// assert_eq!(r.cpu().is_some(), false); + /// assert_eq!(r.components(), false); + /// assert_eq!(r.components_list(), false); + /// assert_eq!(r.users_list(), false); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Creates a new `RefreshKind` with every refresh set to `true`/`Some(...)`. + /// + /// ``` + /// use sysinfo::RefreshKind; + /// + /// let r = RefreshKind::everything(); + /// + /// assert_eq!(r.networks(), true); + /// assert_eq!(r.networks_list(), true); + /// assert_eq!(r.processes().is_some(), true); + /// assert_eq!(r.disks_list(), true); + /// assert_eq!(r.disks(), true); + /// assert_eq!(r.memory(), true); + /// assert_eq!(r.cpu().is_some(), true); + /// assert_eq!(r.components(), true); + /// assert_eq!(r.components_list(), true); + /// assert_eq!(r.users_list(), true); + /// ``` + pub fn everything() -> Self { + Self { + networks: true, + networks_list: true, + processes: Some(ProcessRefreshKind::everything()), + disks: true, + disks_list: true, + memory: true, + cpu: Some(CpuRefreshKind::everything()), + components: true, + components_list: true, + users_list: true, + } + } + + impl_get_set!( + RefreshKind, + processes, + with_processes, + without_processes, + ProcessRefreshKind + ); + impl_get_set!(RefreshKind, networks, with_networks, without_networks); + impl_get_set!( + RefreshKind, + networks_list, + with_networks_list, + without_networks_list + ); + impl_get_set!(RefreshKind, disks, with_disks, without_disks); + impl_get_set!(RefreshKind, disks_list, with_disks_list, without_disks_list); + impl_get_set!(RefreshKind, memory, with_memory, without_memory); + impl_get_set!(RefreshKind, cpu, with_cpu, without_cpu, CpuRefreshKind); + impl_get_set!(RefreshKind, components, with_components, without_components); + impl_get_set!( + RefreshKind, + components_list, + with_components_list, + without_components_list + ); + impl_get_set!(RefreshKind, users_list, with_users_list, without_users_list); +} + +/// Iterator over network interfaces. +/// +/// It is returned by [`Networks::iter`][crate::Networks#method.iter]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt, NetworksExt}; +/// +/// let system = System::new_all(); +/// let networks_iter = system.networks().iter(); +/// ``` +pub struct NetworksIter<'a> { + inner: std::collections::hash_map::Iter<'a, String, NetworkData>, +} + +impl<'a> NetworksIter<'a> { + pub(crate) fn new(v: std::collections::hash_map::Iter<'a, String, NetworkData>) -> Self { + NetworksIter { inner: v } + } +} + +impl<'a> Iterator for NetworksIter<'a> { + type Item = (&'a String, &'a NetworkData); + + fn next(&mut self) -> Option<Self::Item> { + self.inner.next() + } +} + +impl<'a> IntoIterator for &'a Networks { + type Item = (&'a String, &'a NetworkData); + type IntoIter = NetworksIter<'a>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// Enum containing the different supported disks types. +/// +/// This type is returned by [`Disk::get_type`][crate::Disk#method.type]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt, DiskExt}; +/// +/// let system = System::new_all(); +/// for disk in system.disks() { +/// println!("{:?}: {:?}", disk.name(), disk.type_()); +/// } +/// ``` +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum DiskType { + /// HDD type. + HDD, + /// SSD type. + SSD, + /// Unknown type. + Unknown(isize), +} + +/// An enum representing signals on UNIX-like systems. +/// +/// On non-unix systems, this enum is mostly useless and is only there to keep coherency between +/// the different OSes. +/// +/// If you want the list of the supported signals on the current system, use +/// [`SystemExt::SUPPORTED_SIGNALS`][crate::SystemExt::SUPPORTED_SIGNALS]. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Debug)] +pub enum Signal { + /// Hangup detected on controlling terminal or death of controlling process. + Hangup, + /// Interrupt from keyboard. + Interrupt, + /// Quit from keyboard. + Quit, + /// Illegal instruction. + Illegal, + /// Trace/breakpoint trap. + Trap, + /// Abort signal from C abort function. + Abort, + /// IOT trap. A synonym for SIGABRT. + IOT, + /// Bus error (bad memory access). + Bus, + /// Floating point exception. + FloatingPointException, + /// Kill signal. + Kill, + /// User-defined signal 1. + User1, + /// Invalid memory reference. + Segv, + /// User-defined signal 2. + User2, + /// Broken pipe: write to pipe with no readers. + Pipe, + /// Timer signal from C alarm function. + Alarm, + /// Termination signal. + Term, + /// Child stopped or terminated. + Child, + /// Continue if stopped. + Continue, + /// Stop process. + Stop, + /// Stop typed at terminal. + TSTP, + /// Terminal input for background process. + TTIN, + /// Terminal output for background process. + TTOU, + /// Urgent condition on socket. + Urgent, + /// CPU time limit exceeded. + XCPU, + /// File size limit exceeded. + XFSZ, + /// Virtual alarm clock. + VirtualAlarm, + /// Profiling time expired. + Profiling, + /// Windows resize signal. + Winch, + /// I/O now possible. + IO, + /// Pollable event (Sys V). Synonym for IO + Poll, + /// Power failure (System V). + /// + /// Doesn't exist on apple systems so will be ignored. + Power, + /// Bad argument to routine (SVr4). + Sys, +} + +impl std::fmt::Display for Signal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match *self { + Self::Hangup => "Hangup", + Self::Interrupt => "Interrupt", + Self::Quit => "Quit", + Self::Illegal => "Illegal", + Self::Trap => "Trap", + Self::Abort => "Abort", + Self::IOT => "IOT", + Self::Bus => "Bus", + Self::FloatingPointException => "FloatingPointException", + Self::Kill => "Kill", + Self::User1 => "User1", + Self::Segv => "Segv", + Self::User2 => "User2", + Self::Pipe => "Pipe", + Self::Alarm => "Alarm", + Self::Term => "Term", + Self::Child => "Child", + Self::Continue => "Continue", + Self::Stop => "Stop", + Self::TSTP => "TSTP", + Self::TTIN => "TTIN", + Self::TTOU => "TTOU", + Self::Urgent => "Urgent", + Self::XCPU => "XCPU", + Self::XFSZ => "XFSZ", + Self::VirtualAlarm => "VirtualAlarm", + Self::Profiling => "Profiling", + Self::Winch => "Winch", + Self::IO => "IO", + Self::Poll => "Poll", + Self::Power => "Power", + Self::Sys => "Sys", + }; + f.write_str(s) + } +} + +/// A struct representing system load average value. +/// +/// It is returned by [`SystemExt::load_average`][crate::SystemExt::load_average]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt}; +/// +/// let s = System::new_all(); +/// let load_avg = s.load_average(); +/// println!( +/// "one minute: {}%, five minutes: {}%, fifteen minutes: {}%", +/// load_avg.one, +/// load_avg.five, +/// load_avg.fifteen, +/// ); +/// ``` +#[repr(C)] +#[derive(Default, Debug, Clone)] +pub struct LoadAvg { + /// Average load within one minute. + pub one: f64, + /// Average load within five minutes. + pub five: f64, + /// Average load within fifteen minutes. + pub fifteen: f64, +} + +macro_rules! xid { + ($(#[$outer:meta])+ $name:ident, $type:ty) => { + $(#[$outer])+ + #[repr(transparent)] + #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] + pub struct $name(pub(crate) $type); + + impl std::ops::Deref for $name { + type Target = $type; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + }; +} + +macro_rules! uid { + ($(#[$outer:meta])+ $type:ty) => { + xid!($(#[$outer])+ Uid, $type); + }; +} + +macro_rules! gid { + ($(#[$outer:meta])+ $type:ty) => { + xid!($(#[$outer])+ #[derive(Copy)] Gid, $type); + }; +} + +cfg_if::cfg_if! { + if #[cfg(all( + not(feature = "unknown-ci"), + any( + target_os = "freebsd", + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "ios", + ) + ))] { + uid!( + /// A user id wrapping a platform specific type. + libc::uid_t + ); + gid!( + /// A group id wrapping a platform specific type. + libc::gid_t + ); + } else if #[cfg(windows)] { + uid!( + /// A user id wrapping a platform specific type. + Box<str> + ); + gid!( + /// A group id wrapping a platform specific type. + u32 + ); + } else { + uid!( + /// A user id wrapping a platform specific type. + u32 + ); + gid!( + /// A group id wrapping a platform specific type. + u32 + ); + + } +} + +/// Type containing user information. +/// +/// It is returned by [`SystemExt::users`][crate::SystemExt::users]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt}; +/// +/// let s = System::new_all(); +/// println!("users: {:?}", s.users()); +/// ``` +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct User { + pub(crate) uid: Uid, + pub(crate) gid: Gid, + pub(crate) name: String, + pub(crate) groups: Vec<String>, +} + +impl UserExt for User { + fn id(&self) -> &Uid { + &self.uid + } + + fn group_id(&self) -> Gid { + self.gid + } + + fn name(&self) -> &str { + &self.name + } + + fn groups(&self) -> &[String] { + &self.groups + } +} + +/// Type containing read and written bytes. +/// +/// It is returned by [`ProcessExt::disk_usage`][crate::ProcessExt::disk_usage]. +/// +/// ```no_run +/// use sysinfo::{ProcessExt, System, SystemExt}; +/// +/// let s = System::new_all(); +/// for (pid, process) in s.processes() { +/// let disk_usage = process.disk_usage(); +/// println!("[{}] read bytes : new/total => {}/{} B", +/// pid, +/// disk_usage.read_bytes, +/// disk_usage.total_read_bytes, +/// ); +/// println!("[{}] written bytes: new/total => {}/{} B", +/// pid, +/// disk_usage.written_bytes, +/// disk_usage.total_written_bytes, +/// ); +/// } +/// ``` +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd)] +pub struct DiskUsage { + /// Total number of written bytes. + pub total_written_bytes: u64, + /// Number of written bytes since the last refresh. + pub written_bytes: u64, + /// Total number of read bytes. + pub total_read_bytes: u64, + /// Number of read bytes since the last refresh. + pub read_bytes: u64, +} + +/// Enum describing the different status of a process. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ProcessStatus { + /// ## Linux/FreeBSD + /// + /// Waiting in uninterruptible disk sleep. + /// + /// ## macOs + /// + /// Process being created by fork. + /// + /// ## Other OS + /// + /// Not available. + Idle, + /// Running. + Run, + /// ## Linux/FreeBSD + /// + /// Sleeping in an interruptible waiting. + /// + /// ## macOS + /// + /// Sleeping on an address. + /// + /// ## Other OS + /// + /// Not available. + Sleep, + /// ## Linux/FreeBSD + /// + /// Stopped (on a signal) or (before Linux 2.6.33) trace stopped. + /// + /// ## macOS + /// + /// Process debugging or suspension. + /// + /// ## Other OS + /// + /// Not available. + Stop, + /// ## Linux/FreeBSD/macOS + /// + /// Zombie process. Terminated but not reaped by its parent. + /// + /// ## Other OS + /// + /// Not available. + Zombie, + /// ## Linux + /// + /// Tracing stop (Linux 2.6.33 onward). Stopped by debugger during the tracing. + /// + /// ## Other OS + /// + /// Not available. + Tracing, + /// ## Linux/FreeBSD + /// + /// Dead/uninterruptible sleep (usually IO). + /// + /// ## Other OS + /// + /// Not available. + Dead, + /// ## Linux + /// + /// Wakekill (Linux 2.6.33 to 3.13 only). + /// + /// ## Other OS + /// + /// Not available. + Wakekill, + /// ## Linux + /// + /// Waking (Linux 2.6.33 to 3.13 only). + /// + /// ## Other OS + /// + /// Not available. + Waking, + /// ## Linux + /// + /// Parked (Linux 3.9 to 3.13 only). + /// + /// ## Other OS + /// + /// Not available. + Parked, + /// ## FreeBSD + /// + /// Blocked on a lock. + /// + /// ## Other OS + /// + /// Not available. + LockBlocked, + /// Unknown. + Unknown(u32), +} + +/// Returns the pid for the current process. +/// +/// `Err` is returned in case the platform isn't supported. +/// +/// ```no_run +/// use sysinfo::get_current_pid; +/// +/// match get_current_pid() { +/// Ok(pid) => { +/// println!("current pid: {}", pid); +/// } +/// Err(e) => { +/// eprintln!("failed to get current pid: {}", e); +/// } +/// } +/// ``` +#[allow(clippy::unnecessary_wraps)] +pub fn get_current_pid() -> Result<Pid, &'static str> { + cfg_if::cfg_if! { + if #[cfg(feature = "unknown-ci")] { + fn inner() -> Result<Pid, &'static str> { + Err("Unknown platform (CI)") + } + } else if #[cfg(any( + target_os = "freebsd", + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "ios", + ))] { + fn inner() -> Result<Pid, &'static str> { + unsafe { Ok(Pid(libc::getpid())) } + } + } else if #[cfg(windows)] { + fn inner() -> Result<Pid, &'static str> { + use winapi::um::processthreadsapi::GetCurrentProcessId; + + unsafe { Ok(Pid(GetCurrentProcessId() as _)) } + } + } else { + fn inner() -> Result<Pid, &'static str> { + Err("Unknown platform") + } + } + } + inner() +} + +#[cfg(test)] +mod tests { + use super::ProcessStatus; + + // This test only exists to ensure that the `Display` trait is implemented on the + // `ProcessStatus` enum on all targets. + #[test] + fn check_display_impl_process_status() { + println!("{} {:?}", ProcessStatus::Parked, ProcessStatus::Idle); + } +} diff --git a/vendor/sysinfo/src/debug.rs b/vendor/sysinfo/src/debug.rs new file mode 100644 index 000000000..e2f4361d9 --- /dev/null +++ b/vendor/sysinfo/src/debug.rs @@ -0,0 +1,132 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + Component, ComponentExt, Cpu, CpuExt, Disk, DiskExt, NetworkData, NetworkExt, Networks, + NetworksExt, Process, ProcessExt, System, SystemExt, +}; + +use std::fmt; + +impl fmt::Debug for Cpu { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Cpu") + .field("name", &self.name()) + .field("CPU usage", &self.cpu_usage()) + .field("frequency", &self.frequency()) + .field("vendor ID", &self.vendor_id()) + .field("brand", &self.brand()) + .finish() + } +} + +impl fmt::Debug for System { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("System") + .field("global CPU usage", &self.global_cpu_info().cpu_usage()) + .field("load average", &self.load_average()) + .field("total memory", &self.total_memory()) + .field("free memory", &self.free_memory()) + .field("total swap", &self.total_swap()) + .field("free swap", &self.free_swap()) + .field("nb CPUs", &self.cpus().len()) + .field("nb network interfaces", &self.networks().iter().count()) + .field("nb processes", &self.processes().len()) + .field("nb disks", &self.disks().len()) + .field("nb components", &self.components().len()) + .finish() + } +} + +impl fmt::Debug for Disk { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + fmt, + "Disk({:?})[FS: {:?}][Type: {:?}][removable: {}] mounted on {:?}: {}/{} B", + self.name(), + self.file_system() + .iter() + .map(|c| *c as char) + .collect::<Vec<_>>(), + self.type_(), + if self.is_removable() { "yes" } else { "no" }, + self.mount_point(), + self.available_space(), + self.total_space(), + ) + } +} + +impl fmt::Debug for Process { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Process") + .field("pid", &self.pid()) + .field("parent", &self.parent()) + .field("name", &self.name()) + .field("environ", &self.environ()) + .field("command", &self.cmd()) + .field("executable path", &self.exe()) + .field("current working directory", &self.cwd()) + .field("memory usage", &self.memory()) + .field("virtual memory usage", &self.virtual_memory()) + .field("CPU usage", &self.cpu_usage()) + .field("status", &self.status()) + .field("root", &self.root()) + .field("disk_usage", &self.disk_usage()) + .finish() + } +} + +impl fmt::Debug for Component { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(critical) = self.critical() { + write!( + f, + "{}: {}°C (max: {}°C / critical: {}°C)", + self.label(), + self.temperature(), + self.max(), + critical + ) + } else { + write!( + f, + "{}: {}°C (max: {}°C)", + self.label(), + self.temperature(), + self.max() + ) + } + } +} + +impl fmt::Debug for Networks { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Networks {{ {} }}", + self.iter() + .map(|x| format!("{:?}", x)) + .collect::<Vec<_>>() + .join(", ") + ) + } +} + +impl fmt::Debug for NetworkData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NetworkData") + .field("income", &self.received()) + .field("total income", &self.total_received()) + .field("outcome", &self.transmitted()) + .field("total outcome", &self.total_transmitted()) + .field("packets income", &self.packets_received()) + .field("total packets income", &self.total_packets_received()) + .field("packets outcome", &self.packets_transmitted()) + .field("total packets outcome", &self.total_packets_transmitted()) + .field("errors income", &self.errors_on_received()) + .field("total errors income", &self.total_errors_on_received()) + .field("errors outcome", &self.errors_on_transmitted()) + .field("total errors outcome", &self.total_errors_on_transmitted()) + .finish() + } +} diff --git a/vendor/sysinfo/src/freebsd/component.rs b/vendor/sysinfo/src/freebsd/component.rs new file mode 100644 index 000000000..6529be73c --- /dev/null +++ b/vendor/sysinfo/src/freebsd/component.rs @@ -0,0 +1,71 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use super::utils::get_sys_value_by_name; +use crate::ComponentExt; + +#[doc = include_str!("../../md_doc/component.md")] +pub struct Component { + id: Vec<u8>, + label: String, + temperature: f32, + max: f32, +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + None + } + + fn label(&self) -> &str { + &self.label + } + + fn refresh(&mut self) { + unsafe { + if let Some(temperature) = refresh_component(&self.id) { + self.temperature = temperature; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } + } +} + +unsafe fn refresh_component(id: &[u8]) -> Option<f32> { + let mut temperature: libc::c_int = 0; + if !get_sys_value_by_name(id, &mut temperature) { + None + } else { + // convert from Kelvin (x 10 -> 273.2 x 10) to Celsius + Some((temperature - 2732) as f32 / 10.) + } +} + +pub unsafe fn get_components(nb_cpus: usize) -> Vec<Component> { + // For now, we only have temperature for CPUs... + let mut components = Vec::with_capacity(nb_cpus); + + for core in 0..nb_cpus { + let id = format!("dev.cpu.{}.temperature\0", core) + .as_bytes() + .to_vec(); + if let Some(temperature) = refresh_component(&id) { + components.push(Component { + id, + label: format!("CPU {}", core + 1), + temperature, + max: temperature, + }); + } + } + components +} diff --git a/vendor/sysinfo/src/freebsd/cpu.rs b/vendor/sysinfo/src/freebsd/cpu.rs new file mode 100644 index 000000000..9d6c8e506 --- /dev/null +++ b/vendor/sysinfo/src/freebsd/cpu.rs @@ -0,0 +1,44 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::CpuExt; + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + pub(crate) cpu_usage: f32, + name: String, + pub(crate) vendor_id: String, + pub(crate) frequency: u64, +} + +impl Cpu { + pub(crate) fn new(name: String, vendor_id: String, frequency: u64) -> Cpu { + Cpu { + cpu_usage: 0., + name, + vendor_id, + frequency, + } + } +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + fn brand(&self) -> &str { + "" + } +} diff --git a/vendor/sysinfo/src/freebsd/disk.rs b/vendor/sysinfo/src/freebsd/disk.rs new file mode 100644 index 000000000..5156f1215 --- /dev/null +++ b/vendor/sysinfo/src/freebsd/disk.rs @@ -0,0 +1,143 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskExt, DiskType}; + +use std::ffi::{OsStr, OsString}; +use std::path::{Path, PathBuf}; + +use super::utils::c_buf_to_str; + +#[doc = include_str!("../../md_doc/disk.md")] +pub struct Disk { + name: OsString, + c_mount_point: Vec<libc::c_char>, + mount_point: PathBuf, + total_space: u64, + available_space: u64, + file_system: Vec<u8>, + is_removable: bool, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + DiskType::Unknown(-1) + } + + fn name(&self) -> &OsStr { + &self.name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + &self.mount_point + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + fn refresh(&mut self) -> bool { + unsafe { + let mut vfs: libc::statvfs = std::mem::zeroed(); + refresh_disk(self, &mut vfs) + } + } +} + +// FIXME: if you want to get disk I/O usage: +// statfs.[f_syncwrites, f_asyncwrites, f_syncreads, f_asyncreads] + +unsafe fn refresh_disk(disk: &mut Disk, vfs: &mut libc::statvfs) -> bool { + if libc::statvfs(disk.c_mount_point.as_ptr() as *const _, vfs) < 0 { + return false; + } + let f_frsize: u64 = vfs.f_frsize as _; + + disk.total_space = vfs.f_blocks.saturating_mul(f_frsize); + disk.available_space = vfs.f_favail.saturating_mul(f_frsize); + true +} + +pub unsafe fn get_all_disks() -> Vec<Disk> { + let mut fs_infos: *mut libc::statfs = std::ptr::null_mut(); + + let count = libc::getmntinfo(&mut fs_infos, libc::MNT_WAIT); + + if count < 1 { + return Vec::new(); + } + let mut vfs: libc::statvfs = std::mem::zeroed(); + let fs_infos: &[libc::statfs] = std::slice::from_raw_parts(fs_infos as _, count as _); + let mut disks = Vec::new(); + + for fs_info in fs_infos { + if fs_info.f_mntfromname[0] == 0 || fs_info.f_mntonname[0] == 0 { + // If we have missing information, no need to look any further... + continue; + } + let fs_type: &[libc::c_char] = + if let Some(pos) = fs_info.f_fstypename.iter().position(|x| *x == 0) { + &fs_info.f_fstypename[..pos] + } else { + &fs_info.f_fstypename + }; + let fs_type: &[u8] = std::slice::from_raw_parts(fs_type.as_ptr() as _, fs_type.len()); + match fs_type { + b"autofs" | b"devfs" | b"linprocfs" | b"procfs" | b"fdesckfs" | b"tmpfs" + | b"linsysfs" => { + sysinfo_debug!( + "Memory filesystem `{:?}`, ignoring it.", + c_buf_to_str(&fs_info.f_fstypename).unwrap(), + ); + continue; + } + _ => {} + } + + if libc::statvfs(fs_info.f_mntonname.as_ptr(), &mut vfs) != 0 { + continue; + } + + let mount_point = match c_buf_to_str(&fs_info.f_mntonname) { + Some(m) => m, + None => { + sysinfo_debug!("Cannot get disk mount point, ignoring it."); + continue; + } + }; + + let name = if mount_point == "/" { + OsString::from("root") + } else { + OsString::from(mount_point) + }; + + // USB keys and CDs are removable. + let is_removable = + [b"USB", b"usb"].iter().any(|b| b == &fs_type) || fs_type.starts_with(b"/dev/cd"); + + let f_frsize: u64 = vfs.f_frsize as _; + + disks.push(Disk { + name, + c_mount_point: fs_info.f_mntonname.to_vec(), + mount_point: PathBuf::from(mount_point), + total_space: vfs.f_blocks.saturating_mul(f_frsize), + available_space: vfs.f_favail.saturating_mul(f_frsize), + file_system: fs_type.to_vec(), + is_removable, + }); + } + disks +} diff --git a/vendor/sysinfo/src/freebsd/mod.rs b/vendor/sysinfo/src/freebsd/mod.rs new file mode 100644 index 000000000..8a8726812 --- /dev/null +++ b/vendor/sysinfo/src/freebsd/mod.rs @@ -0,0 +1,16 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub mod component; +pub mod cpu; +pub mod disk; +pub mod network; +pub mod process; +pub mod system; +mod utils; + +pub use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo/src/freebsd/network.rs b/vendor/sysinfo/src/freebsd/network.rs new file mode 100644 index 000000000..e58ad823a --- /dev/null +++ b/vendor/sysinfo/src/freebsd/network.rs @@ -0,0 +1,199 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::collections::{hash_map, HashMap}; +use std::mem::MaybeUninit; + +use super::utils; +use crate::{NetworkExt, NetworksExt, NetworksIter}; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $data:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $data.$name; + }}; +} + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Networks { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + fn iter(&self) -> NetworksIter { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + unsafe { + self.refresh_interfaces(true); + } + // Remove interfaces which are gone. + self.interfaces.retain(|_, n| n.updated); + } + + fn refresh(&mut self) { + unsafe { + self.refresh_interfaces(false); + } + } +} + +impl Networks { + unsafe fn refresh_interfaces(&mut self, refresh_all: bool) { + let mut nb_interfaces: libc::c_int = 0; + if !utils::get_sys_value( + &[ + libc::CTL_NET, + libc::PF_LINK, + libc::NETLINK_GENERIC, + libc::IFMIB_SYSTEM, + libc::IFMIB_IFCOUNT, + ], + &mut nb_interfaces, + ) { + return; + } + if refresh_all { + // We don't need to update this value if we're not updating all interfaces. + for interface in self.interfaces.values_mut() { + interface.updated = false; + } + } + let mut data: libc::ifmibdata = MaybeUninit::zeroed().assume_init(); + for row in 1..nb_interfaces { + let mib = [ + libc::CTL_NET, + libc::PF_LINK, + libc::NETLINK_GENERIC, + libc::IFMIB_IFDATA, + row, + libc::IFDATA_GENERAL, + ]; + + if !utils::get_sys_value(&mib, &mut data) { + continue; + } + if let Some(name) = utils::c_buf_to_string(&data.ifmd_name) { + let data = &data.ifmd_data; + match self.interfaces.entry(name) { + hash_map::Entry::Occupied(mut e) => { + let mut interface = e.get_mut(); + + old_and_new!(interface, ifi_ibytes, old_ifi_ibytes, data); + old_and_new!(interface, ifi_obytes, old_ifi_obytes, data); + old_and_new!(interface, ifi_ipackets, old_ifi_ipackets, data); + old_and_new!(interface, ifi_opackets, old_ifi_opackets, data); + old_and_new!(interface, ifi_ierrors, old_ifi_ierrors, data); + old_and_new!(interface, ifi_oerrors, old_ifi_oerrors, data); + interface.updated = true; + } + hash_map::Entry::Vacant(e) => { + if !refresh_all { + // This is simply a refresh, we don't want to add new interfaces! + continue; + } + e.insert(NetworkData { + ifi_ibytes: data.ifi_ibytes, + old_ifi_ibytes: 0, + ifi_obytes: data.ifi_obytes, + old_ifi_obytes: 0, + ifi_ipackets: data.ifi_ipackets, + old_ifi_ipackets: 0, + ifi_opackets: data.ifi_opackets, + old_ifi_opackets: 0, + ifi_ierrors: data.ifi_ierrors, + old_ifi_ierrors: 0, + ifi_oerrors: data.ifi_oerrors, + old_ifi_oerrors: 0, + updated: true, + }); + } + } + } + } + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +pub struct NetworkData { + /// Total number of bytes received over interface. + ifi_ibytes: u64, + old_ifi_ibytes: u64, + /// Total number of bytes transmitted over interface. + ifi_obytes: u64, + old_ifi_obytes: u64, + /// Total number of packets received. + ifi_ipackets: u64, + old_ifi_ipackets: u64, + /// Total number of packets transmitted. + ifi_opackets: u64, + old_ifi_opackets: u64, + /// Shows the total number of packets received with error. This includes + /// too-long-frames errors, ring-buffer overflow errors, CRC errors, + /// frame alignment errors, fifo overruns, and missed packets. + ifi_ierrors: u64, + old_ifi_ierrors: u64, + /// similar to `ifi_ierrors` + ifi_oerrors: u64, + old_ifi_oerrors: u64, + /// Whether or not the above data has been updated during refresh + updated: bool, +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.ifi_ibytes.saturating_sub(self.old_ifi_ibytes) + } + + fn total_received(&self) -> u64 { + self.ifi_ibytes + } + + fn transmitted(&self) -> u64 { + self.ifi_obytes.saturating_sub(self.old_ifi_obytes) + } + + fn total_transmitted(&self) -> u64 { + self.ifi_obytes + } + + fn packets_received(&self) -> u64 { + self.ifi_ipackets.saturating_sub(self.old_ifi_ipackets) + } + + fn total_packets_received(&self) -> u64 { + self.ifi_ipackets + } + + fn packets_transmitted(&self) -> u64 { + self.ifi_opackets.saturating_sub(self.old_ifi_opackets) + } + + fn total_packets_transmitted(&self) -> u64 { + self.ifi_opackets + } + + fn errors_on_received(&self) -> u64 { + self.ifi_ierrors.saturating_sub(self.old_ifi_ierrors) + } + + fn total_errors_on_received(&self) -> u64 { + self.ifi_ierrors + } + + fn errors_on_transmitted(&self) -> u64 { + self.ifi_oerrors.saturating_sub(self.old_ifi_oerrors) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.ifi_oerrors + } +} diff --git a/vendor/sysinfo/src/freebsd/process.rs b/vendor/sysinfo/src/freebsd/process.rs new file mode 100644 index 000000000..b3302edbe --- /dev/null +++ b/vendor/sysinfo/src/freebsd/process.rs @@ -0,0 +1,254 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; + +use std::fmt; +use std::path::{Path, PathBuf}; + +use super::utils::{get_sys_value_str, WrapMap}; + +#[doc(hidden)] +impl From<libc::c_char> for ProcessStatus { + fn from(status: libc::c_char) -> ProcessStatus { + match status { + libc::SIDL => ProcessStatus::Idle, + libc::SRUN => ProcessStatus::Run, + libc::SSLEEP => ProcessStatus::Sleep, + libc::SSTOP => ProcessStatus::Stop, + libc::SZOMB => ProcessStatus::Zombie, + libc::SWAIT => ProcessStatus::Dead, + libc::SLOCK => ProcessStatus::LockBlocked, + x => ProcessStatus::Unknown(x as _), + } + } +} + +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::Dead => "Dead", + ProcessStatus::LockBlocked => "LockBlocked", + _ => "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, + pub(crate) updated: bool, + cpu_usage: f32, + start_time: u64, + run_time: u64, + pub(crate) status: ProcessStatus, + user_id: Uid, + group_id: Gid, + read_bytes: u64, + old_read_bytes: u64, + written_bytes: u64, + old_written_bytes: u64, +} + +impl ProcessExt for Process { + fn kill_with(&self, signal: Signal) -> Option<bool> { + let c_signal = super::system::convert_signal(signal)?; + unsafe { Some(libc::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> { + Some(&self.user_id) + } + + fn group_id(&self) -> Option<Gid> { + Some(self.group_id) + } +} + +pub(crate) unsafe fn get_process_data( + kproc: &libc::kinfo_proc, + wrap: &WrapMap, + page_size: isize, + fscale: f32, + now: u64, + refresh_kind: ProcessRefreshKind, +) -> Result<Option<Process>, ()> { + if kproc.ki_pid != 1 && (kproc.ki_flag as libc::c_int & libc::P_SYSTEM) != 0 { + // We filter out the kernel threads. + return Err(()); + } + + // We now get the values needed for both new and existing process. + let cpu_usage = if refresh_kind.cpu() { + (100 * kproc.ki_pctcpu) as f32 / fscale + } else { + 0. + }; + // Processes can be reparented apparently? + let parent = if kproc.ki_ppid != 0 { + Some(Pid(kproc.ki_ppid)) + } else { + None + }; + let status = ProcessStatus::from(kproc.ki_stat); + + // from FreeBSD source /src/usr.bin/top/machine.c + let virtual_memory = (kproc.ki_size / 1_000) as u64; + let memory = (kproc.ki_rssize as u64).saturating_mul(page_size as _) / 1_000; + // FIXME: This is to get the "real" run time (in micro-seconds). + // let run_time = (kproc.ki_runtime + 5_000) / 10_000; + + if let Some(proc_) = (*wrap.0.get()).get_mut(&Pid(kproc.ki_pid)) { + proc_.cpu_usage = cpu_usage; + proc_.parent = parent; + proc_.status = status; + proc_.virtual_memory = virtual_memory; + proc_.memory = memory; + proc_.run_time = now.saturating_sub(proc_.start_time); + proc_.updated = true; + + if refresh_kind.disk_usage() { + proc_.old_read_bytes = proc_.read_bytes; + proc_.read_bytes = kproc.ki_rusage.ru_inblock as _; + proc_.old_written_bytes = proc_.written_bytes; + proc_.written_bytes = kproc.ki_rusage.ru_oublock as _; + } + + return Ok(None); + } + + // This is a new process, we need to get more information! + let mut buffer = [0; libc::PATH_MAX as usize + 1]; + + let exe = get_sys_value_str( + &[ + libc::CTL_KERN, + libc::KERN_PROC, + libc::KERN_PROC_PATHNAME, + kproc.ki_pid, + ], + &mut buffer, + ) + .unwrap_or_default(); + // For some reason, it can return completely invalid path like `p\u{5}`. So we need to use + // procstat to get around this problem. + // let cwd = get_sys_value_str( + // &[ + // libc::CTL_KERN, + // libc::KERN_PROC, + // libc::KERN_PROC_CWD, + // kproc.ki_pid, + // ], + // &mut buffer, + // ) + // .map(|s| s.into()) + // .unwrap_or_else(PathBuf::new); + + let start_time = kproc.ki_start.tv_sec as u64; + Ok(Some(Process { + pid: Pid(kproc.ki_pid), + parent, + user_id: Uid(kproc.ki_ruid), + group_id: Gid(kproc.ki_rgid), + start_time, + run_time: now.saturating_sub(start_time), + cpu_usage, + virtual_memory, + memory, + // procstat_getfiles + cwd: PathBuf::new(), + exe: exe.into(), + // kvm_getargv isn't thread-safe so we get it in the main thread. + name: String::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. + root: PathBuf::new(), + // kvm_getenvv isn't thread-safe so we get it in the main thread. + environ: Vec::new(), + status, + read_bytes: kproc.ki_rusage.ru_inblock as _, + old_read_bytes: 0, + written_bytes: kproc.ki_rusage.ru_oublock as _, + old_written_bytes: 0, + updated: true, + })) +} diff --git a/vendor/sysinfo/src/freebsd/system.rs b/vendor/sysinfo/src/freebsd/system.rs new file mode 100644 index 000000000..16da2de50 --- /dev/null +++ b/vendor/sysinfo/src/freebsd/system.rs @@ -0,0 +1,803 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + sys::{component::Component, Cpu, Disk, Networks, Process}, + CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, +}; + +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::ffi::CStr; +use std::mem::MaybeUninit; +use std::path::{Path, PathBuf}; +use std::ptr::NonNull; + +use super::utils::{ + self, boot_time, c_buf_to_string, from_cstr_array, get_frequency_for_cpu, get_sys_value, + get_sys_value_array, get_sys_value_by_name, get_sys_value_str_by_name, get_system_info, + init_mib, +}; + +use libc::c_int; + +declare_signals! { + c_int, + Signal::Hangup => libc::SIGHUP, + Signal::Interrupt => libc::SIGINT, + Signal::Quit => libc::SIGQUIT, + Signal::Illegal => libc::SIGILL, + Signal::Trap => libc::SIGTRAP, + Signal::Abort => libc::SIGABRT, + Signal::IOT => libc::SIGIOT, + Signal::Bus => libc::SIGBUS, + Signal::FloatingPointException => libc::SIGFPE, + Signal::Kill => libc::SIGKILL, + Signal::User1 => libc::SIGUSR1, + Signal::Segv => libc::SIGSEGV, + Signal::User2 => libc::SIGUSR2, + Signal::Pipe => libc::SIGPIPE, + Signal::Alarm => libc::SIGALRM, + Signal::Term => libc::SIGTERM, + Signal::Child => libc::SIGCHLD, + Signal::Continue => libc::SIGCONT, + Signal::Stop => libc::SIGSTOP, + Signal::TSTP => libc::SIGTSTP, + Signal::TTIN => libc::SIGTTIN, + Signal::TTOU => libc::SIGTTOU, + Signal::Urgent => libc::SIGURG, + Signal::XCPU => libc::SIGXCPU, + Signal::XFSZ => libc::SIGXFSZ, + Signal::VirtualAlarm => libc::SIGVTALRM, + Signal::Profiling => libc::SIGPROF, + Signal::Winch => libc::SIGWINCH, + Signal::IO => libc::SIGIO, + Signal::Sys => libc::SIGSYS, + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: HashMap<Pid, Process>, + mem_total: u64, + mem_free: u64, + mem_used: u64, + swap_total: u64, + swap_used: u64, + global_cpu: Cpu, + cpus: Vec<Cpu>, + components: Vec<Component>, + disks: Vec<Disk>, + networks: Networks, + users: Vec<User>, + boot_time: u64, + system_info: SystemInfo, + got_cpu_frequency: bool, +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + fn new_with_specifics(refreshes: RefreshKind) -> System { + let system_info = SystemInfo::new(); + + let mut s = System { + process_list: HashMap::with_capacity(200), + mem_total: 0, + mem_free: 0, + mem_used: 0, + swap_total: 0, + swap_used: 0, + global_cpu: Cpu::new(String::new(), String::new(), 0), + cpus: Vec::with_capacity(system_info.nb_cpus as _), + components: Vec::with_capacity(2), + disks: Vec::with_capacity(1), + networks: Networks::new(), + users: Vec::new(), + boot_time: boot_time(), + system_info, + got_cpu_frequency: false, + }; + s.refresh_specifics(refreshes); + s + } + + fn refresh_memory(&mut self) { + if self.mem_total == 0 { + self.mem_total = self.system_info.get_total_memory(); + } + self.mem_used = self.system_info.get_used_memory(); + self.mem_free = self.system_info.get_free_memory(); + let (swap_used, swap_total) = self.system_info.get_swap_info(); + self.swap_total = swap_total; + self.swap_used = swap_used; + } + + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { + if self.cpus.is_empty() { + let mut frequency = 0; + + // We get the CPU vendor ID in here. + let vendor_id = + get_sys_value_str_by_name(b"hw.model\0").unwrap_or_else(|| "<unknown>".to_owned()); + for pos in 0..self.system_info.nb_cpus { + if refresh_kind.frequency() { + unsafe { + frequency = get_frequency_for_cpu(pos); + } + } + self.cpus.push(Cpu::new( + format!("cpu {}", pos), + vendor_id.clone(), + frequency, + )); + } + self.global_cpu.vendor_id = vendor_id; + self.got_cpu_frequency = refresh_kind.frequency(); + } else if refresh_kind.frequency() && !self.got_cpu_frequency { + for (pos, proc_) in self.cpus.iter_mut().enumerate() { + unsafe { + proc_.frequency = get_frequency_for_cpu(pos as _); + } + } + self.got_cpu_frequency = true; + } + if refresh_kind.cpu_usage() { + self.system_info + .get_cpu_usage(&mut self.global_cpu, &mut self.cpus); + } + } + + fn refresh_components_list(&mut self) { + if self.cpus.is_empty() { + self.refresh_cpu(); + } + self.components = unsafe { super::component::get_components(self.cpus.len()) }; + } + + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { + unsafe { self.refresh_procs(refresh_kind) } + } + + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + unsafe { + let kd = self.system_info.kd.as_ptr(); + let mut count = 0; + let procs = libc::kvm_getprocs(kd, libc::KERN_PROC_PROC, 0, &mut count); + if count < 1 { + sysinfo_debug!("kvm_getprocs returned nothing..."); + return false; + } + let now = super::utils::get_now(); + + let fscale = self.system_info.fscale; + let page_size = self.system_info.page_size as isize; + let proc_list = utils::WrapMap(UnsafeCell::new(&mut self.process_list)); + let procs: &mut [utils::KInfoProc] = + std::slice::from_raw_parts_mut(procs as _, count as _); + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + macro_rules! multi_iter { + ($name:ident, $($iter:tt)+) => { + $name = crate::utils::into_iter(procs).$($iter)+; + } + } + + let ret; + #[cfg(not(feature = "multithread"))] + multi_iter!(ret, find(|kproc| kproc.ki_pid == pid.0)); + #[cfg(feature = "multithread")] + multi_iter!(ret, find_any(|kproc| kproc.ki_pid == pid.0)); + + let kproc = if let Some(kproc) = ret { + kproc + } else { + return false; + }; + match super::process::get_process_data( + kproc, + &proc_list, + page_size, + fscale, + now, + refresh_kind, + ) { + Ok(Some(proc_)) => { + self.add_missing_proc_info(self.system_info.kd.as_ptr(), kproc, proc_); + true + } + Ok(None) => true, + Err(_) => false, + } + } + } + + fn refresh_disks_list(&mut self) { + self.disks = unsafe { super::disk::get_all_disks() }; + } + + fn refresh_users_list(&mut self) { + self.users = crate::users::get_users_list(); + } + + // COMMON PART + // + // Need to be moved into a "common" file to avoid duplication. + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.get(&pid) + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn global_cpu_info(&self) -> &Cpu { + &self.global_cpu + } + + fn cpus(&self) -> &[Cpu] { + &self.cpus + } + + fn physical_core_count(&self) -> Option<usize> { + let mut physical_core_count: u32 = 0; + + unsafe { + if get_sys_value_by_name(b"hw.ncpu\0", &mut physical_core_count) { + Some(physical_core_count as _) + } else { + None + } + } + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + self.mem_free + } + + fn available_memory(&self) -> u64 { + self.mem_free + } + + fn used_memory(&self) -> u64 { + self.mem_used + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_total - self.swap_used + } + + // TODO: need to be checked + fn used_swap(&self) -> u64 { + self.swap_used + } + + fn components(&self) -> &[Component] { + &self.components + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut self.components + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks + } + + fn uptime(&self) -> u64 { + unsafe { + let csec = libc::time(::std::ptr::null_mut()); + + libc::difftime(csec, self.boot_time as _) as u64 + } + } + + fn boot_time(&self) -> u64 { + self.boot_time + } + + fn load_average(&self) -> LoadAvg { + let mut loads = vec![0f64; 3]; + unsafe { + libc::getloadavg(loads.as_mut_ptr(), 3); + LoadAvg { + one: loads[0], + five: loads[1], + fifteen: loads[2], + } + } + } + + fn users(&self) -> &[User] { + &self.users + } + + fn name(&self) -> Option<String> { + self.system_info.get_os_name() + } + + fn long_os_version(&self) -> Option<String> { + self.system_info.get_os_release_long() + } + + fn host_name(&self) -> Option<String> { + self.system_info.get_hostname() + } + + fn kernel_version(&self) -> Option<String> { + self.system_info.get_kernel_version() + } + + fn os_version(&self) -> Option<String> { + self.system_info.get_os_release() + } +} + +impl Default for System { + fn default() -> Self { + Self::new() + } +} + +impl System { + unsafe fn refresh_procs(&mut self, refresh_kind: ProcessRefreshKind) { + let kd = self.system_info.kd.as_ptr(); + let procs = { + let mut count = 0; + let procs = libc::kvm_getprocs(kd, libc::KERN_PROC_PROC, 0, &mut count); + if count < 1 { + sysinfo_debug!("kvm_getprocs returned nothing..."); + return; + } + #[cfg(feature = "multithread")] + use rayon::iter::{ParallelIterator, ParallelIterator as IterTrait}; + #[cfg(not(feature = "multithread"))] + use std::iter::Iterator as IterTrait; + + crate::utils::into_iter(&mut self.process_list).for_each(|(_, proc_)| { + proc_.updated = false; + }); + + let fscale = self.system_info.fscale; + let page_size = self.system_info.page_size as isize; + let now = super::utils::get_now(); + let proc_list = utils::WrapMap(UnsafeCell::new(&mut self.process_list)); + let procs: &mut [utils::KInfoProc] = + std::slice::from_raw_parts_mut(procs as _, count as _); + + IterTrait::filter_map(crate::utils::into_iter(procs), |kproc| { + super::process::get_process_data( + kproc, + &proc_list, + page_size, + fscale, + now, + refresh_kind, + ) + .ok() + .and_then(|p| p.map(|p| (kproc, p))) + }) + .collect::<Vec<_>>() + }; + + // We remove all processes that don't exist anymore. + self.process_list.retain(|_, v| v.updated); + + for (kproc, proc_) in procs { + self.add_missing_proc_info(kd, kproc, proc_); + } + } + + unsafe fn add_missing_proc_info( + &mut self, + kd: *mut libc::kvm_t, + kproc: &libc::kinfo_proc, + mut proc_: Process, + ) { + proc_.cmd = from_cstr_array(libc::kvm_getargv(kd, kproc, 0) as _); + self.system_info.get_proc_missing_info(kproc, &mut proc_); + if !proc_.cmd.is_empty() { + // First, we try to retrieve the name from the command line. + let p = Path::new(&proc_.cmd[0]); + if let Some(name) = p.file_name().and_then(|s| s.to_str()) { + proc_.name = name.to_owned(); + } + if proc_.root.as_os_str().is_empty() { + if let Some(parent) = p.parent() { + proc_.root = parent.to_path_buf(); + } + } + } + if proc_.name.is_empty() { + // 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_.name = c_buf_to_string(&kproc.ki_comm).unwrap_or_default(); + } + proc_.environ = from_cstr_array(libc::kvm_getenvv(kd, kproc, 0) as _); + self.process_list.insert(proc_.pid, proc_); + } +} + +#[derive(Debug)] +struct Zfs { + enabled: bool, + mib_arcstats_size: [c_int; 5], +} + +impl Zfs { + fn new() -> Self { + let mut zfs = Self { + enabled: false, + mib_arcstats_size: Default::default(), + }; + unsafe { + init_mib( + b"kstat.zfs.misc.arcstats.size\0", + &mut zfs.mib_arcstats_size, + ); + let mut arc_size: u64 = 0; + if get_sys_value(&zfs.mib_arcstats_size, &mut arc_size) { + zfs.enabled = arc_size != 0; + } + } + zfs + } + + fn arc_size(&self) -> Option<u64> { + if self.enabled { + let mut arc_size: u64 = 0; + unsafe { + get_sys_value(&self.mib_arcstats_size, &mut arc_size); + Some(arc_size) + } + } else { + None + } + } +} + +/// This struct is used to get system information more easily. +#[derive(Debug)] +struct SystemInfo { + hw_physical_memory: [c_int; 2], + page_size: c_int, + virtual_page_count: [c_int; 4], + virtual_wire_count: [c_int; 4], + virtual_active_count: [c_int; 4], + virtual_cache_count: [c_int; 4], + virtual_inactive_count: [c_int; 4], + virtual_free_count: [c_int; 4], + os_type: [c_int; 2], + os_release: [c_int; 2], + kern_version: [c_int; 2], + hostname: [c_int; 2], + buf_space: [c_int; 2], + nb_cpus: c_int, + kd: NonNull<libc::kvm_t>, + // For these two fields, we could use `kvm_getcptime` but the function isn't very efficient... + mib_cp_time: [c_int; 2], + mib_cp_times: [c_int; 2], + // For the global CPU usage. + cp_time: utils::VecSwitcher<libc::c_ulong>, + // For each CPU usage. + cp_times: utils::VecSwitcher<libc::c_ulong>, + /// From FreeBSD manual: "The kernel fixed-point scale factor". It's used when computing + /// processes' CPU usage. + fscale: f32, + procstat: *mut libc::procstat, + zfs: Zfs, +} + +// This is needed because `kd: *mut libc::kvm_t` isn't thread-safe. +unsafe impl Send for SystemInfo {} +unsafe impl Sync for SystemInfo {} + +impl SystemInfo { + fn new() -> Self { + unsafe { + let mut errbuf = + MaybeUninit::<[libc::c_char; libc::_POSIX2_LINE_MAX as usize]>::uninit(); + let kd = NonNull::new(libc::kvm_openfiles( + std::ptr::null(), + b"/dev/null\0".as_ptr() as *const _, + std::ptr::null(), + 0, + errbuf.as_mut_ptr() as *mut _, + )) + .expect("kvm_openfiles failed"); + + let mut smp: c_int = 0; + let mut nb_cpus: c_int = 1; + if !get_sys_value_by_name(b"kern.smp.active\0", &mut smp) { + smp = 0; + } + #[allow(clippy::collapsible_if)] // I keep as is for readability reasons. + if smp != 0 { + if !get_sys_value_by_name(b"kern.smp.cpus\0", &mut nb_cpus) || nb_cpus < 1 { + nb_cpus = 1; + } + } + + let mut si = SystemInfo { + hw_physical_memory: Default::default(), + page_size: 0, + virtual_page_count: Default::default(), + virtual_wire_count: Default::default(), + virtual_active_count: Default::default(), + virtual_cache_count: Default::default(), + virtual_inactive_count: Default::default(), + virtual_free_count: Default::default(), + buf_space: Default::default(), + os_type: Default::default(), + os_release: Default::default(), + kern_version: Default::default(), + hostname: Default::default(), + nb_cpus, + kd, + mib_cp_time: Default::default(), + mib_cp_times: Default::default(), + cp_time: utils::VecSwitcher::new(vec![0; libc::CPUSTATES as usize]), + cp_times: utils::VecSwitcher::new(vec![ + 0; + nb_cpus as usize * libc::CPUSTATES as usize + ]), + fscale: 0., + procstat: std::ptr::null_mut(), + zfs: Zfs::new(), + }; + let mut fscale: c_int = 0; + if !get_sys_value_by_name(b"kern.fscale\0", &mut fscale) { + // Default value used in htop. + fscale = 2048; + } + si.fscale = fscale as f32; + + if !get_sys_value_by_name(b"vm.stats.vm.v_page_size\0", &mut si.page_size) { + panic!("cannot get page size..."); + } + + init_mib(b"hw.physmem\0", &mut si.hw_physical_memory); + init_mib(b"vm.stats.vm.v_page_count\0", &mut si.virtual_page_count); + init_mib(b"vm.stats.vm.v_wire_count\0", &mut si.virtual_wire_count); + init_mib( + b"vm.stats.vm.v_active_count\0", + &mut si.virtual_active_count, + ); + init_mib(b"vm.stats.vm.v_cache_count\0", &mut si.virtual_cache_count); + init_mib( + b"vm.stats.vm.v_inactive_count\0", + &mut si.virtual_inactive_count, + ); + init_mib(b"vm.stats.vm.v_free_count\0", &mut si.virtual_free_count); + init_mib(b"vfs.bufspace\0", &mut si.buf_space); + + init_mib(b"kern.ostype\0", &mut si.os_type); + init_mib(b"kern.osrelease\0", &mut si.os_release); + init_mib(b"kern.version\0", &mut si.kern_version); + init_mib(b"kern.hostname\0", &mut si.hostname); + + init_mib(b"kern.cp_time\0", &mut si.mib_cp_time); + init_mib(b"kern.cp_times\0", &mut si.mib_cp_times); + + si + } + } + + fn get_os_name(&self) -> Option<String> { + get_system_info(&[self.os_type[0], self.os_type[1]], Some("FreeBSD")) + } + + fn get_kernel_version(&self) -> Option<String> { + get_system_info(&[self.kern_version[0], self.kern_version[1]], None) + } + + fn get_os_release_long(&self) -> Option<String> { + get_system_info(&[self.os_release[0], self.os_release[1]], None) + } + + fn get_os_release(&self) -> Option<String> { + // It returns something like "13.0-RELEASE". We want to keep everything until the "-". + get_system_info(&[self.os_release[0], self.os_release[1]], None) + .and_then(|s| s.split('-').next().map(|s| s.to_owned())) + } + + fn get_hostname(&self) -> Option<String> { + get_system_info(&[self.hostname[0], self.hostname[1]], Some("")) + } + + /// Returns (used, total). + fn get_swap_info(&self) -> (u64, u64) { + // Magic number used in htop. Cannot find how they got it when reading `kvm_getswapinfo` + // source code so here we go... + const LEN: usize = 16; + let mut swap = MaybeUninit::<[libc::kvm_swap; LEN]>::uninit(); + unsafe { + let nswap = + libc::kvm_getswapinfo(self.kd.as_ptr(), swap.as_mut_ptr() as *mut _, LEN as _, 0) + as usize; + if nswap < 1 { + return (0, 0); + } + let swap = + std::slice::from_raw_parts(swap.as_ptr() as *mut libc::kvm_swap, nswap.min(LEN)); + let (used, total) = swap.iter().fold((0, 0), |(used, total): (u64, u64), swap| { + ( + used.saturating_add(swap.ksw_used as _), + total.saturating_add(swap.ksw_total as _), + ) + }); + ( + used.saturating_mul(self.page_size as _) / 1_000, + total.saturating_mul(self.page_size as _) / 1_000, + ) + } + } + + fn get_total_memory(&self) -> u64 { + let mut nb_pages: u64 = 0; + unsafe { + if get_sys_value(&self.virtual_page_count, &mut nb_pages) { + return nb_pages.saturating_mul(self.page_size as _) / 1_000; + } + + // This is a fallback. It includes all the available memory, not just the one available for + // the users. + let mut total_memory: u64 = 0; + get_sys_value(&self.hw_physical_memory, &mut total_memory); + total_memory / 1_000 + } + } + + fn get_used_memory(&self) -> u64 { + let mut mem_active: u64 = 0; + let mut mem_wire: u64 = 0; + + unsafe { + get_sys_value(&self.virtual_active_count, &mut mem_active); + get_sys_value(&self.virtual_wire_count, &mut mem_wire); + + let mut mem_wire = mem_wire.saturating_mul(self.page_size as _); + // We need to subtract "ZFS ARC" from the "wired memory" because it should belongs to cache + // but the kernel reports it as "wired memory" instead... + if let Some(arc_size) = self.zfs.arc_size() { + mem_wire -= arc_size; + } + let used = mem_active + .saturating_mul(self.page_size as _) + .saturating_add(mem_wire); + used / 1_000 + } + } + + fn get_free_memory(&self) -> u64 { + let mut buffers_mem: u64 = 0; + let mut inactive_mem: u64 = 0; + let mut cached_mem: u64 = 0; + let mut free_mem: u64 = 0; + + unsafe { + get_sys_value(&self.buf_space, &mut buffers_mem); + get_sys_value(&self.virtual_inactive_count, &mut inactive_mem); + get_sys_value(&self.virtual_cache_count, &mut cached_mem); + get_sys_value(&self.virtual_free_count, &mut free_mem); + // For whatever reason, buffers_mem is already the right value... + let free = buffers_mem + .saturating_add(inactive_mem.saturating_mul(self.page_size as u64)) + .saturating_add(cached_mem.saturating_mul(self.page_size as u64)) + .saturating_add(free_mem.saturating_mul(self.page_size as u64)); + free / 1_000 + } + } + + fn get_cpu_usage(&mut self, global: &mut Cpu, cpus: &mut [Cpu]) { + unsafe { + get_sys_value_array(&self.mib_cp_time, self.cp_time.get_mut()); + get_sys_value_array(&self.mib_cp_times, self.cp_times.get_mut()); + } + + fn fill_cpu(proc_: &mut Cpu, new_cp_time: &[libc::c_ulong], old_cp_time: &[libc::c_ulong]) { + let mut total_new: u64 = 0; + let mut total_old: u64 = 0; + let mut cp_diff: libc::c_ulong = 0; + + for i in 0..(libc::CPUSTATES as usize) { + // We obviously don't want to get the idle part of the CPU usage, otherwise + // we would always be at 100%... + if i != libc::CP_IDLE as usize { + cp_diff += new_cp_time[i] - old_cp_time[i]; + } + total_new += new_cp_time[i] as u64; + total_old += old_cp_time[i] as u64; + } + + let total_diff = total_new - total_old; + if total_diff < 1 { + proc_.cpu_usage = 0.; + } else { + proc_.cpu_usage = cp_diff as f32 / total_diff as f32 * 100.; + } + } + + fill_cpu(global, self.cp_time.get_new(), self.cp_time.get_old()); + let old_cp_times = self.cp_times.get_old(); + let new_cp_times = self.cp_times.get_new(); + for (pos, proc_) in cpus.iter_mut().enumerate() { + let index = pos * libc::CPUSTATES as usize; + + fill_cpu(proc_, &new_cp_times[index..], &old_cp_times[index..]); + } + } + + #[allow(clippy::collapsible_if)] // I keep as is for readability reasons. + unsafe fn get_proc_missing_info(&mut self, kproc: &libc::kinfo_proc, proc_: &mut Process) { + if self.procstat.is_null() { + self.procstat = libc::procstat_open_sysctl(); + } + if self.procstat.is_null() { + return; + } + let head = libc::procstat_getfiles(self.procstat, kproc as *const _ as usize as *mut _, 0); + if head.is_null() { + return; + } + let mut entry = (*head).stqh_first; + let mut done = 0; + while !entry.is_null() && done < 2 { + { + let tmp = &*entry; + if tmp.fs_uflags & libc::PS_FST_UFLAG_CDIR != 0 { + if !tmp.fs_path.is_null() { + if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { + proc_.cwd = PathBuf::from(p); + done += 1; + } + } + } else if tmp.fs_uflags & libc::PS_FST_UFLAG_RDIR != 0 { + if !tmp.fs_path.is_null() { + if let Ok(p) = CStr::from_ptr(tmp.fs_path).to_str() { + proc_.root = PathBuf::from(p); + done += 1; + } + } + } + } + entry = (*entry).next.stqe_next; + } + libc::procstat_freefiles(self.procstat, head); + } +} + +impl Drop for SystemInfo { + fn drop(&mut self) { + unsafe { + libc::kvm_close(self.kd.as_ptr()); + if !self.procstat.is_null() { + libc::procstat_close(self.procstat); + } + } + } +} diff --git a/vendor/sysinfo/src/freebsd/utils.rs b/vendor/sysinfo/src/freebsd/utils.rs new file mode 100644 index 000000000..00de3e9d9 --- /dev/null +++ b/vendor/sysinfo/src/freebsd/utils.rs @@ -0,0 +1,296 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +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::mem; +use std::time::SystemTime; + +/// This struct is used to switch between the "old" and "new" every time you use "get_mut". +#[derive(Debug)] +pub(crate) struct VecSwitcher<T> { + v1: Vec<T>, + v2: Vec<T>, + first: bool, +} + +impl<T: Clone> VecSwitcher<T> { + pub fn new(v1: Vec<T>) -> Self { + let v2 = v1.clone(); + + Self { + v1, + v2, + first: true, + } + } + + pub fn get_mut(&mut self) -> &mut [T] { + self.first = !self.first; + if self.first { + // It means that `v2` will be the "new". + &mut self.v2 + } else { + // It means that `v1` will be the "new". + &mut self.v1 + } + } + + pub fn get_old(&self) -> &[T] { + if self.first { + &self.v1 + } else { + &self.v2 + } + } + + pub fn get_new(&self) -> &[T] { + if self.first { + &self.v2 + } else { + &self.v1 + } + } +} + +#[inline] +pub unsafe fn init_mib(name: &[u8], mib: &mut [c_int]) { + let mut len = mib.len(); + libc::sysctlnametomib(name.as_ptr() as _, mib.as_mut_ptr(), &mut len); +} + +pub(crate) fn boot_time() -> u64 { + let mut boot_time = timeval { + tv_sec: 0, + tv_usec: 0, + }; + let mut len = std::mem::size_of::<timeval>(); + let mut mib: [c_int; 2] = [libc::CTL_KERN, libc::KERN_BOOTTIME]; + unsafe { + if libc::sysctl( + mib.as_mut_ptr(), + mib.len() as _, + &mut boot_time as *mut timeval as *mut _, + &mut len, + std::ptr::null_mut(), + 0, + ) < 0 + { + 0 + } else { + boot_time.tv_sec as _ + } + } +} + +pub(crate) unsafe fn get_sys_value<T: Sized>(mib: &[c_int], value: &mut T) -> bool { + let mut len = mem::size_of::<T>() as libc::size_t; + libc::sysctl( + mib.as_ptr(), + mib.len() as _, + value as *mut _ as *mut _, + &mut len, + std::ptr::null_mut(), + 0, + ) == 0 +} + +pub(crate) unsafe fn get_sys_value_array<T: Sized>(mib: &[c_int], value: &mut [T]) -> bool { + let mut len = (mem::size_of::<T>() * value.len()) as libc::size_t; + libc::sysctl( + mib.as_ptr(), + mib.len() as _, + value.as_mut_ptr() as *mut _, + &mut len as *mut _, + std::ptr::null_mut(), + 0, + ) == 0 +} + +pub(crate) fn c_buf_to_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) { + // Shrink buffer to terminate the null bytes + std::str::from_utf8(&buf[..pos]).ok() + } else { + std::str::from_utf8(buf).ok() + } + } +} + +pub(crate) fn c_buf_to_string(buf: &[libc::c_char]) -> Option<String> { + c_buf_to_str(buf).map(|s| s.to_owned()) +} + +pub(crate) unsafe fn get_sys_value_str(mib: &[c_int], buf: &mut [libc::c_char]) -> Option<String> { + let mut len = (mem::size_of::<libc::c_char>() * buf.len()) as libc::size_t; + if libc::sysctl( + mib.as_ptr(), + mib.len() as _, + buf.as_mut_ptr() as *mut _, + &mut len, + std::ptr::null_mut(), + 0, + ) != 0 + { + return None; + } + c_buf_to_string(&buf[..len / mem::size_of::<libc::c_char>()]) +} + +pub(crate) unsafe fn get_sys_value_by_name<T: Sized>(name: &[u8], value: &mut T) -> bool { + let mut len = mem::size_of::<T>() as libc::size_t; + let original = len; + + libc::sysctlbyname( + name.as_ptr() as *const c_char, + value as *mut _ as *mut _, + &mut len, + std::ptr::null_mut(), + 0, + ) == 0 + && original == len +} + +pub(crate) fn get_sys_value_str_by_name(name: &[u8]) -> Option<String> { + let mut size = 0; + + unsafe { + if libc::sysctlbyname( + name.as_ptr() as *const c_char, + std::ptr::null_mut(), + &mut size, + std::ptr::null_mut(), + 0, + ) == 0 + && size > 0 + { + // now create a buffer with the size and get the real value + let mut buf: Vec<libc::c_char> = vec![0; size as usize]; + + if libc::sysctlbyname( + name.as_ptr() as *const c_char, + buf.as_mut_ptr() as *mut _, + &mut size, + std::ptr::null_mut(), + 0, + ) == 0 + && size > 0 + { + c_buf_to_string(&buf) + } else { + // getting the system value failed + None + } + } else { + None + } + } +} + +pub(crate) fn get_system_info(mib: &[c_int], default: Option<&str>) -> Option<String> { + let mut size = 0; + + unsafe { + // Call first to get size + libc::sysctl( + mib.as_ptr(), + mib.len() as _, + std::ptr::null_mut(), + &mut size, + std::ptr::null_mut(), + 0, + ); + + // exit early if we did not update the size + if size == 0 { + default.map(|s| s.to_owned()) + } else { + // set the buffer to the correct size + let mut buf: Vec<libc::c_char> = vec![0; size as usize]; + + if libc::sysctl( + mib.as_ptr(), + mib.len() as _, + buf.as_mut_ptr() as _, + &mut size, + std::ptr::null_mut(), + 0, + ) == -1 + { + // If command fails return default + default.map(|s| s.to_owned()) + } else { + c_buf_to_string(&buf) + } + } + } +} + +pub(crate) unsafe fn from_cstr_array(ptr: *const *const c_char) -> Vec<String> { + if ptr.is_null() { + return Vec::new(); + } + let mut max = 0; + loop { + let ptr = ptr.add(max); + if (*ptr).is_null() { + break; + } + max += 1; + } + if max == 0 { + return Vec::new(); + } + let mut ret = Vec::with_capacity(max); + + for pos in 0..max { + let p = ptr.add(pos); + if let Ok(s) = CStr::from_ptr(*p).to_str() { + ret.push(s.to_owned()); + } + } + ret +} + +pub(crate) fn get_now() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|n| n.as_secs()) + .unwrap_or(0) +} + +// All this is needed because `kinfo_proc` doesn't implement `Send` (because it contains pointers). +pub(crate) struct WrapMap<'a>(pub UnsafeCell<&'a mut HashMap<Pid, Process>>); + +unsafe impl<'a> Send for WrapMap<'a> {} +unsafe impl<'a> Sync for WrapMap<'a> {} + +#[repr(transparent)] +pub(crate) struct KInfoProc(libc::kinfo_proc); +unsafe impl Send for KInfoProc {} +unsafe impl Sync for KInfoProc {} + +impl std::ops::Deref for KInfoProc { + type Target = libc::kinfo_proc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub(crate) unsafe fn get_frequency_for_cpu(cpu_nb: c_int) -> u64 { + let mut frequency = 0; + + // The information can be missing if it's running inside a VM. + if !get_sys_value_by_name( + format!("dev.cpu.{}.freq\0", cpu_nb).as_bytes(), + &mut frequency, + ) { + frequency = 0; + } + frequency as _ +} diff --git a/vendor/sysinfo/src/lib.rs b/vendor/sysinfo/src/lib.rs new file mode 100644 index 000000000..977de23a3 --- /dev/null +++ b/vendor/sysinfo/src/lib.rs @@ -0,0 +1,447 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#![doc = include_str!("../README.md")] +#![allow(unknown_lints)] +#![deny(missing_docs)] +#![deny(rustdoc::broken_intra_doc_links)] +#![allow(clippy::upper_case_acronyms)] +#![allow(clippy::non_send_fields_in_send_ty)] +#![allow(renamed_and_removed_lints)] +#![allow(unknown_lints)] + +#[macro_use] +mod macros; + +cfg_if::cfg_if! { + if #[cfg(feature = "unknown-ci")] { + // This is used in CI to check that the build for unknown targets is compiling fine. + mod unknown; + use unknown as sys; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 0; + } else if #[cfg(any(target_os = "macos", target_os = "ios"))] { + mod apple; + use apple as sys; + extern crate core_foundation_sys; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 1; + } else if #[cfg(windows)] { + mod windows; + use windows as sys; + extern crate winapi; + extern crate ntapi; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 1; + } else if #[cfg(any(target_os = "linux", target_os = "android"))] { + mod linux; + use linux as sys; + pub(crate) mod users; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 1; + } else if #[cfg(target_os = "freebsd")] { + mod freebsd; + use freebsd as sys; + pub(crate) mod users; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 1; + } else { + mod unknown; + use unknown as sys; + + #[cfg(test)] + pub(crate) const MIN_USERS: usize = 0; + } +} + +pub use common::{ + get_current_pid, CpuRefreshKind, DiskType, DiskUsage, Gid, LoadAvg, NetworksIter, Pid, PidExt, + ProcessRefreshKind, ProcessStatus, RefreshKind, Signal, Uid, User, +}; +pub use sys::{Component, Cpu, Disk, NetworkData, Networks, Process, System}; +pub use traits::{ + ComponentExt, CpuExt, DiskExt, NetworkExt, NetworksExt, ProcessExt, SystemExt, UserExt, +}; + +#[cfg(feature = "c-interface")] +pub use c_interface::*; + +#[cfg(feature = "c-interface")] +mod c_interface; +mod common; +mod debug; +mod system; +mod traits; +mod utils; + +/// This function is only used on linux targets, on the other platforms it does nothing and returns +/// `false`. +/// +/// On linux, to improve performance, we keep a `/proc` file open for each process we index with +/// a maximum number of files open equivalent to half of the system limit. +/// +/// The problem is that some users might need all the available file descriptors so we need to +/// allow them to change this limit. +/// +/// Note that if you set a limit bigger than the system limit, the system limit will be set. +/// +/// Returns `true` if the new value has been set. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt, set_open_files_limit}; +/// +/// // We call the function before any call to the processes update. +/// if !set_open_files_limit(10) { +/// // It'll always return false on non-linux targets. +/// eprintln!("failed to update the open files limit..."); +/// } +/// let s = System::new_all(); +/// ``` +pub fn set_open_files_limit(mut _new_limit: isize) -> bool { + cfg_if::cfg_if! { + if #[cfg(all(not(feature = "unknown-ci"), any(target_os = "linux", target_os = "android")))] + { + if _new_limit < 0 { + _new_limit = 0; + } + let max = sys::system::get_max_nb_fds(); + if _new_limit > max { + _new_limit = max; + } + unsafe { + if let Ok(ref mut x) = sys::system::REMAINING_FILES.lock() { + // If files are already open, to be sure that the number won't be bigger when those + // files are closed, we subtract the current number of opened files to the new + // limit. + let diff = max.saturating_sub(**x); + **x = _new_limit.saturating_sub(diff); + true + } else { + false + } + } + } else { + false + } + } +} + +// FIXME: Can be removed once negative trait bounds are supported. +#[cfg(doctest)] +mod doctest { + /// Check that `Process` doesn't implement `Clone`. + /// + /// First we check that the "basic" code works: + /// + /// ```no_run + /// use sysinfo::{Process, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let p: &Process = s.processes().values().next().unwrap(); + /// ``` + /// + /// And now we check if it fails when we try to clone it: + /// + /// ```compile_fail + /// use sysinfo::{Process, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let p: &Process = s.processes().values().next().unwrap(); + /// let p = (*p).clone(); + /// ``` + mod process_clone {} + + /// Check that `System` doesn't implement `Clone`. + /// + /// First we check that the "basic" code works: + /// + /// ```no_run + /// use sysinfo::{Process, System, SystemExt}; + /// + /// let s = System::new(); + /// ``` + /// + /// And now we check if it fails when we try to clone it: + /// + /// ```compile_fail + /// use sysinfo::{Process, System, SystemExt}; + /// + /// let s = System::new(); + /// let s = s.clone(); + /// ``` + mod system_clone {} +} + +#[cfg(test)] +mod test { + use crate::*; + + #[cfg(feature = "unknown-ci")] + #[test] + fn check_unknown_ci_feature() { + assert!(!System::IS_SUPPORTED); + } + + #[test] + fn check_process_memory_usage() { + let mut s = System::new(); + s.refresh_all(); + + if System::IS_SUPPORTED { + // No process should have 0 as memory usage. + #[cfg(not(feature = "apple-sandbox"))] + assert!(!s.processes().iter().all(|(_, proc_)| proc_.memory() == 0)); + } else { + // There should be no process, but if there is one, its memory usage should be 0. + assert!(s.processes().iter().all(|(_, proc_)| proc_.memory() == 0)); + } + } + + #[test] + fn check_memory_usage() { + let mut s = System::new(); + + assert_eq!(s.total_memory(), 0); + assert_eq!(s.free_memory(), 0); + assert_eq!(s.available_memory(), 0); + assert_eq!(s.used_memory(), 0); + assert_eq!(s.total_swap(), 0); + assert_eq!(s.free_swap(), 0); + assert_eq!(s.used_swap(), 0); + + s.refresh_memory(); + if System::IS_SUPPORTED { + assert!(s.total_memory() > 0); + assert!(s.used_memory() > 0); + if s.total_swap() > 0 { + // I think it's pretty safe to assume that there is still some swap left... + assert!(s.free_swap() > 0); + } + } else { + assert_eq!(s.total_memory(), 0); + assert_eq!(s.used_memory(), 0); + assert_eq!(s.total_swap(), 0); + assert_eq!(s.free_swap(), 0); + } + } + + #[cfg(target_os = "linux")] + #[test] + fn check_processes_cpu_usage() { + if !System::IS_SUPPORTED { + return; + } + let mut s = System::new(); + + s.refresh_processes(); + // All CPU usage will start at zero until the second refresh + assert!(s + .processes() + .iter() + .all(|(_, proc_)| proc_.cpu_usage() == 0.0)); + + // Wait a bit to update CPU usage values + std::thread::sleep(std::time::Duration::from_millis(100)); + s.refresh_processes(); + assert!(s + .processes() + .iter() + .all(|(_, proc_)| proc_.cpu_usage() >= 0.0 + && proc_.cpu_usage() <= (s.cpus().len() as f32) * 100.0)); + assert!(s + .processes() + .iter() + .any(|(_, proc_)| proc_.cpu_usage() > 0.0)); + } + + #[test] + fn check_cpu_usage() { + if !System::IS_SUPPORTED { + return; + } + let mut s = System::new(); + for _ in 0..10 { + s.refresh_cpu(); + // Wait a bit to update CPU usage values + std::thread::sleep(std::time::Duration::from_millis(100)); + if s.cpus().iter().any(|c| c.cpu_usage() > 0.0) { + // All good! + return; + } + } + panic!("CPU usage is always zero..."); + } + + #[test] + fn check_users() { + let mut s = System::new(); + assert!(s.users().is_empty()); + s.refresh_users_list(); + assert!(s.users().len() >= MIN_USERS); + + let mut s = System::new(); + assert!(s.users().is_empty()); + s.refresh_all(); + assert!(s.users().is_empty()); + + let s = System::new_all(); + assert!(s.users().len() >= MIN_USERS); + } + + #[test] + fn check_uid_gid() { + let mut s = System::new(); + assert!(s.users().is_empty()); + s.refresh_users_list(); + let users = s.users(); + assert!(users.len() >= MIN_USERS); + + if System::IS_SUPPORTED { + #[cfg(not(target_os = "windows"))] + { + let user = users + .iter() + .find(|u| u.name() == "root") + .expect("no root user"); + assert_eq!(**user.id(), 0); + assert_eq!(*user.group_id(), 0); + if let Some(user) = users.iter().find(|u| *u.group_id() > 0) { + assert!(**user.id() > 0); + assert!(*user.group_id() > 0); + } + assert!(users.iter().filter(|u| **u.id() > 0).count() > 0); + } + + // And now check that our `get_user_by_id` method works. + s.refresh_processes(); + assert!(s + .processes() + .iter() + .filter_map(|(_, p)| p.user_id()) + .any(|uid| s.get_user_by_id(uid).is_some())); + } + } + + #[test] + fn check_system_info() { + // We don't want to test on unsupported systems. + if System::IS_SUPPORTED { + let s = System::new(); + assert!(!s.name().expect("Failed to get system name").is_empty()); + + assert!(!s + .kernel_version() + .expect("Failed to get kernel version") + .is_empty()); + + assert!(!s.os_version().expect("Failed to get os version").is_empty()); + + assert!(!s + .long_os_version() + .expect("Failed to get long OS version") + .is_empty()); + } + } + + #[test] + fn check_host_name() { + // We don't want to test on unsupported systems. + if System::IS_SUPPORTED { + let s = System::new(); + assert!(s.host_name().is_some()); + } + } + + #[test] + fn check_refresh_process_return_value() { + // We don't want to test on unsupported systems. + if System::IS_SUPPORTED { + let _pid = get_current_pid().expect("Failed to get current PID"); + + #[cfg(not(feature = "apple-sandbox"))] + { + let mut s = System::new(); + // First check what happens in case the process isn't already in our process list. + assert!(s.refresh_process(_pid)); + // Then check that it still returns true if the process is already in our process list. + assert!(s.refresh_process(_pid)); + } + } + } + + #[test] + fn ensure_is_supported_is_set_correctly() { + if MIN_USERS > 0 { + assert!(System::IS_SUPPORTED); + } else { + assert!(!System::IS_SUPPORTED); + } + } + + #[test] + fn check_cpus_number() { + let mut s = System::new(); + + // This information isn't retrieved by default. + assert!(s.cpus().is_empty()); + if System::IS_SUPPORTED { + // The physical cores count is recomputed every time the function is called, so the + // information must be relevant even with nothing initialized. + let physical_cores_count = s + .physical_core_count() + .expect("failed to get number of physical cores"); + + s.refresh_cpu(); + // The cpus shouldn't be empty anymore. + assert!(!s.cpus().is_empty()); + + // In case we are running inside a VM, it's possible to not have a physical core, only + // logical ones, which is why we don't test `physical_cores_count > 0`. + let physical_cores_count2 = s + .physical_core_count() + .expect("failed to get number of physical cores"); + assert!(physical_cores_count2 <= s.cpus().len()); + assert_eq!(physical_cores_count, physical_cores_count2); + } else { + assert_eq!(s.physical_core_count(), None); + } + assert!(s.physical_core_count().unwrap_or(0) <= s.cpus().len()); + } + + #[test] + fn check_nb_supported_signals() { + if System::IS_SUPPORTED { + assert!( + !System::SUPPORTED_SIGNALS.is_empty(), + "SUPPORTED_SIGNALS shoudn't be empty on supported systems!" + ); + } else { + assert!( + System::SUPPORTED_SIGNALS.is_empty(), + "SUPPORTED_SIGNALS should be empty on not support systems!" + ); + } + } + + // Ensure that the CPUs frequency isn't retrieved until we ask for it. + #[test] + #[cfg(not(target_os = "freebsd"))] // In a VM, it'll fail. + fn check_cpu_frequency() { + if !System::IS_SUPPORTED { + return; + } + let mut s = System::new(); + s.refresh_processes(); + for proc_ in s.cpus() { + assert_eq!(proc_.frequency(), 0); + } + s.refresh_cpu(); + for proc_ in s.cpus() { + assert_ne!(proc_.frequency(), 0); + } + } +} diff --git a/vendor/sysinfo/src/linux/component.rs b/vendor/sysinfo/src/linux/component.rs new file mode 100644 index 000000000..3a10588e5 --- /dev/null +++ b/vendor/sysinfo/src/linux/component.rs @@ -0,0 +1,189 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::ComponentExt; + +use std::collections::HashMap; +use std::fs::{metadata, read_dir, File}; +use std::io::Read; +use std::path::{Path, PathBuf}; + +#[doc = include_str!("../../md_doc/component.md")] +pub struct Component { + temperature: f32, + max: f32, + critical: Option<f32>, + label: String, + input_file: PathBuf, +} + +fn get_file_line(file: &Path, capacity: usize) -> Option<String> { + let mut reader = String::with_capacity(capacity); + if let Ok(mut f) = File::open(file) { + if f.read_to_string(&mut reader).is_ok() { + Some(reader) + } else { + None + } + } else { + None + } +} + +fn is_file<T: AsRef<Path>>(path: T) -> bool { + metadata(path).ok().map(|m| m.is_file()).unwrap_or(false) +} + +fn append_files(components: &mut Vec<Component>, folder: &Path) { + let mut matchings: HashMap<u32, Vec<String>> = HashMap::with_capacity(10); + + if let Ok(dir) = read_dir(folder) { + for entry in dir.flatten() { + let entry = entry.path(); + if entry.is_dir() + || !entry + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .starts_with("temp") + { + continue; + } + if let Some(entry) = entry.file_name() { + if let Some(entry) = entry.to_str() { + let mut parts = entry.split('_'); + if let Some(Some(id)) = parts.next().map(|s| s[4..].parse::<u32>().ok()) { + matchings + .entry(id) + .or_insert_with(|| Vec::with_capacity(5)) + .push( + parts + .next() + .map(|s| format!("_{}", s)) + .unwrap_or_else(String::new), + ); + } + } + } + } + for (key, val) in &matchings { + let mut found_input = None; + let mut found_label = None; + for (pos, v) in val.iter().enumerate() { + match v.as_str() { + // raspberry has empty string for temperature input + "_input" | "" => { + found_input = Some(pos); + } + "_label" => { + found_label = Some(pos); + } + _ => {} + } + } + if let (Some(_), Some(found_input)) = (found_label, found_input) { + let mut p_label = folder.to_path_buf(); + let mut p_input = folder.to_path_buf(); + let mut p_crit = folder.to_path_buf(); + let mut p_max = folder.to_path_buf(); + + p_label.push(&format!("temp{}_label", key)); + p_input.push(&format!("temp{}{}", key, val[found_input])); + p_max.push(&format!("temp{}_max", key)); + p_crit.push(&format!("temp{}_crit", key)); + if is_file(&p_input) { + let label = get_file_line(p_label.as_path(), 10) + .unwrap_or_else(|| format!("Component {}", key)) // needed for raspberry pi + .replace('\n', ""); + let max = get_file_line(p_max.as_path(), 10).map(|max| { + max.replace('\n', "").parse::<f32>().unwrap_or(100_000f32) / 1000f32 + }); + let crit = get_file_line(p_crit.as_path(), 10).map(|crit| { + crit.replace('\n', "").parse::<f32>().unwrap_or(100_000f32) / 1000f32 + }); + components.push(Component::new(label, p_input.as_path(), max, crit)); + } + } + } + } +} + +impl Component { + /// Creates a new component with the given information. + pub(crate) fn new( + label: String, + input_path: &Path, + max: Option<f32>, + critical: Option<f32>, + ) -> Component { + let mut c = Component { + temperature: 0f32, + label, + input_file: input_path.to_path_buf(), + max: max.unwrap_or(0.0), + critical, + }; + c.refresh(); + c + } +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + self.critical + } + + fn label(&self) -> &str { + &self.label + } + + fn refresh(&mut self) { + if let Some(content) = get_file_line(self.input_file.as_path(), 10) { + self.temperature = content + .replace('\n', "") + .parse::<f32>() + .unwrap_or(100_000f32) + / 1000f32; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } +} + +pub(crate) fn get_components() -> Vec<Component> { + let mut components = Vec::with_capacity(10); + if let Ok(dir) = read_dir(&Path::new("/sys/class/hwmon/")) { + for entry in dir.flatten() { + let entry = entry.path(); + if !entry.is_dir() + || !entry + .file_name() + .and_then(|x| x.to_str()) + .unwrap_or("") + .starts_with("hwmon") + { + continue; + } + append_files(&mut components, &entry); + } + components.sort_by(|c1, c2| c1.label.to_lowercase().cmp(&c2.label.to_lowercase())); + } + if is_file("/sys/class/thermal/thermal_zone0/temp") { + // Specfic to raspberry pi. + components.push(Component::new( + "CPU".to_owned(), + Path::new("/sys/class/thermal/thermal_zone0/temp"), + None, + None, + )); + } + components +} diff --git a/vendor/sysinfo/src/linux/cpu.rs b/vendor/sysinfo/src/linux/cpu.rs new file mode 100644 index 000000000..f3970c318 --- /dev/null +++ b/vendor/sysinfo/src/linux/cpu.rs @@ -0,0 +1,357 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#![allow(clippy::too_many_arguments)] + +use std::collections::HashSet; +use std::fs::File; +use std::io::Read; + +use crate::CpuExt; + +/// Struct containing values to compute a CPU usage. +#[derive(Clone, Copy)] +pub(crate) struct CpuValues { + user: u64, + nice: u64, + system: u64, + idle: u64, + iowait: u64, + irq: u64, + softirq: u64, + steal: u64, + _guest: u64, + _guest_nice: u64, +} + +impl CpuValues { + /// Creates a new instance of `CpuValues` with everything set to `0`. + pub fn new() -> CpuValues { + CpuValues { + user: 0, + nice: 0, + system: 0, + idle: 0, + iowait: 0, + irq: 0, + softirq: 0, + steal: 0, + _guest: 0, + _guest_nice: 0, + } + } + + /// Creates a new instance of `CpuValues` with everything set to the corresponding argument. + pub fn new_with_values( + user: u64, + nice: u64, + system: u64, + idle: u64, + iowait: u64, + irq: u64, + softirq: u64, + steal: u64, + guest: u64, + guest_nice: u64, + ) -> CpuValues { + CpuValues { + user, + nice, + system, + idle, + iowait, + irq, + softirq, + steal, + _guest: guest, + _guest_nice: guest_nice, + } + } + + /*pub fn is_zero(&self) -> bool { + self.user == 0 && self.nice == 0 && self.system == 0 && self.idle == 0 && + self.iowait == 0 && self.irq == 0 && self.softirq == 0 && self.steal == 0 && + self.guest == 0 && self.guest_nice == 0 + }*/ + + /// Sets the given argument to the corresponding fields. + pub fn set( + &mut self, + user: u64, + nice: u64, + system: u64, + idle: u64, + iowait: u64, + irq: u64, + softirq: u64, + steal: u64, + guest: u64, + guest_nice: u64, + ) { + self.user = user; + self.nice = nice; + self.system = system; + self.idle = idle; + self.iowait = iowait; + self.irq = irq; + self.softirq = softirq; + self.steal = steal; + self._guest = guest; + self._guest_nice = guest_nice; + } + + /// Returns work time. + pub fn work_time(&self) -> u64 { + self.user + .saturating_add(self.nice) + .saturating_add(self.system) + .saturating_add(self.irq) + .saturating_add(self.softirq) + .saturating_add(self.steal) + } + + /// Returns total time. + pub fn total_time(&self) -> u64 { + // `guest` is already included in `user` + // `guest_nice` is already included in `nice` + self.work_time() + .saturating_add(self.idle) + .saturating_add(self.iowait) + } +} + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + old_values: CpuValues, + new_values: CpuValues, + pub(crate) name: String, + cpu_usage: f32, + total_time: u64, + old_total_time: u64, + pub(crate) frequency: u64, + pub(crate) vendor_id: String, + pub(crate) brand: String, +} + +impl Cpu { + pub(crate) fn new_with_values( + name: &str, + user: u64, + nice: u64, + system: u64, + idle: u64, + iowait: u64, + irq: u64, + softirq: u64, + steal: u64, + guest: u64, + guest_nice: u64, + frequency: u64, + vendor_id: String, + brand: String, + ) -> Cpu { + Cpu { + name: name.to_owned(), + old_values: CpuValues::new(), + new_values: CpuValues::new_with_values( + user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, + ), + cpu_usage: 0f32, + total_time: 0, + old_total_time: 0, + frequency, + vendor_id, + brand, + } + } + + pub(crate) fn set( + &mut self, + user: u64, + nice: u64, + system: u64, + idle: u64, + iowait: u64, + irq: u64, + softirq: u64, + steal: u64, + guest: u64, + guest_nice: u64, + ) { + macro_rules! min { + ($a:expr, $b:expr) => { + if $a > $b { + ($a - $b) as f32 + } else { + 1. + } + }; + } + self.old_values = self.new_values; + self.new_values.set( + user, nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice, + ); + self.total_time = self.new_values.total_time(); + self.old_total_time = self.old_values.total_time(); + self.cpu_usage = min!(self.new_values.work_time(), self.old_values.work_time()) + / min!(self.total_time, self.old_total_time) + * 100.; + if self.cpu_usage > 100. { + self.cpu_usage = 100.; // to prevent the percentage to go above 100% + } + } +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + /// Returns the CPU frequency in MHz. + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + fn brand(&self) -> &str { + &self.brand + } +} + +pub(crate) fn get_raw_times(p: &Cpu) -> (u64, u64) { + (p.total_time, p.old_total_time) +} + +pub(crate) fn get_cpu_frequency(cpu_core_index: usize) -> u64 { + let mut s = String::new(); + if File::open(format!( + "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_cur_freq", + cpu_core_index + )) + .and_then(|mut f| f.read_to_string(&mut s)) + .is_ok() + { + let freq_option = s.trim().split('\n').next(); + if let Some(freq_string) = freq_option { + if let Ok(freq) = freq_string.parse::<u64>() { + return freq / 1000; + } + } + } + s.clear(); + if File::open("/proc/cpuinfo") + .and_then(|mut f| f.read_to_string(&mut s)) + .is_err() + { + return 0; + } + let find_cpu_mhz = s.split('\n').find(|line| { + line.starts_with("cpu MHz\t") + || line.starts_with("BogoMIPS") + || line.starts_with("clock\t") + || line.starts_with("bogomips per cpu") + }); + find_cpu_mhz + .and_then(|line| line.split(':').last()) + .and_then(|val| val.replace("MHz", "").trim().parse::<f64>().ok()) + .map(|speed| speed as u64) + .unwrap_or_default() +} + +#[allow(unused_assignments)] +pub(crate) fn get_physical_core_count() -> Option<usize> { + let mut s = String::new(); + if let Err(_e) = File::open("/proc/cpuinfo").and_then(|mut f| f.read_to_string(&mut s)) { + sysinfo_debug!("Cannot read `/proc/cpuinfo` file: {:?}", _e); + return None; + } + + macro_rules! add_core { + ($core_ids_and_physical_ids:ident, $core_id:ident, $physical_id:ident, $cpu:ident) => {{ + if !$core_id.is_empty() && !$physical_id.is_empty() { + $core_ids_and_physical_ids.insert(format!("{} {}", $core_id, $physical_id)); + } else if !$cpu.is_empty() { + // On systems with only physical cores like raspberry, there is no "core id" or + // "physical id" fields. So if one of them is missing, we simply use the "CPU" + // info and count it as a physical core. + $core_ids_and_physical_ids.insert($cpu.to_owned()); + } + $core_id = ""; + $physical_id = ""; + $cpu = ""; + }}; + } + + let mut core_ids_and_physical_ids: HashSet<String> = HashSet::new(); + let mut core_id = ""; + let mut physical_id = ""; + let mut cpu = ""; + + for line in s.lines() { + if line.is_empty() { + add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu); + } else if line.starts_with("processor") { + cpu = line + .splitn(2, ':') + .last() + .map(|x| x.trim()) + .unwrap_or_default(); + } else if line.starts_with("core id") { + core_id = line + .splitn(2, ':') + .last() + .map(|x| x.trim()) + .unwrap_or_default(); + } else if line.starts_with("physical id") { + physical_id = line + .splitn(2, ':') + .last() + .map(|x| x.trim()) + .unwrap_or_default(); + } + } + add_core!(core_ids_and_physical_ids, core_id, physical_id, cpu); + + Some(core_ids_and_physical_ids.len()) +} + +/// Returns the brand/vendor string for the first CPU (which should be the same for all CPUs). +pub(crate) fn get_vendor_id_and_brand() -> (String, String) { + let mut s = String::new(); + if File::open("/proc/cpuinfo") + .and_then(|mut f| f.read_to_string(&mut s)) + .is_err() + { + return (String::new(), String::new()); + } + + fn get_value(s: &str) -> String { + s.split(':') + .last() + .map(|x| x.trim().to_owned()) + .unwrap_or_default() + } + + let mut vendor_id = None; + let mut brand = None; + + for it in s.split('\n') { + if it.starts_with("vendor_id\t") { + vendor_id = Some(get_value(it)); + } else if it.starts_with("model name\t") { + brand = Some(get_value(it)); + } else { + continue; + } + if brand.is_some() && vendor_id.is_some() { + break; + } + } + (vendor_id.unwrap_or_default(), brand.unwrap_or_default()) +} diff --git a/vendor/sysinfo/src/linux/disk.rs b/vendor/sysinfo/src/linux/disk.rs new file mode 100644 index 000000000..5a313fd27 --- /dev/null +++ b/vendor/sysinfo/src/linux/disk.rs @@ -0,0 +1,292 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::utils::get_all_data; +use crate::{utils, DiskExt, DiskType}; + +use libc::statvfs; +use std::ffi::{OsStr, OsString}; +use std::fs; +use std::mem; +use std::os::unix::ffi::OsStrExt; +use std::path::{Path, PathBuf}; + +macro_rules! cast { + ($x:expr) => { + u64::from($x) + }; +} + +#[doc = include_str!("../../md_doc/disk.md")] +#[derive(PartialEq, Eq)] +pub struct Disk { + type_: DiskType, + device_name: OsString, + file_system: Vec<u8>, + mount_point: PathBuf, + total_space: u64, + available_space: u64, + is_removable: bool, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + self.type_ + } + + fn name(&self) -> &OsStr { + &self.device_name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + &self.mount_point + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + fn refresh(&mut self) -> bool { + unsafe { + let mut stat: statvfs = mem::zeroed(); + let mount_point_cpath = utils::to_cpath(&self.mount_point); + if statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat) == 0 { + let tmp = cast!(stat.f_bsize).saturating_mul(cast!(stat.f_bavail)); + self.available_space = cast!(tmp); + true + } else { + false + } + } + } +} + +fn new_disk( + device_name: &OsStr, + mount_point: &Path, + file_system: &[u8], + removable_entries: &[PathBuf], +) -> Option<Disk> { + let mount_point_cpath = utils::to_cpath(mount_point); + let type_ = find_type_for_device_name(device_name); + let mut total = 0; + let mut available = 0; + unsafe { + let mut stat: statvfs = mem::zeroed(); + if statvfs(mount_point_cpath.as_ptr() as *const _, &mut stat) == 0 { + let bsize = cast!(stat.f_bsize); + let blocks = cast!(stat.f_blocks); + let bavail = cast!(stat.f_bavail); + total = bsize.saturating_mul(blocks); + available = bsize.saturating_mul(bavail); + } + if total == 0 { + return None; + } + let mount_point = mount_point.to_owned(); + let is_removable = removable_entries + .iter() + .any(|e| e.as_os_str() == device_name); + Some(Disk { + type_, + device_name: device_name.to_owned(), + file_system: file_system.to_owned(), + mount_point, + total_space: cast!(total), + available_space: cast!(available), + is_removable, + }) + } +} + +#[allow(clippy::manual_range_contains)] +fn find_type_for_device_name(device_name: &OsStr) -> DiskType { + // The format of devices are as follows: + // - device_name is symbolic link in the case of /dev/mapper/ + // and /dev/root, and the target is corresponding device under + // /sys/block/ + // - In the case of /dev/sd, the format is /dev/sd[a-z][1-9], + // corresponding to /sys/block/sd[a-z] + // - In the case of /dev/nvme, the format is /dev/nvme[0-9]n[0-9]p[0-9], + // corresponding to /sys/block/nvme[0-9]n[0-9] + // - In the case of /dev/mmcblk, the format is /dev/mmcblk[0-9]p[0-9], + // corresponding to /sys/block/mmcblk[0-9] + let device_name_path = device_name.to_str().unwrap_or_default(); + let real_path = fs::canonicalize(device_name).unwrap_or_else(|_| PathBuf::from(device_name)); + let mut real_path = real_path.to_str().unwrap_or_default(); + if device_name_path.starts_with("/dev/mapper/") { + // Recursively solve, for example /dev/dm-0 + if real_path != device_name_path { + return find_type_for_device_name(OsStr::new(&real_path)); + } + } else if device_name_path.starts_with("/dev/sd") || device_name_path.starts_with("/dev/vd") { + // Turn "sda1" into "sda" or "vda1" into "vda" + real_path = real_path.trim_start_matches("/dev/"); + real_path = real_path.trim_end_matches(|c| c >= '0' && c <= '9'); + } else if device_name_path.starts_with("/dev/nvme") { + // Turn "nvme0n1p1" into "nvme0n1" + real_path = real_path.trim_start_matches("/dev/"); + real_path = real_path.trim_end_matches(|c| c >= '0' && c <= '9'); + real_path = real_path.trim_end_matches(|c| c == 'p'); + } else if device_name_path.starts_with("/dev/root") { + // Recursively solve, for example /dev/mmcblk0p1 + if real_path != device_name_path { + return find_type_for_device_name(OsStr::new(&real_path)); + } + } else if device_name_path.starts_with("/dev/mmcblk") { + // Turn "mmcblk0p1" into "mmcblk0" + real_path = real_path.trim_start_matches("/dev/"); + real_path = real_path.trim_end_matches(|c| c >= '0' && c <= '9'); + real_path = real_path.trim_end_matches(|c| c == 'p'); + } else { + // Default case: remove /dev/ and expects the name presents under /sys/block/ + // For example, /dev/dm-0 to dm-0 + real_path = real_path.trim_start_matches("/dev/"); + } + + let trimmed: &OsStr = OsStrExt::from_bytes(real_path.as_bytes()); + + let path = Path::new("/sys/block/") + .to_owned() + .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) + .unwrap_or_default() + .trim() + .parse() + .ok() + { + // The disk is marked as rotational so it's a HDD. + Some(1) => DiskType::HDD, + // The disk is marked as non-rotational so it's very likely a SSD. + Some(0) => DiskType::SSD, + // Normally it shouldn't happen but welcome to the wonderful world of IT! :D + Some(x) => DiskType::Unknown(x), + // The information isn't available... + None => DiskType::Unknown(-1), + } +} + +fn get_all_disks_inner(content: &str) -> Vec<Disk> { + // The goal of this array is to list all removable devices (the ones whose name starts with + // "usb-"). Then we check if + let removable_entries = match fs::read_dir("/dev/disk/by-id/") { + Ok(r) => r + .filter_map(|res| Some(res.ok()?.path())) + .filter_map(|e| { + if e.file_name() + .and_then(|x| Some(x.to_str()?.starts_with("usb-"))) + .unwrap_or_default() + { + e.canonicalize().ok() + } else { + None + } + }) + .collect::<Vec<PathBuf>>(), + _ => Vec::new(), + }; + + content + .lines() + .map(|line| { + let line = line.trim_start(); + // mounts format + // http://man7.org/linux/man-pages/man5/fstab.5.html + // fs_spec<tab>fs_file<tab>fs_vfstype<tab>other fields + let mut fields = line.split_whitespace(); + let fs_spec = fields.next().unwrap_or(""); + let fs_file = fields + .next() + .unwrap_or("") + .replace("\\134", "\\") + .replace("\\040", " ") + .replace("\\011", "\t") + .replace("\\012", "\n"); + let fs_vfstype = fields.next().unwrap_or(""); + (fs_spec, fs_file, fs_vfstype) + }) + .filter(|(fs_spec, fs_file, fs_vfstype)| { + // Check if fs_vfstype is one of our 'ignored' file systems. + let filtered = matches!( + *fs_vfstype, + "rootfs" | // https://www.kernel.org/doc/Documentation/filesystems/ramfs-rootfs-initramfs.txt + "sysfs" | // pseudo file system for kernel objects + "proc" | // another pseudo file system + "tmpfs" | + "devtmpfs" | + "cgroup" | + "cgroup2" | + "pstore" | // https://www.kernel.org/doc/Documentation/ABI/testing/pstore + "squashfs" | // squashfs is a compressed read-only file system (for snaps) + "rpc_pipefs" | // The pipefs pseudo file system service + "iso9660" // optical media + ); + + !(filtered || + fs_file.starts_with("/sys") || // check if fs_file is an 'ignored' mount point + fs_file.starts_with("/proc") || + (fs_file.starts_with("/run") && !fs_file.starts_with("/run/media")) || + fs_spec.starts_with("sunrpc")) + }) + .filter_map(|(fs_spec, fs_file, fs_vfstype)| { + new_disk( + fs_spec.as_ref(), + Path::new(&fs_file), + fs_vfstype.as_bytes(), + &removable_entries, + ) + }) + .collect() +} + +pub(crate) fn get_all_disks() -> Vec<Disk> { + get_all_disks_inner(&get_all_data("/proc/mounts", 16_385).unwrap_or_default()) +} + +// #[test] +// fn check_all_disks() { +// let disks = get_all_disks_inner( +// r#"tmpfs /proc tmpfs rw,seclabel,relatime 0 0 +// proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 +// systemd-1 /proc/sys/fs/binfmt_misc autofs rw,relatime,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=17771 0 0 +// tmpfs /sys tmpfs rw,seclabel,relatime 0 0 +// sysfs /sys sysfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0 +// securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0 +// cgroup2 /sys/fs/cgroup cgroup2 rw,seclabel,nosuid,nodev,noexec,relatime,nsdelegate 0 0 +// pstore /sys/fs/pstore pstore rw,seclabel,nosuid,nodev,noexec,relatime 0 0 +// none /sys/fs/bpf bpf rw,nosuid,nodev,noexec,relatime,mode=700 0 0 +// configfs /sys/kernel/config configfs rw,nosuid,nodev,noexec,relatime 0 0 +// selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0 +// debugfs /sys/kernel/debug debugfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0 +// tmpfs /dev/shm tmpfs rw,seclabel,relatime 0 0 +// devpts /dev/pts devpts rw,seclabel,relatime,gid=5,mode=620,ptmxmode=666 0 0 +// tmpfs /sys/fs/selinux tmpfs rw,seclabel,relatime 0 0 +// /dev/vda2 /proc/filesystems xfs rw,seclabel,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota 0 0 +// "#, +// ); +// assert_eq!(disks.len(), 1); +// assert_eq!( +// disks[0], +// Disk { +// type_: DiskType::Unknown(-1), +// name: OsString::from("devpts"), +// file_system: vec![100, 101, 118, 112, 116, 115], +// mount_point: PathBuf::from("/dev/pts"), +// total_space: 0, +// available_space: 0, +// } +// ); +// } diff --git a/vendor/sysinfo/src/linux/mod.rs b/vendor/sysinfo/src/linux/mod.rs new file mode 100644 index 000000000..9cff98907 --- /dev/null +++ b/vendor/sysinfo/src/linux/mod.rs @@ -0,0 +1,16 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub mod component; +pub mod cpu; +pub mod disk; +pub mod network; +pub mod process; +pub mod system; +pub(crate) mod utils; + +pub use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo/src/linux/network.rs b/vendor/sysinfo/src/linux/network.rs new file mode 100644 index 000000000..0153c151a --- /dev/null +++ b/vendor/sysinfo/src/linux/network.rs @@ -0,0 +1,314 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::fs::File; +use std::io::Read; +use std::path::Path; + +use crate::{NetworkExt, NetworksExt, NetworksIter}; +use std::collections::{hash_map, HashMap}; + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $name; + }}; + ($ty_:expr, $name:ident, $old:ident, $path:expr) => {{ + let _tmp = $path; + $ty_.$old = $ty_.$name; + $ty_.$name = _tmp; + }}; +} + +#[allow(clippy::ptr_arg)] +fn read<P: AsRef<Path>>(parent: P, path: &str, data: &mut Vec<u8>) -> u64 { + if let Ok(mut f) = File::open(parent.as_ref().join(path)) { + if let Ok(size) = f.read(data) { + let mut i = 0; + let mut ret = 0; + + while i < size && i < data.len() && data[i] >= b'0' && data[i] <= b'9' { + ret *= 10; + ret += (data[i] - b'0') as u64; + i += 1; + } + return ret; + } + } + 0 +} + +impl Networks { + pub(crate) fn new() -> Self { + Networks { + interfaces: HashMap::new(), + } + } +} + +fn refresh_networks_list_from_sysfs( + interfaces: &mut HashMap<String, NetworkData>, + sysfs_net: &Path, +) { + if let Ok(dir) = std::fs::read_dir(sysfs_net) { + let mut data = vec![0; 30]; + + for stats in interfaces.values_mut() { + stats.updated = false; + } + + for entry in dir.flatten() { + let parent = &entry.path().join("statistics"); + let entry = match entry.file_name().into_string() { + Ok(entry) => entry, + Err(_) => continue, + }; + let rx_bytes = read(parent, "rx_bytes", &mut data); + let tx_bytes = read(parent, "tx_bytes", &mut data); + let rx_packets = read(parent, "rx_packets", &mut data); + let tx_packets = read(parent, "tx_packets", &mut data); + let rx_errors = read(parent, "rx_errors", &mut data); + let tx_errors = read(parent, "tx_errors", &mut data); + // let rx_compressed = read(parent, "rx_compressed", &mut data); + // let tx_compressed = read(parent, "tx_compressed", &mut data); + match interfaces.entry(entry) { + hash_map::Entry::Occupied(mut e) => { + let mut interface = e.get_mut(); + old_and_new!(interface, rx_bytes, old_rx_bytes); + old_and_new!(interface, tx_bytes, old_tx_bytes); + old_and_new!(interface, rx_packets, old_rx_packets); + old_and_new!(interface, tx_packets, old_tx_packets); + old_and_new!(interface, rx_errors, old_rx_errors); + old_and_new!(interface, tx_errors, old_tx_errors); + // old_and_new!(e, rx_compressed, old_rx_compressed); + // old_and_new!(e, tx_compressed, old_tx_compressed); + interface.updated = true; + } + hash_map::Entry::Vacant(e) => { + e.insert(NetworkData { + rx_bytes, + old_rx_bytes: rx_bytes, + tx_bytes, + old_tx_bytes: tx_bytes, + rx_packets, + old_rx_packets: rx_packets, + tx_packets, + old_tx_packets: tx_packets, + rx_errors, + old_rx_errors: rx_errors, + tx_errors, + old_tx_errors: tx_errors, + // rx_compressed, + // old_rx_compressed: rx_compressed, + // tx_compressed, + // old_tx_compressed: tx_compressed, + updated: true, + }); + } + }; + } + + // Remove interfaces which are gone. + interfaces.retain(|_, d| d.updated); + } +} + +impl NetworksExt for Networks { + fn iter(&self) -> NetworksIter { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh(&mut self) { + let mut v = vec![0; 30]; + + for (interface_name, data) in self.interfaces.iter_mut() { + data.update(interface_name, &mut v); + } + } + + fn refresh_networks_list(&mut self) { + refresh_networks_list_from_sysfs(&mut self.interfaces, Path::new("/sys/class/net/")); + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +pub struct NetworkData { + /// Total number of bytes received over interface. + rx_bytes: u64, + old_rx_bytes: u64, + /// Total number of bytes transmitted over interface. + tx_bytes: u64, + old_tx_bytes: u64, + /// Total number of packets received. + rx_packets: u64, + old_rx_packets: u64, + /// Total number of packets transmitted. + tx_packets: u64, + old_tx_packets: u64, + /// Shows the total number of packets received with error. This includes + /// too-long-frames errors, ring-buffer overflow errors, CRC errors, + /// frame alignment errors, fifo overruns, and missed packets. + rx_errors: u64, + old_rx_errors: u64, + /// similar to `rx_errors` + tx_errors: u64, + old_tx_errors: u64, + // /// Indicates the number of compressed packets received by this + // /// network device. This value might only be relevant for interfaces + // /// that support packet compression (e.g: PPP). + // rx_compressed: usize, + // old_rx_compressed: usize, + // /// Indicates the number of transmitted compressed packets. Note + // /// this might only be relevant for devices that support + // /// compression (e.g: PPP). + // tx_compressed: usize, + // old_tx_compressed: usize, + /// Whether or not the above data has been updated during refresh + updated: bool, +} + +impl NetworkData { + fn update(&mut self, path: &str, data: &mut Vec<u8>) { + let path = &Path::new("/sys/class/net/").join(path).join("statistics"); + old_and_new!(self, rx_bytes, old_rx_bytes, read(path, "rx_bytes", data)); + old_and_new!(self, tx_bytes, old_tx_bytes, read(path, "tx_bytes", data)); + old_and_new!( + self, + rx_packets, + old_rx_packets, + read(path, "rx_packets", data) + ); + old_and_new!( + self, + tx_packets, + old_tx_packets, + read(path, "tx_packets", data) + ); + old_and_new!( + self, + rx_errors, + old_rx_errors, + read(path, "rx_errors", data) + ); + old_and_new!( + self, + tx_errors, + old_tx_errors, + read(path, "tx_errors", data) + ); + // old_and_new!( + // self, + // rx_compressed, + // old_rx_compressed, + // read(path, "rx_compressed", data) + // ); + // old_and_new!( + // self, + // tx_compressed, + // old_tx_compressed, + // read(path, "tx_compressed", data) + // ); + } +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.rx_bytes.saturating_sub(self.old_rx_bytes) + } + + fn total_received(&self) -> u64 { + self.rx_bytes + } + + fn transmitted(&self) -> u64 { + self.tx_bytes.saturating_sub(self.old_tx_bytes) + } + + fn total_transmitted(&self) -> u64 { + self.tx_bytes + } + + fn packets_received(&self) -> u64 { + self.rx_packets.saturating_sub(self.old_rx_packets) + } + + fn total_packets_received(&self) -> u64 { + self.rx_packets + } + + fn packets_transmitted(&self) -> u64 { + self.tx_packets.saturating_sub(self.old_tx_packets) + } + + fn total_packets_transmitted(&self) -> u64 { + self.tx_packets + } + + fn errors_on_received(&self) -> u64 { + self.rx_errors.saturating_sub(self.old_rx_errors) + } + + fn total_errors_on_received(&self) -> u64 { + self.rx_errors + } + + fn errors_on_transmitted(&self) -> u64 { + self.tx_errors.saturating_sub(self.old_tx_errors) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.tx_errors + } +} + +#[cfg(test)] +mod test { + use super::refresh_networks_list_from_sysfs; + use std::collections::HashMap; + use std::fs; + + #[test] + fn refresh_networks_list_add_interface() { + let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory"); + + fs::create_dir(sys_net_dir.path().join("itf1")).expect("failed to create subdirectory"); + + let mut interfaces = HashMap::new(); + + refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); + assert_eq!(interfaces.keys().collect::<Vec<_>>(), ["itf1"]); + + fs::create_dir(sys_net_dir.path().join("itf2")).expect("failed to create subdirectory"); + + refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); + let mut itf_names: Vec<String> = interfaces.keys().map(|n| n.to_owned()).collect(); + itf_names.sort(); + assert_eq!(itf_names, ["itf1", "itf2"]); + } + + #[test] + fn refresh_networks_list_remove_interface() { + let sys_net_dir = tempfile::tempdir().expect("failed to create temporary directory"); + + let itf1_dir = sys_net_dir.path().join("itf1"); + let itf2_dir = sys_net_dir.path().join("itf2"); + fs::create_dir(&itf1_dir).expect("failed to create subdirectory"); + fs::create_dir(&itf2_dir).expect("failed to create subdirectory"); + + let mut interfaces = HashMap::new(); + + refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); + let mut itf_names: Vec<String> = interfaces.keys().map(|n| n.to_owned()).collect(); + itf_names.sort(); + assert_eq!(itf_names, ["itf1", "itf2"]); + + fs::remove_dir(&itf1_dir).expect("failed to remove subdirectory"); + + refresh_networks_list_from_sysfs(&mut interfaces, sys_net_dir.path()); + assert_eq!(interfaces.keys().collect::<Vec<_>>(), ["itf2"]); + } +} diff --git a/vendor/sysinfo/src/linux/process.rs b/vendor/sysinfo/src/linux/process.rs new file mode 100644 index 000000000..0b06e26f1 --- /dev/null +++ b/vendor/sysinfo/src/linux/process.rs @@ -0,0 +1,700 @@ +// 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, REMAINING_FILES}; +use crate::sys::utils::{get_all_data, get_all_data_from_file, realpath}; +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<File>, + old_read_bytes: u64, + old_written_bytes: u64, + read_bytes: u64, + written_bytes: u64, +} + +impl Process { + pub(crate) fn new( + pid: Pid, + parent: Option<Pid>, + start_time_without_boot_time: u64, + info: &SystemInfo, + ) -> Process { + Process { + name: String::with_capacity(20), + pid, + parent, + 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, + start_time: start_time_without_boot_time.saturating_add(info.boot_time), + 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 + } +} + +impl Drop for Process { + fn drop(&mut self) { + if self.stat_file.is_some() { + unsafe { + if let Ok(ref mut x) = crate::sys::system::REMAINING_FILES.lock() { + **x += 1; + } + } + } + } +} + +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 mut path = PathBuf::from(path); + path.push("io"); + let data = match get_all_data(&path, 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> {} + +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 get_status = |p: &mut Process, part: &str| { + p.status = part + .chars() + .next() + .map(ProcessStatus::from) + .unwrap_or_else(|| ProcessStatus::Unknown(0)); + }; + let parent_memory = proc_list.memory; + let parent_virtual_memory = proc_list.virtual_memory; + if let Some(ref mut entry) = proc_list.tasks.get_mut(&pid) { + let data = if let Some(ref mut f) = entry.stat_file { + get_all_data_from_file(f, 1024).map_err(|_| ())? + } else { + let mut tmp = PathBuf::from(path); + tmp.push("stat"); + let mut file = File::open(tmp).map_err(|_| ())?; + let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; + entry.stat_file = check_nb_open_files(file); + data + }; + let parts = parse_stat_file(&data)?; + 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() { + let mut tmp = PathBuf::from(path); + tmp.push("status"); + if let Some((user_id, group_id)) = get_uid_and_gid(&tmp) { + entry.user_id = Some(Uid(user_id)); + entry.group_id = Some(Gid(group_id)); + } + } + return Ok((None, pid)); + } + + let mut tmp = PathBuf::from(path); + + tmp.push("stat"); + let mut file = std::fs::File::open(&tmp).map_err(|_| ())?; + let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; + let stat_file = check_nb_open_files(file); + let parts = parse_stat_file(&data)?; + let name = parts[1]; + + let parent_pid = 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, + } + }; + + // To be noted that the start time is invalid here, it still needs + let start_time = u64::from_str(parts[21]).unwrap_or(0) / info.clock_cycle; + let mut p = Process::new(pid, parent_pid, start_time, info); + + p.stat_file = stat_file; + get_status(&mut p, parts[2]); + + if refresh_kind.user() { + tmp.pop(); + tmp.push("status"); + if let Some((user_id, group_id)) = get_uid_and_gid(&tmp) { + p.user_id = Some(Uid(user_id)); + p.group_id = Some(Gid(group_id)); + } + } + + 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(); + tmp.pop(); + tmp.push("cmdline"); + p.cmd = copy_from_file(&tmp); + tmp.pop(); + tmp.push("exe"); + match tmp.read_link() { + Ok(exe_path) => { + p.exe = exe_path; + } + Err(_) => { + p.exe = if let Some(cmd) = p.cmd.get(0) { + PathBuf::from(cmd) + } else { + PathBuf::new() + }; + } + } + tmp.pop(); + tmp.push("environ"); + p.environ = copy_from_file(&tmp); + tmp.pop(); + tmp.push("cwd"); + p.cwd = realpath(&tmp); + tmp.pop(); + tmp.push("root"); + p.root = realpath(&tmp); + } + + 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); + } + Ok((Some(p), 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. Divising by 1_000 gives us kb. + // see: https://man7.org/linux/man-pages/man5/proc.5.html + entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0) / 1_000; + 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 { + if let Ok(d) = fs::read_dir(path) { + let folders = d + .filter_map(|entry| { + if let Ok(entry) = entry { + let entry = entry.path(); + + if entry.is_dir() { + Some(entry) + } else { + None + } + } 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| { + if let Ok((p, _)) = _get_process_data( + e.as_path(), + proc_list.get(), + pid, + uptime, + info, + refresh_kind, + ) { + p + } else { + None + } + }) + .collect::<Vec<_>>() + } else { + let mut updated_pids = Vec::with_capacity(folders.len()); + let new_tasks = folders + .iter() + .filter_map(|e| { + if let Ok((p, pid)) = + _get_process_data(e.as_path(), proc_list, pid, uptime, info, refresh_kind) + { + updated_pids.push(pid); + p + } else { + None + } + }) + .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 + } else { + false + } +} + +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 check_nb_open_files(f: File) -> Option<File> { + unsafe { + if let Ok(ref mut x) = REMAINING_FILES.lock() { + if **x > 0 { + **x -= 1; + return Some(f); + } + } + } + // Something bad happened... + None +} + +macro_rules! unwrap_or_return { + ($data:expr) => {{ + match $data { + Some(x) => x, + None => return Err(()), + } + }}; +} + +fn parse_stat_file(data: &str) -> Result<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(unwrap_or_return!(data_it.next())); + let mut data_it = unwrap_or_return!(data_it.next()).rsplitn(2, ')'); + let data = unwrap_or_return!(data_it.next()); + parts.push(unwrap_or_return!(data_it.next())); + parts.extend(data.split_whitespace()); + // Remove command name '(' + if let Some(name) = parts[1].strip_prefix('(') { + parts[1] = name; + } + Ok(parts) +} diff --git a/vendor/sysinfo/src/linux/system.rs b/vendor/sysinfo/src/linux/system.rs new file mode 100644 index 000000000..7a8d0a010 --- /dev/null +++ b/vendor/sysinfo/src/linux/system.rs @@ -0,0 +1,852 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::component::{self, Component}; +use crate::sys::cpu::*; +use crate::sys::disk; +use crate::sys::process::*; +use crate::sys::utils::get_all_data; +use crate::{ + CpuRefreshKind, Disk, LoadAvg, Networks, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, +}; + +use libc::{self, c_char, c_int, sysconf, _SC_CLK_TCK, _SC_HOST_NAME_MAX, _SC_PAGESIZE}; +use std::collections::HashMap; +use std::fs::File; +use std::io::{BufRead, BufReader, Read}; +use std::path::Path; +use std::str::FromStr; +use std::sync::{Arc, Mutex}; + +// This whole thing is to prevent having too many files open at once. It could be problematic +// for processes using a lot of files and using sysinfo at the same time. +#[allow(clippy::mutex_atomic)] +pub(crate) static mut REMAINING_FILES: once_cell::sync::Lazy<Arc<Mutex<isize>>> = + once_cell::sync::Lazy::new(|| { + unsafe { + let mut limits = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 { + // Most linux system now defaults to 1024. + return Arc::new(Mutex::new(1024 / 2)); + } + // We save the value in case the update fails. + let current = limits.rlim_cur; + + // The set the soft limit to the hard one. + limits.rlim_cur = limits.rlim_max; + // In this part, we leave minimum 50% of the available file descriptors to the process + // using sysinfo. + Arc::new(Mutex::new( + if libc::setrlimit(libc::RLIMIT_NOFILE, &limits) == 0 { + limits.rlim_cur / 2 + } else { + current / 2 + } as _, + )) + } + }); + +pub(crate) fn get_max_nb_fds() -> isize { + unsafe { + let mut limits = libc::rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + if libc::getrlimit(libc::RLIMIT_NOFILE, &mut limits) != 0 { + // Most linux system now defaults to 1024. + 1024 / 2 + } else { + limits.rlim_max as isize / 2 + } + } +} + +macro_rules! to_str { + ($e:expr) => { + unsafe { std::str::from_utf8_unchecked($e) } + }; +} + +fn boot_time() -> u64 { + if let Ok(f) = File::open("/proc/stat") { + let buf = BufReader::new(f); + let line = buf + .split(b'\n') + .filter_map(|r| r.ok()) + .find(|l| l.starts_with(b"btime")); + + if let Some(line) = line { + return line + .split(|x| *x == b' ') + .filter(|s| !s.is_empty()) + .nth(1) + .map(to_u64) + .unwrap_or(0); + } + } + // Either we didn't find "btime" or "/proc/stat" wasn't available for some reason... + let mut up = libc::timespec { + tv_sec: 0, + tv_nsec: 0, + }; + unsafe { + if libc::clock_gettime(libc::CLOCK_BOOTTIME, &mut up) == 0 { + up.tv_sec as u64 + } else { + sysinfo_debug!("clock_gettime failed: boot time cannot be retrieve..."); + 0 + } + } +} + +pub(crate) struct SystemInfo { + pub(crate) page_size_kb: u64, + pub(crate) clock_cycle: u64, + pub(crate) boot_time: u64, +} + +impl SystemInfo { + fn new() -> Self { + unsafe { + Self { + page_size_kb: (sysconf(_SC_PAGESIZE) / 1024) as _, + clock_cycle: sysconf(_SC_CLK_TCK) as _, + boot_time: boot_time(), + } + } + } +} + +declare_signals! { + c_int, + Signal::Hangup => libc::SIGHUP, + Signal::Interrupt => libc::SIGINT, + Signal::Quit => libc::SIGQUIT, + Signal::Illegal => libc::SIGILL, + Signal::Trap => libc::SIGTRAP, + Signal::Abort => libc::SIGABRT, + Signal::IOT => libc::SIGIOT, + Signal::Bus => libc::SIGBUS, + Signal::FloatingPointException => libc::SIGFPE, + Signal::Kill => libc::SIGKILL, + Signal::User1 => libc::SIGUSR1, + Signal::Segv => libc::SIGSEGV, + Signal::User2 => libc::SIGUSR2, + Signal::Pipe => libc::SIGPIPE, + Signal::Alarm => libc::SIGALRM, + Signal::Term => libc::SIGTERM, + Signal::Child => libc::SIGCHLD, + Signal::Continue => libc::SIGCONT, + Signal::Stop => libc::SIGSTOP, + Signal::TSTP => libc::SIGTSTP, + Signal::TTIN => libc::SIGTTIN, + Signal::TTOU => libc::SIGTTOU, + Signal::Urgent => libc::SIGURG, + Signal::XCPU => libc::SIGXCPU, + Signal::XFSZ => libc::SIGXFSZ, + Signal::VirtualAlarm => libc::SIGVTALRM, + Signal::Profiling => libc::SIGPROF, + Signal::Winch => libc::SIGWINCH, + Signal::IO => libc::SIGIO, + Signal::Poll => libc::SIGPOLL, + Signal::Power => libc::SIGPWR, + Signal::Sys => libc::SIGSYS, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: Process, + mem_total: u64, + mem_free: u64, + mem_available: u64, + mem_buffers: u64, + mem_page_cache: u64, + mem_slab_reclaimable: u64, + swap_total: u64, + swap_free: u64, + global_cpu: Cpu, + cpus: Vec<Cpu>, + components: Vec<Component>, + disks: Vec<Disk>, + networks: Networks, + users: Vec<User>, + /// Field set to `false` in `update_cpus` and to `true` in `refresh_processes_specifics`. + /// + /// The reason behind this is to avoid calling the `update_cpus` more than necessary. + /// For example when running `refresh_all` or `refresh_specifics`. + need_cpus_update: bool, + info: SystemInfo, + got_cpu_frequency: bool, +} + +impl System { + /// It is sometime possible that a CPU usage computation is bigger than + /// `"number of CPUs" * 100`. + /// + /// To prevent that, we compute ahead of time this maximum value and ensure that processes' + /// CPU usage don't go over it. + fn get_max_process_cpu_usage(&self) -> f32 { + self.cpus.len() as f32 * 100. + } + + fn clear_procs(&mut self, refresh_kind: ProcessRefreshKind) { + let (total_time, compute_cpu, max_value) = if refresh_kind.cpu() { + if self.need_cpus_update { + self.refresh_cpus(true, CpuRefreshKind::new().with_cpu_usage()); + } + + if self.cpus.is_empty() { + sysinfo_debug!("cannot compute processes CPU usage: no CPU found..."); + (0., false, 0.) + } else { + let (new, old) = get_raw_times(&self.global_cpu); + let total_time = if old > new { 1 } else { new - old }; + ( + total_time as f32 / self.cpus.len() as f32, + true, + self.get_max_process_cpu_usage(), + ) + } + } else { + (0., false, 0.) + }; + + self.process_list.tasks.retain(|_, proc_| { + if !proc_.updated { + return false; + } + if compute_cpu { + compute_cpu_usage(proc_, total_time, max_value); + } + proc_.updated = false; + true + }); + } + + fn refresh_cpus(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) { + if let Ok(f) = File::open("/proc/stat") { + self.need_cpus_update = false; + + let buf = BufReader::new(f); + let mut i: usize = 0; + let first = self.cpus.is_empty(); + let mut it = buf.split(b'\n'); + let (vendor_id, brand) = if first { + get_vendor_id_and_brand() + } else { + (String::new(), String::new()) + }; + + if first || refresh_kind.cpu_usage() { + if let Some(Ok(line)) = it.next() { + if &line[..4] != b"cpu " { + return; + } + let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); + if first { + self.global_cpu.name = to_str!(parts.next().unwrap_or(&[])).to_owned(); + } else { + parts.next(); + } + self.global_cpu.set( + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + ); + } + if first || !only_update_global_cpu { + while let Some(Ok(line)) = it.next() { + if &line[..3] != b"cpu" { + break; + } + + let mut parts = line.split(|x| *x == b' ').filter(|s| !s.is_empty()); + if first { + self.cpus.push(Cpu::new_with_values( + to_str!(parts.next().unwrap_or(&[])), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + 0, + vendor_id.clone(), + brand.clone(), + )); + } else { + parts.next(); // we don't want the name again + self.cpus[i].set( + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + parts.next().map(to_u64).unwrap_or(0), + ); + } + + i += 1; + } + } + } + + if refresh_kind.frequency() && !self.got_cpu_frequency { + #[cfg(feature = "multithread")] + use rayon::iter::{ + IndexedParallelIterator, IntoParallelRefMutIterator, ParallelIterator, + }; + + #[cfg(feature = "multithread")] + // This function is voluntarily made generic in case we want to generalize it. + fn iter_mut<'a, T>( + val: &'a mut T, + ) -> <&'a mut T as rayon::iter::IntoParallelIterator>::Iter + where + &'a mut T: rayon::iter::IntoParallelIterator, + { + val.par_iter_mut() + } + + #[cfg(not(feature = "multithread"))] + fn iter_mut<'a>(val: &'a mut Vec<Cpu>) -> std::slice::IterMut<'a, Cpu> { + val.iter_mut() + } + + // `get_cpu_frequency` is very slow, so better run it in parallel. + self.global_cpu.frequency = iter_mut(&mut self.cpus) + .enumerate() + .map(|(pos, proc_)| { + proc_.frequency = get_cpu_frequency(pos); + proc_.frequency + }) + .max() + .unwrap_or(0); + + self.got_cpu_frequency = true; + } + + if first { + self.global_cpu.vendor_id = vendor_id; + self.global_cpu.brand = brand; + } + } + } +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + fn new_with_specifics(refreshes: RefreshKind) -> System { + let info = SystemInfo::new(); + let process_list = Process::new(Pid(0), None, 0, &info); + let mut s = System { + process_list, + mem_total: 0, + mem_free: 0, + mem_available: 0, + mem_buffers: 0, + mem_page_cache: 0, + mem_slab_reclaimable: 0, + swap_total: 0, + swap_free: 0, + global_cpu: Cpu::new_with_values( + "", + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + String::new(), + String::new(), + ), + cpus: Vec::with_capacity(4), + components: Vec::new(), + disks: Vec::with_capacity(2), + networks: Networks::new(), + users: Vec::new(), + need_cpus_update: true, + info, + got_cpu_frequency: false, + }; + s.refresh_specifics(refreshes); + s + } + + fn refresh_components_list(&mut self) { + self.components = component::get_components(); + } + + fn refresh_memory(&mut self) { + if let Ok(data) = get_all_data("/proc/meminfo", 16_385) { + for line in data.split('\n') { + let mut iter = line.split(':'); + let field = match iter.next() { + Some("MemTotal") => &mut self.mem_total, + Some("MemFree") => &mut self.mem_free, + Some("MemAvailable") => &mut self.mem_available, + Some("Buffers") => &mut self.mem_buffers, + Some("Cached") => &mut self.mem_page_cache, + Some("SReclaimable") => &mut self.mem_slab_reclaimable, + Some("SwapTotal") => &mut self.swap_total, + Some("SwapFree") => &mut self.swap_free, + _ => continue, + }; + if let Some(val_str) = iter.next().and_then(|s| s.trim_start().split(' ').next()) { + if let Ok(value) = u64::from_str(val_str) { + // /proc/meminfo reports KiB, though it says "kB". Convert it. + *field = value.saturating_mul(128) / 125; + } + } + } + } + } + + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { + self.refresh_cpus(false, refresh_kind); + } + + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { + let uptime = self.uptime(); + refresh_procs( + &mut self.process_list, + Path::new("/proc"), + Pid(0), + uptime, + &self.info, + refresh_kind, + ); + self.clear_procs(refresh_kind); + self.need_cpus_update = true; + } + + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + let uptime = self.uptime(); + let found = match _get_process_data( + &Path::new("/proc/").join(pid.to_string()), + &mut self.process_list, + Pid(0), + uptime, + &self.info, + refresh_kind, + ) { + Ok((Some(p), pid)) => { + self.process_list.tasks.insert(pid, p); + true + } + Ok(_) => true, + Err(_) => false, + }; + if found { + if refresh_kind.cpu() { + self.refresh_cpus(true, CpuRefreshKind::new().with_cpu_usage()); + + if self.cpus.is_empty() { + sysinfo_debug!("Cannot compute process CPU usage: no cpus found..."); + return found; + } + let (new, old) = get_raw_times(&self.global_cpu); + let total_time = (if old >= new { 1 } else { new - old }) as f32; + + let max_cpu_usage = self.get_max_process_cpu_usage(); + if let Some(p) = self.process_list.tasks.get_mut(&pid) { + compute_cpu_usage(p, total_time / self.cpus.len() as f32, max_cpu_usage); + p.updated = false; + } + } else if let Some(p) = self.process_list.tasks.get_mut(&pid) { + p.updated = false; + } + } + found + } + + fn refresh_disks_list(&mut self) { + self.disks = disk::get_all_disks(); + } + + fn refresh_users_list(&mut self) { + self.users = crate::users::get_users_list(); + } + + // COMMON PART + // + // Need to be moved into a "common" file to avoid duplication. + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list.tasks + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.tasks.get(&pid) + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn global_cpu_info(&self) -> &Cpu { + &self.global_cpu + } + + fn cpus(&self) -> &[Cpu] { + &self.cpus + } + + fn physical_core_count(&self) -> Option<usize> { + get_physical_core_count() + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + self.mem_free + } + + fn available_memory(&self) -> u64 { + self.mem_available + } + + fn used_memory(&self) -> u64 { + self.mem_total + - self.mem_free + - self.mem_buffers + - self.mem_page_cache + - self.mem_slab_reclaimable + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_free + } + + // need to be checked + fn used_swap(&self) -> u64 { + self.swap_total - self.swap_free + } + + fn components(&self) -> &[Component] { + &self.components + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut self.components + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks + } + + fn uptime(&self) -> u64 { + let content = get_all_data("/proc/uptime", 50).unwrap_or_default(); + content + .split('.') + .next() + .and_then(|t| t.parse().ok()) + .unwrap_or_default() + } + + fn boot_time(&self) -> u64 { + self.info.boot_time + } + + fn load_average(&self) -> LoadAvg { + let mut s = String::new(); + if File::open("/proc/loadavg") + .and_then(|mut f| f.read_to_string(&mut s)) + .is_err() + { + return LoadAvg::default(); + } + let loads = s + .trim() + .split(' ') + .take(3) + .map(|val| val.parse::<f64>().unwrap()) + .collect::<Vec<f64>>(); + LoadAvg { + one: loads[0], + five: loads[1], + fifteen: loads[2], + } + } + + fn users(&self) -> &[User] { + &self.users + } + + #[cfg(not(target_os = "android"))] + fn name(&self) -> Option<String> { + get_system_info_linux( + InfoType::Name, + Path::new("/etc/os-release"), + Path::new("/etc/lsb-release"), + ) + } + + #[cfg(target_os = "android")] + fn name(&self) -> Option<String> { + get_system_info_android(InfoType::Name) + } + + fn long_os_version(&self) -> Option<String> { + #[cfg(target_os = "android")] + let system_name = "Android"; + + #[cfg(not(target_os = "android"))] + let system_name = "Linux"; + + Some(format!( + "{} {} {}", + system_name, + self.os_version().unwrap_or_default(), + self.name().unwrap_or_default() + )) + } + + fn host_name(&self) -> Option<String> { + unsafe { + let hostname_max = sysconf(_SC_HOST_NAME_MAX); + let mut buffer = vec![0_u8; hostname_max as usize]; + if libc::gethostname(buffer.as_mut_ptr() as *mut c_char, buffer.len()) == 0 { + if let Some(pos) = buffer.iter().position(|x| *x == 0) { + // Shrink buffer to terminate the null bytes + buffer.resize(pos, 0); + } + String::from_utf8(buffer).ok() + } else { + sysinfo_debug!("gethostname failed: hostname cannot be retrieved..."); + None + } + } + } + + fn kernel_version(&self) -> Option<String> { + let mut raw = std::mem::MaybeUninit::<libc::utsname>::zeroed(); + + unsafe { + if libc::uname(raw.as_mut_ptr()) == 0 { + let info = raw.assume_init(); + + let release = info + .release + .iter() + .filter(|c| **c != 0) + .map(|c| *c as u8 as char) + .collect::<String>(); + + Some(release) + } else { + None + } + } + } + + #[cfg(not(target_os = "android"))] + fn os_version(&self) -> Option<String> { + get_system_info_linux( + InfoType::OsVersion, + Path::new("/etc/os-release"), + Path::new("/etc/lsb-release"), + ) + } + + #[cfg(target_os = "android")] + fn os_version(&self) -> Option<String> { + get_system_info_android(InfoType::OsVersion) + } +} + +impl Default for System { + fn default() -> System { + System::new() + } +} + +fn to_u64(v: &[u8]) -> u64 { + let mut x = 0; + + for c in v { + x *= 10; + x += u64::from(c - b'0'); + } + x +} + +#[derive(PartialEq, Eq)] +enum InfoType { + /// The end-user friendly name of: + /// - Android: The device model + /// - Linux: The distributions name + Name, + OsVersion, +} + +#[cfg(not(target_os = "android"))] +fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> Option<String> { + if let Ok(f) = File::open(path) { + let reader = BufReader::new(f); + + let info_str = match info { + InfoType::Name => "NAME=", + InfoType::OsVersion => "VERSION_ID=", + }; + + for line in reader.lines().flatten() { + if let Some(stripped) = line.strip_prefix(info_str) { + return Some(stripped.replace('"', "")); + } + } + } + + // Fallback to `/etc/lsb-release` file for systems where VERSION_ID is not included. + // VERSION_ID is not required in the `/etc/os-release` file + // per https://www.linux.org/docs/man5/os-release.html + // If this fails for some reason, fallback to None + let reader = BufReader::new(File::open(fallback_path).ok()?); + + let info_str = match info { + InfoType::OsVersion => "DISTRIB_RELEASE=", + InfoType::Name => "DISTRIB_ID=", + }; + for line in reader.lines().flatten() { + if let Some(stripped) = line.strip_prefix(info_str) { + return Some(stripped.replace('"', "")); + } + } + None +} + +#[cfg(target_os = "android")] +fn get_system_info_android(info: InfoType) -> Option<String> { + // https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/os/Build.java#58 + let name: &'static [u8] = match info { + InfoType::Name => b"ro.product.model\0", + InfoType::OsVersion => b"ro.build.version.release\0", + }; + + let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize]; + unsafe { + let len = libc::__system_property_get( + name.as_ptr() as *const c_char, + value_buffer.as_mut_ptr() as *mut c_char, + ); + + if len != 0 { + if let Some(pos) = value_buffer.iter().position(|c| *c == 0) { + value_buffer.resize(pos, 0); + } + String::from_utf8(value_buffer).ok() + } else { + None + } + } +} + +#[cfg(test)] +mod test { + #[cfg(target_os = "android")] + use super::get_system_info_android; + #[cfg(not(target_os = "android"))] + use super::get_system_info_linux; + use super::InfoType; + + #[test] + #[cfg(target_os = "android")] + fn lsb_release_fallback_android() { + assert!(get_system_info_android(InfoType::OsVersion).is_some()); + assert!(get_system_info_android(InfoType::Name).is_some()); + } + + #[test] + #[cfg(not(target_os = "android"))] + fn lsb_release_fallback_not_android() { + use std::path::Path; + + let dir = tempfile::tempdir().expect("failed to create temporary directory"); + let tmp1 = dir.path().join("tmp1"); + let tmp2 = dir.path().join("tmp2"); + + // /etc/os-release + std::fs::write( + &tmp1, + r#"NAME="Ubuntu" +VERSION="20.10 (Groovy Gorilla)" +ID=ubuntu +ID_LIKE=debian +PRETTY_NAME="Ubuntu 20.10" +VERSION_ID="20.10" +VERSION_CODENAME=groovy +UBUNTU_CODENAME=groovy +"#, + ) + .expect("Failed to create tmp1"); + + // /etc/lsb-release + std::fs::write( + &tmp2, + r#"DISTRIB_ID=Ubuntu +DISTRIB_RELEASE=20.10 +DISTRIB_CODENAME=groovy +DISTRIB_DESCRIPTION="Ubuntu 20.10" +"#, + ) + .expect("Failed to create tmp2"); + + // Check for the "normal" path: "/etc/os-release" + assert_eq!( + get_system_info_linux(InfoType::OsVersion, &tmp1, Path::new("")), + Some("20.10".to_owned()) + ); + assert_eq!( + get_system_info_linux(InfoType::Name, &tmp1, Path::new("")), + Some("Ubuntu".to_owned()) + ); + + // Check for the "fallback" path: "/etc/lsb-release" + assert_eq!( + get_system_info_linux(InfoType::OsVersion, Path::new(""), &tmp2), + Some("20.10".to_owned()) + ); + assert_eq!( + get_system_info_linux(InfoType::Name, Path::new(""), &tmp2), + Some("Ubuntu".to_owned()) + ); + } +} diff --git a/vendor/sysinfo/src/linux/utils.rs b/vendor/sysinfo/src/linux/utils.rs new file mode 100644 index 000000000..cd881a3d1 --- /dev/null +++ b/vendor/sysinfo/src/linux/utils.rs @@ -0,0 +1,55 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::fs::File; +use std::io::{self, Read, Seek, SeekFrom}; +use std::path::Path; + +pub(crate) fn get_all_data_from_file(file: &mut File, size: usize) -> io::Result<String> { + let mut buf = String::with_capacity(size); + file.seek(SeekFrom::Start(0))?; + file.read_to_string(&mut buf)?; + Ok(buf) +} + +pub(crate) fn get_all_data<P: AsRef<Path>>(file_path: P, size: usize) -> io::Result<String> { + let mut file = File::open(file_path.as_ref())?; + get_all_data_from_file(&mut file, size) +} + +#[allow(clippy::useless_conversion)] +pub(crate) fn realpath(original: &Path) -> std::path::PathBuf { + use libc::{lstat, stat, S_IFLNK, S_IFMT}; + use std::fs; + use std::mem::MaybeUninit; + use std::path::PathBuf; + + fn and(x: u32, y: u32) -> u32 { + x & y + } + + // let ori = Path::new(original.to_str().unwrap()); + // Right now lstat on windows doesn't work quite well + // if cfg!(windows) { + // return PathBuf::from(ori); + // } + let result = PathBuf::from(original); + let mut result_s = result.to_str().unwrap_or("").as_bytes().to_vec(); + result_s.push(0); + let mut buf = MaybeUninit::<stat>::uninit(); + unsafe { + let res = lstat(result_s.as_ptr() as *const _, buf.as_mut_ptr()); + if res < 0 { + PathBuf::new() + } else { + let buf = buf.assume_init(); + if and(buf.st_mode.into(), S_IFMT.into()) != S_IFLNK.into() { + PathBuf::new() + } else { + match fs::read_link(&result) { + Ok(f) => f, + Err(_) => PathBuf::new(), + } + } + } + } +} diff --git a/vendor/sysinfo/src/macros.rs b/vendor/sysinfo/src/macros.rs new file mode 100644 index 000000000..3bb4defa1 --- /dev/null +++ b/vendor/sysinfo/src/macros.rs @@ -0,0 +1,58 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#[cfg(feature = "debug")] +#[doc(hidden)] +#[allow(unused)] +macro_rules! sysinfo_debug { + ($($x:tt)*) => {{ + eprintln!($($x)*); + }} +} + +#[cfg(not(feature = "debug"))] +#[doc(hidden)] +#[allow(unused)] +macro_rules! sysinfo_debug { + ($($x:tt)*) => {{}}; +} + +macro_rules! declare_signals { + ($kind:ty, _ => None,) => ( + use crate::Signal; + + pub(crate) const fn supported_signals() -> &'static [Signal] { + &[] + } + ); + + ($kind:ty, $(Signal::$signal:ident => $map:expr,)+ _ => None,) => ( + use crate::Signal; + + pub(crate) const fn supported_signals() -> &'static [Signal] { + &[$(Signal::$signal,)*] + } + + #[inline] + pub(crate) fn convert_signal(s: Signal) -> Option<$kind> { + match s { + $(Signal::$signal => Some($map),)* + _ => None, + } + } + ); + + ($kind:ty, $(Signal::$signal:ident => $map:expr,)+) => ( + use crate::Signal; + + pub(crate) const fn supported_signals() -> &'static [Signal] { + &[$(Signal::$signal,)*] + } + + #[inline] + pub(crate) fn convert_signal(s: Signal) -> Option<$kind> { + match s { + $(Signal::$signal => Some($map),)* + } + } + ) +} diff --git a/vendor/sysinfo/src/sysinfo.h b/vendor/sysinfo/src/sysinfo.h new file mode 100644 index 000000000..13c039128 --- /dev/null +++ b/vendor/sysinfo/src/sysinfo.h @@ -0,0 +1,45 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#pragma once + +#include <sys/types.h> +#include <stdbool.h> + +typedef void* CSystem; +typedef const void* CProcess; +typedef const char* RString; + +CSystem *sysinfo_init(); +void sysinfo_destroy(CSystem system); +void sysinfo_refresh_system(CSystem system); +void sysinfo_refresh_all(CSystem system); +void sysinfo_refresh_processes(CSystem system); +#ifdef __linux__ +void sysinfo_refresh_process(CSystem system, pid_t pid); +#endif +void sysinfo_refresh_disks(CSystem system); +void sysinfo_refresh_disk_list(CSystem system); +size_t sysinfo_get_total_memory(CSystem system); +size_t sysinfo_get_free_memory(CSystem system); +size_t sysinfo_get_used_memory(CSystem system); +size_t sysinfo_get_total_swap(CSystem system); +size_t sysinfo_get_free_swap(CSystem system); +size_t sysinfo_get_used_swap(CSystem system); +size_t sysinfo_get_network_income(CSystem system); +size_t sysinfo_get_network_outcome(CSystem system); +void sysinfo_get_cpus_usage(CSystem system, unsigned int *length, float **cpus); +size_t sysinfo_get_processes(CSystem system, bool (*fn_pointer)(pid_t, CProcess, void*), + void *data); +#ifdef __linux__ +size_t sysinfo_process_get_tasks(CProcess process, bool (*fn_pointer)(pid_t, CProcess, void*), + void *data); +#endif +CProcess sysinfo_get_process_by_pid(CSystem system, pid_t pid); +pid_t sysinfo_process_get_pid(CProcess process); +pid_t sysinfo_process_get_parent_pid(CProcess process); +float sysinfo_process_get_cpu_usage(CProcess process); +size_t sysinfo_process_get_memory(CProcess process); +RString sysinfo_process_get_executable_path(CProcess process); +RString sysinfo_process_get_root_directory(CProcess process); +RString sysinfo_process_get_current_directory(CProcess process); +void sysinfo_rstring_free(RString str); diff --git a/vendor/sysinfo/src/system.rs b/vendor/sysinfo/src/system.rs new file mode 100644 index 000000000..63a70651f --- /dev/null +++ b/vendor/sysinfo/src/system.rs @@ -0,0 +1,117 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +// Once https://github.com/rust-lang/rfcs/blob/master/text/1422-pub-restricted.md +// feature gets stabilized, we can move common parts in here. + +#[cfg(test)] +mod tests { + use crate::{ProcessExt, System, SystemExt}; + + #[test] + fn test_refresh_system() { + let mut sys = System::new(); + sys.refresh_system(); + // We don't want to test on unsupported systems. + if System::IS_SUPPORTED { + assert!(sys.total_memory() != 0); + assert!(sys.free_memory() != 0); + } + assert!(sys.total_memory() >= sys.free_memory()); + assert!(sys.total_swap() >= sys.free_swap()); + } + + #[test] + fn test_refresh_process() { + let mut sys = System::new(); + assert!(sys.processes().is_empty(), "no process should be listed!"); + // We don't want to test on unsupported systems. + + #[cfg(not(feature = "apple-sandbox"))] + if System::IS_SUPPORTED { + assert!( + sys.refresh_process(crate::get_current_pid().expect("failed to get current pid")), + "process not listed", + ); + // Ensure that the process was really added to the list! + assert!(sys + .process(crate::get_current_pid().expect("failed to get current pid")) + .is_some()); + } + } + + #[test] + fn test_get_process() { + let mut sys = System::new(); + sys.refresh_processes(); + let current_pid = match crate::get_current_pid() { + Ok(pid) => pid, + _ => { + if !System::IS_SUPPORTED { + return; + } + panic!("get_current_pid should work!"); + } + }; + if let Some(p) = sys.process(current_pid) { + assert!(p.memory() > 0); + } else { + #[cfg(not(feature = "apple-sandbox"))] + assert!(!System::IS_SUPPORTED); + } + } + + #[test] + fn check_if_send_and_sync() { + trait Foo { + fn foo(&self) {} + } + impl<T> Foo for T where T: Send {} + + trait Bar { + fn bar(&self) {} + } + + impl<T> Bar for T where T: Sync {} + + let mut sys = System::new(); + sys.refresh_processes(); + let current_pid = match crate::get_current_pid() { + Ok(pid) => pid, + _ => { + if !System::IS_SUPPORTED { + return; + } + panic!("get_current_pid should work!"); + } + }; + if let Some(p) = sys.process(current_pid) { + p.foo(); // If this doesn't compile, it'll simply mean that the Process type + // doesn't implement the Send trait. + p.bar(); // If this doesn't compile, it'll simply mean that the Process type + // doesn't implement the Sync trait. + } else { + #[cfg(not(feature = "apple-sandbox"))] + assert!(!System::IS_SUPPORTED); + } + } + + #[test] + fn check_hostname_has_no_nuls() { + let sys = System::new(); + + if let Some(hostname) = sys.host_name() { + assert!(!hostname.contains('\u{0}')) + } + } + + #[test] + fn check_uptime() { + let sys = System::new(); + let uptime = sys.uptime(); + if System::IS_SUPPORTED { + std::thread::sleep(std::time::Duration::from_millis(1000)); + let new_uptime = sys.uptime(); + assert!(uptime < new_uptime); + } + } +} diff --git a/vendor/sysinfo/src/traits.rs b/vendor/sysinfo/src/traits.rs new file mode 100644 index 000000000..3d5eafaa8 --- /dev/null +++ b/vendor/sysinfo/src/traits.rs @@ -0,0 +1,1582 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + common::{Gid, Uid}, + sys::{Component, Cpu, Disk, Networks, Process}, +}; +use crate::{ + CpuRefreshKind, DiskType, DiskUsage, LoadAvg, NetworksIter, Pid, ProcessRefreshKind, + ProcessStatus, RefreshKind, Signal, User, +}; + +use std::collections::HashMap; +use std::ffi::OsStr; +use std::fmt::Debug; +use std::path::Path; + +/// Contains all the methods of the [`Disk`][crate::Disk] struct. +/// +/// ```no_run +/// use sysinfo::{DiskExt, System, SystemExt}; +/// +/// let s = System::new(); +/// for disk in s.disks() { +/// println!("{:?}: {:?}", disk.name(), disk.type_()); +/// } +/// ``` +pub trait DiskExt: Debug { + /// Returns the disk type. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{:?}", disk.type_()); + /// } + /// ``` + fn type_(&self) -> DiskType; + + /// Returns the disk name. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{:?}", disk.name()); + /// } + /// ``` + fn name(&self) -> &OsStr; + + /// Returns the file system used on this disk (so for example: `EXT4`, `NTFS`, etc...). + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{:?}", disk.file_system()); + /// } + /// ``` + fn file_system(&self) -> &[u8]; + + /// Returns the mount point of the disk (`/` for example). + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{:?}", disk.mount_point()); + /// } + /// ``` + fn mount_point(&self) -> &Path; + + /// Returns the total disk size, in bytes. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{}", disk.total_space()); + /// } + /// ``` + fn total_space(&self) -> u64; + + /// Returns the available disk size, in bytes. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{}", disk.available_space()); + /// } + /// ``` + fn available_space(&self) -> u64; + + /// Returns `true` if the disk is removable. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for disk in s.disks() { + /// println!("{}", disk.is_removable()); + /// } + /// ``` + fn is_removable(&self) -> bool; + + /// Updates the disk' information. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for disk in s.disks_mut() { + /// disk.refresh(); + /// } + /// ``` + fn refresh(&mut self) -> bool; +} + +/// Contains all the methods of the [`Process`][crate::Process] struct. +pub trait ProcessExt: Debug { + /// Sends [`Signal::Kill`] to the process (which is the only signal supported on all supported + /// platforms by this crate). + /// + /// If you want to send another signal, take a look at [`ProcessExt::kill_with`]. + /// + /// To get the list of the supported signals on this system, use + /// [`SystemExt::SUPPORTED_SIGNALS`]. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// process.kill(); + /// } + /// ``` + fn kill(&self) -> bool { + self.kill_with(Signal::Kill).unwrap_or(false) + } + + /// Sends the given `signal` to the process. If the signal doesn't exist on this platform, + /// it'll do nothing and will return `None`. Otherwise it'll return if the signal was sent + /// successfully. + /// + /// If you just want to kill the process, use [`ProcessExt::kill`] directly. + /// + /// To get the list of the supported signals on this system, use + /// [`SystemExt::SUPPORTED_SIGNALS`]. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, Signal, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// if process.kill_with(Signal::Kill).is_none() { + /// eprintln!("This signal isn't supported on this platform"); + /// } + /// } + /// ``` + fn kill_with(&self, signal: Signal) -> Option<bool>; + + /// Returns the name of the process. + /// + /// **⚠️ Important ⚠️** + /// + /// On **linux**, there are two things to know about processes' name: + /// 1. It is limited to 15 characters. + /// 2. It is not always the exe name. + /// + /// If you are looking for a specific process, unless you know what you are doing, in most + /// cases it's better to use [`ProcessExt::exe`] instead (which can be empty sometimes!). + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.name()); + /// } + /// ``` + fn name(&self) -> &str; + + /// Returns the command line. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{:?}", process.cmd()); + /// } + /// ``` + fn cmd(&self) -> &[String]; + + /// Returns the path to the process. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.exe().display()); + /// } + /// ``` + fn exe(&self) -> &Path; + + /// Returns the pid of the process. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.pid()); + /// } + /// ``` + fn pid(&self) -> Pid; + + /// Returns the environment variables of the process. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{:?}", process.environ()); + /// } + /// ``` + fn environ(&self) -> &[String]; + + /// Returns the current working directory. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.cwd().display()); + /// } + /// ``` + fn cwd(&self) -> &Path; + + /// Returns the path of the root directory. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.root().display()); + /// } + /// ``` + fn root(&self) -> &Path; + + /// Returns the memory usage (in KB). + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{} KB", process.memory()); + /// } + /// ``` + fn memory(&self) -> u64; + + /// Returns the virtual memory usage (in KB). + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{} KB", process.virtual_memory()); + /// } + /// ``` + fn virtual_memory(&self) -> u64; + + /// Returns the parent pid. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{:?}", process.parent()); + /// } + /// ``` + fn parent(&self) -> Option<Pid>; + + /// Returns the status of the processus. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{:?}", process.status()); + /// } + /// ``` + fn status(&self) -> ProcessStatus; + + /// Returns the time where the process was started (in seconds) from epoch. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("Started at {} seconds", process.start_time()); + /// } + /// ``` + fn start_time(&self) -> u64; + + /// Returns for how much time the process has been running (in seconds). + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("Running since {} seconds", process.run_time()); + /// } + /// ``` + fn run_time(&self) -> u64; + + /// Returns the total CPU usage (in %). Notice that it might be bigger than 100 if run on a + /// multicore machine. + /// + /// If you want a value between 0% and 100%, divide the returned value by the number of CPU + /// CPUs. + /// + /// **Warning**: If you want accurate CPU usage number, better leave a bit of time + /// between two calls of this method (200 ms for example). + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}%", process.cpu_usage()); + /// } + /// ``` + fn cpu_usage(&self) -> f32; + + /// Returns number of bytes read and written to disk. + /// + /// ⚠️ On Windows and FreeBSD, this method actually returns **ALL** I/O read and written bytes. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// let disk_usage = process.disk_usage(); + /// println!("read bytes : new/total => {}/{}", + /// disk_usage.read_bytes, + /// disk_usage.total_read_bytes, + /// ); + /// println!("written bytes: new/total => {}/{}", + /// disk_usage.written_bytes, + /// disk_usage.total_written_bytes, + /// ); + /// } + /// ``` + fn disk_usage(&self) -> DiskUsage; + + /// Returns the ID of the owner user of this process or `None` if this information couldn't + /// be retrieved. If you want to get the [`User`] from it, take a look at + /// [`SystemExt::get_user_by_id`]. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// + /// if let Some(process) = s.process(Pid::from(1337)) { + /// eprintln!("User id for process 1337: {:?}", process.user_id()); + /// } + /// ``` + fn user_id(&self) -> Option<&Uid>; + + /// Returns the process group ID of the process. + /// + /// ⚠️ It always returns `None` on Windows. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// + /// if let Some(process) = s.process(Pid::from(1337)) { + /// eprintln!("Group id for process 1337: {:?}", process.group_id()); + /// } + /// ``` + fn group_id(&self) -> Option<Gid>; +} + +/// Contains all the methods of the [`Cpu`][crate::Cpu] struct. +pub trait CpuExt: Debug { + /// Returns this CPU's usage. + /// + /// Note: You'll need to refresh it at least twice (diff between the first and the second is + /// how CPU usage is computed) at first if you want to have a non-zero value. + /// + /// ```no_run + /// use sysinfo::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for cpu in s.cpus() { + /// println!("{}%", cpu.cpu_usage()); + /// } + /// ``` + fn cpu_usage(&self) -> f32; + + /// Returns this CPU's name. + /// + /// ```no_run + /// use sysinfo::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for cpu in s.cpus() { + /// println!("{}", cpu.name()); + /// } + /// ``` + fn name(&self) -> &str; + + /// Returns the CPU's vendor id. + /// + /// ```no_run + /// use sysinfo::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for cpu in s.cpus() { + /// println!("{}", cpu.vendor_id()); + /// } + /// ``` + fn vendor_id(&self) -> &str; + + /// Returns the CPU's brand. + /// + /// ```no_run + /// use sysinfo::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for cpu in s.cpus() { + /// println!("{}", cpu.brand()); + /// } + /// ``` + fn brand(&self) -> &str; + + /// Returns the CPU's frequency. + /// + /// ```no_run + /// use sysinfo::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// for cpu in s.cpus() { + /// println!("{}", cpu.frequency()); + /// } + /// ``` + fn frequency(&self) -> u64; +} + +/// Contains all the methods of the [`System`][crate::System] type. +pub trait SystemExt: Sized + Debug + Default + Send + Sync { + /// Returns `true` if this OS is supported. Please refer to the + /// [crate-level documentation](index.html) to get the list of supported OSes. + /// + /// ``` + /// use sysinfo::{System, SystemExt}; + /// + /// if System::IS_SUPPORTED { + /// println!("This OS is supported!"); + /// } else { + /// println!("This OS isn't supported (yet?)."); + /// } + /// ``` + const IS_SUPPORTED: bool; + + /// Returns the list of the supported signals on this system (used by + /// [`ProcessExt::kill_with`]). + /// + /// ``` + /// use sysinfo::{System, SystemExt}; + /// + /// println!("supported signals: {:?}", System::SUPPORTED_SIGNALS); + /// ``` + const SUPPORTED_SIGNALS: &'static [Signal]; + + /// Creates a new [`System`] instance with nothing loaded except the cpus list. If you + /// want to load components, network interfaces or the disks, you'll have to use the + /// `refresh_*_list` methods. [`SystemExt::refresh_networks_list`] for example. + /// + /// Use the [`refresh_all`] method to update its internal information (or any of the `refresh_` + /// method). + /// + /// [`System`]: crate::System + /// [`refresh_all`]: #method.refresh_all + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// ``` + fn new() -> Self { + Self::new_with_specifics(RefreshKind::new()) + } + + /// Creates a new [`System`] instance with everything loaded. + /// + /// It is an equivalent of [`SystemExt::new_with_specifics`]`(`[`RefreshKind::everything`]`())`. + /// + /// [`System`]: crate::System + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// ``` + fn new_all() -> Self { + Self::new_with_specifics(RefreshKind::everything()) + } + + /// Creates a new [`System`] instance and refresh the data corresponding to the + /// given [`RefreshKind`]. + /// + /// [`System`]: crate::System + /// + /// ``` + /// use sysinfo::{RefreshKind, System, SystemExt}; + /// + /// // We want everything except disks. + /// let mut system = System::new_with_specifics(RefreshKind::everything().without_disks_list()); + /// + /// assert_eq!(system.disks().len(), 0); + /// # if System::IS_SUPPORTED && !cfg!(feature = "apple-sandbox") { + /// assert!(system.processes().len() > 0); + /// # } + /// + /// // If you want the disks list afterwards, just call the corresponding + /// // "refresh_disks_list": + /// system.refresh_disks_list(); + /// let disks = system.disks(); + /// ``` + fn new_with_specifics(refreshes: RefreshKind) -> Self; + + /// Refreshes according to the given [`RefreshKind`]. It calls the corresponding + /// "refresh_" methods. + /// + /// ``` + /// use sysinfo::{ProcessRefreshKind, RefreshKind, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// + /// // Let's just update networks and processes: + /// s.refresh_specifics( + /// RefreshKind::new().with_networks().with_processes(ProcessRefreshKind::everything()), + /// ); + /// ``` + fn refresh_specifics(&mut self, refreshes: RefreshKind) { + if refreshes.memory() { + self.refresh_memory(); + } + if let Some(kind) = refreshes.cpu() { + self.refresh_cpu_specifics(kind); + } + if refreshes.components_list() { + self.refresh_components_list(); + } else if refreshes.components() { + self.refresh_components(); + } + if refreshes.networks_list() { + self.refresh_networks_list(); + } else if refreshes.networks() { + self.refresh_networks(); + } + if let Some(kind) = refreshes.processes() { + self.refresh_processes_specifics(kind); + } + if refreshes.disks_list() { + self.refresh_disks_list(); + } else if refreshes.disks() { + self.refresh_disks(); + } + if refreshes.users_list() { + self.refresh_users_list(); + } + } + + /// Refreshes all system, processes, disks and network interfaces information. + /// + /// Please note that it doesn't recompute disks list, components list, network interfaces + /// list nor users list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_all(); + /// ``` + fn refresh_all(&mut self) { + self.refresh_system(); + self.refresh_processes(); + self.refresh_disks(); + self.refresh_networks(); + } + + /// Refreshes system information (RAM, swap, CPU usage and components' temperature). + /// + /// If you want some more specific refreshes, you might be interested into looking at + /// [`refresh_memory`], [`refresh_cpu`] and [`refresh_components`]. + /// + /// [`refresh_memory`]: SystemExt::refresh_memory + /// [`refresh_cpu`]: SystemExt::refresh_memory + /// [`refresh_components`]: SystemExt::refresh_components + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_system(); + /// ``` + fn refresh_system(&mut self) { + self.refresh_memory(); + self.refresh_cpu(); + self.refresh_components(); + } + + /// Refreshes RAM and SWAP usage. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_memory(); + /// ``` + fn refresh_memory(&mut self); + + /// Refreshes CPUs information. + /// + /// ⚠️ Please note that the result will very likely be inaccurate at the first call. + /// You need to call this method at least twice (with a bit of time between each call, like + /// 200ms) to get accurate values as it uses previous results to compute the next value. + /// + /// Calling this method is the same as calling + /// `refresh_cpu_specifics(CpuRefreshKind::everything())`. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_cpu(); + /// ``` + fn refresh_cpu(&mut self) { + self.refresh_cpu_specifics(CpuRefreshKind::everything()) + } + + /// Refreshes CPUs specific information. + /// + /// Please note that it doesn't recompute disks list, components list, network interfaces + /// list nor users list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_all(); + /// ``` + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind); + + /// Refreshes components' temperature. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_components(); + /// ``` + fn refresh_components(&mut self) { + for component in self.components_mut() { + component.refresh(); + } + } + + /// Refreshes components list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new(); + /// s.refresh_components_list(); + /// ``` + fn refresh_components_list(&mut self); + + /// Gets all processes and updates their information. + /// + /// It does the same as `system.refresh_processes_specifics(ProcessRefreshKind::everything())`. + /// + /// ⚠️ On Linux, `sysinfo` keeps the `stat` files open by default. You can change this behaviour + /// by using [`set_open_files_limit`][crate::set_open_files_limit]. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_processes(); + /// ``` + fn refresh_processes(&mut self) { + self.refresh_processes_specifics(ProcessRefreshKind::everything()); + } + + /// Gets all processes and updates the specified information. + /// + /// ⚠️ On Linux, `sysinfo` keeps the `stat` files open by default. You can change this behaviour + /// by using [`set_open_files_limit`][crate::set_open_files_limit]. + /// + /// ```no_run + /// use sysinfo::{ProcessRefreshKind, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_processes_specifics(ProcessRefreshKind::new()); + /// ``` + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind); + + /// Refreshes *only* the process corresponding to `pid`. Returns `false` if the process doesn't + /// exist (it will **NOT** be removed from the processes if it doesn't exist anymore). If it + /// isn't listed yet, it'll be added. + /// + /// It is the same as calling + /// `sys.refresh_process_specifics(pid, ProcessRefreshKind::everything())`. + /// + /// ```no_run + /// use sysinfo::{Pid, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_process(Pid::from(1337)); + /// ``` + fn refresh_process(&mut self, pid: Pid) -> bool { + self.refresh_process_specifics(pid, ProcessRefreshKind::everything()) + } + + /// Refreshes *only* the process corresponding to `pid`. Returns `false` if the process doesn't + /// exist (it will **NOT** be removed from the processes if it doesn't exist anymore). If it + /// isn't listed yet, it'll be added. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessRefreshKind, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_process_specifics(Pid::from(1337), ProcessRefreshKind::new()); + /// ``` + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool; + + /// Refreshes the listed disks' information. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_disks(); + /// ``` + fn refresh_disks(&mut self) { + for disk in self.disks_mut() { + disk.refresh(); + } + } + + /// The disk list will be emptied then completely recomputed. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_disks_list(); + /// ``` + fn refresh_disks_list(&mut self); + + /// Refreshes users list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_users_list(); + /// ``` + fn refresh_users_list(&mut self); + + /// Refreshes networks data. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_networks(); + /// ``` + /// + /// It is a shortcut for: + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.networks_mut(); + /// networks.refresh(); + /// ``` + fn refresh_networks(&mut self) { + self.networks_mut().refresh(); + } + + /// The network list will be updated: removing not existing anymore interfaces and adding new + /// ones. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// s.refresh_networks_list(); + /// ``` + /// + /// This is a shortcut for: + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.networks_mut(); + /// networks.refresh_networks_list(); + /// ``` + fn refresh_networks_list(&mut self) { + self.networks_mut().refresh_networks_list(); + } + + /// Returns the process list. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for (pid, process) in s.processes() { + /// println!("{} {}", pid, process.name()); + /// } + /// ``` + fn processes(&self) -> &HashMap<Pid, Process>; + + /// Returns the process corresponding to the given pid or `None` if no such process exists. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// if let Some(process) = s.process(Pid::from(1337)) { + /// println!("{}", process.name()); + /// } + /// ``` + fn process(&self, pid: Pid) -> Option<&Process>; + + /// Returns an iterator of process containing the given `name`. + /// + /// If you want only the processes with exactly the given `name`, take a look at + /// [`SystemExt::processes_by_exact_name`]. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for process in s.processes_by_name("htop") { + /// println!("{} {}", process.pid(), process.name()); + /// } + /// ``` + // FIXME: replace the returned type with `impl Iterator<Item = &Process>` when it's supported! + fn processes_by_name<'a>( + &'a self, + name: &'a str, + ) -> Box<dyn Iterator<Item = &'a Process> + 'a> { + Box::new( + self.processes() + .values() + .filter(move |val: &&Process| val.name().contains(name)), + ) + } + + /// Returns an iterator of processes with exactly the given `name`. + /// + /// If you instead want the processes containing `name`, take a look at + /// [`SystemExt::processes_by_name`]. + /// + /// ```no_run + /// use sysinfo::{ProcessExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for process in s.processes_by_exact_name("htop") { + /// println!("{} {}", process.pid(), process.name()); + /// } + /// ``` + // FIXME: replace the returned type with `impl Iterator<Item = &Process>` when it's supported! + fn processes_by_exact_name<'a>( + &'a self, + name: &'a str, + ) -> Box<dyn Iterator<Item = &'a Process> + 'a> { + Box::new( + self.processes() + .values() + .filter(move |val: &&Process| val.name() == name), + ) + } + + /// Returns "global" cpus information (aka the addition of all the CPUs). + /// + /// To have up-to-date information, you need to call [`SystemExt::refresh_cpu`] or + /// [`SystemExt::refresh_specifics`] with `cpu` enabled. + /// + /// ```no_run + /// use sysinfo::{CpuRefreshKind, CpuExt, RefreshKind, System, SystemExt}; + /// + /// let s = System::new_with_specifics( + /// RefreshKind::new().with_cpu(CpuRefreshKind::everything()), + /// ); + /// println!("{}%", s.global_cpu_info().cpu_usage()); + /// ``` + fn global_cpu_info(&self) -> &Cpu; + + /// Returns the list of the CPUs. + /// + /// By default, the list of cpus is empty until you call [`SystemExt::refresh_cpu`] or + /// [`SystemExt::refresh_specifics`] with `cpu` enabled. + /// + /// ```no_run + /// use sysinfo::{CpuRefreshKind, CpuExt, RefreshKind, System, SystemExt}; + /// + /// let s = System::new_with_specifics( + /// RefreshKind::new().with_cpu(CpuRefreshKind::everything()), + /// ); + /// for cpu in s.cpus() { + /// println!("{}%", cpu.cpu_usage()); + /// } + /// ``` + fn cpus(&self) -> &[Cpu]; + + /// Returns the number of physical cores on the CPU or `None` if it couldn't get it. + /// + /// In case there are multiple CPUs, it will combine the physical core count of all the CPUs. + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{CpuExt, System, SystemExt}; + /// + /// let s = System::new(); + /// println!("{:?}", s.physical_core_count()); + /// ``` + fn physical_core_count(&self) -> Option<usize>; + + /// Returns the RAM size in KB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KB", s.total_memory()); + /// ``` + fn total_memory(&self) -> u64; + + /// Returns the amount of free RAM in KB. + /// + /// Generally, "free" memory refers to unallocated memory whereas "available" memory refers to + /// memory that is available for (re)use. + /// + /// Side note: Windows doesn't report "free" memory so this method returns the same value + /// as [`get_available_memory`](#tymethod.available_memory). + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KB", s.free_memory()); + /// ``` + fn free_memory(&self) -> u64; + + /// Returns the amount of available RAM in KB. + /// + /// Generally, "free" memory refers to unallocated memory whereas "available" memory refers to + /// memory that is available for (re)use. + /// + /// ⚠️ Windows and FreeBSD don't report "available" memory so [`SystemExt::free_memory`] + /// returns the same value as this method. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KB", s.available_memory()); + /// ``` + fn available_memory(&self) -> u64; + + /// Returns the amount of used RAM in KB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KB", s.used_memory()); + /// ``` + fn used_memory(&self) -> u64; + + /// Returns the SWAP size in KB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KB", s.total_swap()); + /// ``` + fn total_swap(&self) -> u64; + + /// Returns the amount of free SWAP in KB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KB", s.free_swap()); + /// ``` + fn free_swap(&self) -> u64; + + /// Returns the amount of used SWAP in KB. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("{} KB", s.used_swap()); + /// ``` + fn used_swap(&self) -> u64; + + /// Returns the components list. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.components() { + /// println!("{}: {}°C", component.label(), component.temperature()); + /// } + /// ``` + fn components(&self) -> &[Component]; + + /// Returns a mutable components list. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for component in s.components_mut() { + /// component.refresh(); + /// } + /// ``` + fn components_mut(&mut self) -> &mut [Component]; + + /// Returns the disks list. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for disk in s.disks() { + /// println!("{:?}", disk.name()); + /// } + /// ``` + fn disks(&self) -> &[Disk]; + + /// Returns the users list. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt, UserExt}; + /// + /// let mut s = System::new_all(); + /// for user in s.users() { + /// println!("{} is in {} groups", user.name(), user.groups().len()); + /// } + /// ``` + fn users(&self) -> &[User]; + + /// Returns the disks list. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for disk in s.disks_mut() { + /// disk.refresh(); + /// } + /// ``` + fn disks_mut(&mut self) -> &mut [Disk]; + + /// Returns the network interfaces object. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, data) in networks { + /// println!( + /// "[{}] in: {}, out: {}", + /// interface_name, + /// data.received(), + /// data.transmitted(), + /// ); + /// } + /// ``` + fn networks(&self) -> &Networks; + + /// Returns a mutable access to network interfaces. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.networks_mut(); + /// networks.refresh_networks_list(); + /// ``` + fn networks_mut(&mut self) -> &mut Networks; + + /// Returns system uptime (in seconds). + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// println!("System running since {} seconds", s.uptime()); + /// ``` + fn uptime(&self) -> u64; + + /// Returns the time (in seconds) when the system booted since UNIX epoch. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("System booted at {} seconds", s.boot_time()); + /// ``` + fn boot_time(&self) -> u64; + + /// Returns the system load average value. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new_all(); + /// let load_avg = s.load_average(); + /// println!( + /// "one minute: {}%, five minutes: {}%, fifteen minutes: {}%", + /// load_avg.one, + /// load_avg.five, + /// load_avg.fifteen, + /// ); + /// ``` + fn load_average(&self) -> LoadAvg; + + /// Returns the system name. + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("OS: {:?}", s.name()); + /// ``` + fn name(&self) -> Option<String>; + + /// Returns the system's kernel version. + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("kernel version: {:?}", s.kernel_version()); + /// ``` + fn kernel_version(&self) -> Option<String>; + + /// Returns the system version (e.g. for MacOS this will return 11.1 rather than the kernel version). + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("OS version: {:?}", s.os_version()); + /// ``` + fn os_version(&self) -> Option<String>; + + /// Returns the system long os version (e.g "MacOS 11.2 BigSur"). + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("Long OS Version: {:?}", s.long_os_version()); + /// ``` + fn long_os_version(&self) -> Option<String>; + + /// Returns the system hostname based off DNS + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("Hostname: {:?}", s.host_name()); + /// ``` + fn host_name(&self) -> Option<String>; + + /// Returns the [`User`] matching the given `user_id`. + /// + /// **Important**: The user list must be filled before using this method, otherwise it will + /// always return `None` (through the `refresh_*` methods). + /// + /// It is a shorthand for: + /// + /// ```ignore + /// let s = System::new_all(); + /// s.users().find(|user| user.id() == user_id); + /// ``` + /// + /// Full example: + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// + /// if let Some(process) = s.process(Pid::from(1337)) { + /// if let Some(user_id) = process.user_id() { + /// eprintln!("User for process 1337: {:?}", s.get_user_by_id(user_id)); + /// } + /// } + /// ``` + fn get_user_by_id(&self, user_id: &Uid) -> Option<&User> { + self.users().iter().find(|user| user.id() == user_id) + } +} + +/// Getting volume of received and transmitted data. +pub trait NetworkExt: Debug { + /// Returns the number of received bytes since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {} B", network.received()); + /// } + /// ``` + fn received(&self) -> u64; + + /// Returns the total number of received bytes. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {} B", network.total_received()); + /// } + /// ``` + fn total_received(&self) -> u64; + + /// Returns the number of transmitted bytes since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {} B", network.transmitted()); + /// } + /// ``` + fn transmitted(&self) -> u64; + + /// Returns the total number of transmitted bytes. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {} B", network.total_transmitted()); + /// } + /// ``` + fn total_transmitted(&self) -> u64; + + /// Returns the number of incoming packets since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.packets_received()); + /// } + /// ``` + fn packets_received(&self) -> u64; + + /// Returns the total number of incoming packets. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.total_packets_received()); + /// } + /// ``` + fn total_packets_received(&self) -> u64; + + /// Returns the number of outcoming packets since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.packets_transmitted()); + /// } + /// ``` + fn packets_transmitted(&self) -> u64; + + /// Returns the total number of outcoming packets. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.total_packets_transmitted()); + /// } + /// ``` + fn total_packets_transmitted(&self) -> u64; + + /// Returns the number of incoming errors since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.errors_on_received()); + /// } + /// ``` + fn errors_on_received(&self) -> u64; + + /// Returns the total number of incoming errors. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {}", network.total_errors_on_received()); + /// } + /// ``` + fn total_errors_on_received(&self) -> u64; + + /// Returns the number of outcoming errors since the last refresh. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.errors_on_transmitted()); + /// } + /// ``` + fn errors_on_transmitted(&self) -> u64; + + /// Returns the total number of outcoming errors. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("out: {}", network.total_errors_on_transmitted()); + /// } + /// ``` + fn total_errors_on_transmitted(&self) -> u64; +} + +/// Interacting with network interfaces. +pub trait NetworksExt: Debug { + /// Returns an iterator over the network interfaces. + /// + /// ```no_run + /// use sysinfo::{NetworkExt, NetworksExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// let networks = s.networks(); + /// for (interface_name, network) in networks { + /// println!("in: {} B", network.received()); + /// } + /// ``` + fn iter(&self) -> NetworksIter; + + /// Refreshes the network interfaces list. + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.networks_mut(); + /// networks.refresh_networks_list(); + /// ``` + fn refresh_networks_list(&mut self); + + /// Refreshes the network interfaces' content. + /// + /// ```no_run + /// use sysinfo::{NetworksExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// let networks = s.networks_mut(); + /// networks.refresh(); + /// ``` + fn refresh(&mut self); +} + +/// Getting a component temperature information. +pub trait ComponentExt: Debug { + /// Returns the temperature of the component (in celsius degree). + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.components() { + /// println!("{}°C", component.temperature()); + /// } + /// ``` + fn temperature(&self) -> f32; + + /// Returns the maximum temperature of the component (in celsius degree). + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.components() { + /// println!("{}°C", component.max()); + /// } + /// ``` + fn max(&self) -> f32; + + /// Returns the highest temperature before the component halts (in celsius degree). + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.components() { + /// println!("{:?}°C", component.critical()); + /// } + /// ``` + fn critical(&self) -> Option<f32>; + + /// Returns the label of the component. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let s = System::new_all(); + /// for component in s.components() { + /// println!("{}", component.label()); + /// } + /// ``` + fn label(&self) -> &str; + + /// Refreshes component. + /// + /// ```no_run + /// use sysinfo::{ComponentExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// for component in s.components_mut() { + /// component.refresh(); + /// } + /// ``` + fn refresh(&mut self); +} + +/// Getting information for a user. +/// +/// It is returned from [`SystemExt::users`]. +/// +/// ```no_run +/// use sysinfo::{System, SystemExt, UserExt}; +/// +/// let mut s = System::new_all(); +/// for user in s.users() { +/// println!("{} is in {} groups", user.name(), user.groups().len()); +/// } +/// ``` +pub trait UserExt: Debug { + /// Return the user id of the user. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt, UserExt}; + /// + /// let mut s = System::new_all(); + /// for user in s.users() { + /// println!("{:?}", *user.id()); + /// } + /// ``` + fn id(&self) -> &Uid; + + /// Return the group id of the user. + /// + /// *NOTE* - On Windows, this value defaults to 0. Windows doesn't have a `username` specific group assigned to the user. + /// They do however have unique [Security Identifiers](https://docs.microsoft.com/en-us/windows/win32/secauthz/security-identifiers) + /// made up of various [Components](https://docs.microsoft.com/en-us/windows/win32/secauthz/sid-components). + /// Pieces of the SID may be a candidate for this field, but it doesn't map well to a single group id. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt, UserExt}; + /// + /// let mut s = System::new_all(); + /// for user in s.users() { + /// println!("{}", *user.group_id()); + /// } + /// ``` + fn group_id(&self) -> Gid; + + /// Returns the name of the user. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt, UserExt}; + /// + /// let mut s = System::new_all(); + /// for user in s.users() { + /// println!("{}", user.name()); + /// } + /// ``` + fn name(&self) -> &str; + + /// Returns the groups of the user. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt, UserExt}; + /// + /// let mut s = System::new_all(); + /// for user in s.users() { + /// println!("{} is in {:?}", user.name(), user.groups()); + /// } + /// ``` + fn groups(&self) -> &[String]; +} diff --git a/vendor/sysinfo/src/unknown/component.rs b/vendor/sysinfo/src/unknown/component.rs new file mode 100644 index 000000000..ec4a8e87a --- /dev/null +++ b/vendor/sysinfo/src/unknown/component.rs @@ -0,0 +1,26 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::ComponentExt; + +#[doc = include_str!("../../md_doc/component.md")] +pub struct Component {} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + 0.0 + } + + fn max(&self) -> f32 { + 0.0 + } + + fn critical(&self) -> Option<f32> { + None + } + + fn label(&self) -> &str { + "" + } + + fn refresh(&mut self) {} +} diff --git a/vendor/sysinfo/src/unknown/cpu.rs b/vendor/sysinfo/src/unknown/cpu.rs new file mode 100644 index 000000000..72b9be447 --- /dev/null +++ b/vendor/sysinfo/src/unknown/cpu.rs @@ -0,0 +1,34 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::CpuExt; + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu {} + +impl Cpu { + pub(crate) fn new() -> Cpu { + Cpu {} + } +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + 0.0 + } + + fn name(&self) -> &str { + "" + } + + fn frequency(&self) -> u64 { + 0 + } + + fn vendor_id(&self) -> &str { + "" + } + + fn brand(&self) -> &str { + "" + } +} diff --git a/vendor/sysinfo/src/unknown/disk.rs b/vendor/sysinfo/src/unknown/disk.rs new file mode 100644 index 000000000..8956da9f5 --- /dev/null +++ b/vendor/sysinfo/src/unknown/disk.rs @@ -0,0 +1,42 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskExt, DiskType}; + +use std::{ffi::OsStr, path::Path}; + +#[doc = include_str!("../../md_doc/disk.md")] +pub struct Disk {} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + unreachable!() + } + + fn name(&self) -> &OsStr { + unreachable!() + } + + fn file_system(&self) -> &[u8] { + &[] + } + + fn mount_point(&self) -> &Path { + Path::new("") + } + + fn total_space(&self) -> u64 { + 0 + } + + fn available_space(&self) -> u64 { + 0 + } + + fn is_removable(&self) -> bool { + false + } + + fn refresh(&mut self) -> bool { + true + } +} diff --git a/vendor/sysinfo/src/unknown/mod.rs b/vendor/sysinfo/src/unknown/mod.rs new file mode 100644 index 000000000..e62712607 --- /dev/null +++ b/vendor/sysinfo/src/unknown/mod.rs @@ -0,0 +1,15 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +pub mod component; +pub mod cpu; +pub mod disk; +pub mod network; +pub mod process; +pub mod system; + +pub use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo/src/unknown/network.rs b/vendor/sysinfo/src/unknown/network.rs new file mode 100644 index 000000000..4a8ff4831 --- /dev/null +++ b/vendor/sysinfo/src/unknown/network.rs @@ -0,0 +1,81 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::collections::HashMap; + +use crate::{NetworkExt, NetworksExt, NetworksIter}; + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Networks { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + fn iter(&self) -> NetworksIter { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) {} + + fn refresh(&mut self) {} +} + +#[doc = include_str!("../../md_doc/network_data.md")] +pub struct NetworkData; + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + 0 + } + + fn total_received(&self) -> u64 { + 0 + } + + fn transmitted(&self) -> u64 { + 0 + } + + fn total_transmitted(&self) -> u64 { + 0 + } + + fn packets_received(&self) -> u64 { + 0 + } + + fn total_packets_received(&self) -> u64 { + 0 + } + + fn packets_transmitted(&self) -> u64 { + 0 + } + + fn total_packets_transmitted(&self) -> u64 { + 0 + } + + fn errors_on_received(&self) -> u64 { + 0 + } + + fn total_errors_on_received(&self) -> u64 { + 0 + } + + fn errors_on_transmitted(&self) -> u64 { + 0 + } + + fn total_errors_on_transmitted(&self) -> u64 { + 0 + } +} diff --git a/vendor/sysinfo/src/unknown/process.rs b/vendor/sysinfo/src/unknown/process.rs new file mode 100644 index 000000000..c4656d3f5 --- /dev/null +++ b/vendor/sysinfo/src/unknown/process.rs @@ -0,0 +1,92 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessStatus, Signal, Uid}; + +use std::fmt; +use std::path::Path; + +impl fmt::Display for ProcessStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Unknown") + } +} + +#[doc = include_str!("../../md_doc/process.md")] +pub struct Process { + pid: Pid, + parent: Option<Pid>, +} + +impl ProcessExt for Process { + fn kill_with(&self, _signal: Signal) -> Option<bool> { + None + } + + fn name(&self) -> &str { + "" + } + + fn cmd(&self) -> &[String] { + &[] + } + + fn exe(&self) -> &Path { + Path::new("") + } + + fn pid(&self) -> Pid { + self.pid + } + + fn environ(&self) -> &[String] { + &[] + } + + fn cwd(&self) -> &Path { + Path::new("") + } + + fn root(&self) -> &Path { + Path::new("") + } + + fn memory(&self) -> u64 { + 0 + } + + fn virtual_memory(&self) -> u64 { + 0 + } + + fn parent(&self) -> Option<Pid> { + self.parent + } + + fn status(&self) -> ProcessStatus { + ProcessStatus::Unknown(0) + } + + fn start_time(&self) -> u64 { + 0 + } + + fn run_time(&self) -> u64 { + 0 + } + + fn cpu_usage(&self) -> f32 { + 0.0 + } + + fn disk_usage(&self) -> DiskUsage { + DiskUsage::default() + } + + fn user_id(&self) -> Option<&Uid> { + None + } + + fn group_id(&self) -> Option<Gid> { + None + } +} diff --git a/vendor/sysinfo/src/unknown/system.rs b/vendor/sysinfo/src/unknown/system.rs new file mode 100644 index 000000000..4603ae737 --- /dev/null +++ b/vendor/sysinfo/src/unknown/system.rs @@ -0,0 +1,171 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + sys::{component::Component, Cpu, Disk, Networks, Process}, + CpuRefreshKind, LoadAvg, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, +}; + +use std::collections::HashMap; + +declare_signals! { + (), + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + processes_list: HashMap<Pid, Process>, + networks: Networks, + global_cpu: Cpu, +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = false; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + fn new_with_specifics(_: RefreshKind) -> System { + System { + processes_list: Default::default(), + networks: Networks::new(), + global_cpu: Cpu::new(), + } + } + + fn refresh_memory(&mut self) {} + + fn refresh_cpu_specifics(&mut self, _refresh_kind: CpuRefreshKind) {} + + fn refresh_components_list(&mut self) {} + + fn refresh_processes_specifics(&mut self, _refresh_kind: ProcessRefreshKind) {} + + fn refresh_process_specifics(&mut self, _pid: Pid, _refresh_kind: ProcessRefreshKind) -> bool { + false + } + + fn refresh_disks_list(&mut self) {} + + fn refresh_users_list(&mut self) {} + + // COMMON PART + // + // Need to be moved into a "common" file to avoid duplication. + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.processes_list + } + + fn process(&self, _pid: Pid) -> Option<&Process> { + None + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn global_cpu_info(&self) -> &Cpu { + &self.global_cpu + } + + fn cpus(&self) -> &[Cpu] { + &[] + } + + fn physical_core_count(&self) -> Option<usize> { + None + } + + fn total_memory(&self) -> u64 { + 0 + } + + fn free_memory(&self) -> u64 { + 0 + } + + fn available_memory(&self) -> u64 { + 0 + } + + fn used_memory(&self) -> u64 { + 0 + } + + fn total_swap(&self) -> u64 { + 0 + } + + fn free_swap(&self) -> u64 { + 0 + } + + fn used_swap(&self) -> u64 { + 0 + } + + fn components(&self) -> &[Component] { + &[] + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut [] + } + + fn disks(&self) -> &[Disk] { + &[] + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut [] + } + + fn uptime(&self) -> u64 { + 0 + } + + fn boot_time(&self) -> u64 { + 0 + } + + fn load_average(&self) -> LoadAvg { + LoadAvg { + one: 0., + five: 0., + fifteen: 0., + } + } + + fn users(&self) -> &[User] { + &[] + } + + fn name(&self) -> Option<String> { + None + } + + fn long_os_version(&self) -> Option<String> { + None + } + + fn kernel_version(&self) -> Option<String> { + None + } + + fn os_version(&self) -> Option<String> { + None + } + + fn host_name(&self) -> Option<String> { + None + } +} + +impl Default for System { + fn default() -> System { + System::new() + } +} diff --git a/vendor/sysinfo/src/users.rs b/vendor/sysinfo/src/users.rs new file mode 100644 index 000000000..1891ec7cc --- /dev/null +++ b/vendor/sysinfo/src/users.rs @@ -0,0 +1,97 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + common::{Gid, Uid}, + User, +}; + +use libc::{getgrgid, getgrouplist}; +use std::fs::File; +use std::io::Read; + +pub fn get_users_list() -> Vec<User> { + let mut s = String::new(); + let mut ngroups = 100; + let mut groups = vec![0; ngroups as usize]; + + let _ = File::open("/etc/passwd").and_then(|mut f| f.read_to_string(&mut s)); + s.lines() + .filter_map(|line| { + let mut parts = line.split(':'); + if let Some(username) = parts.next() { + let mut parts = parts.skip(1); + // Skip the user if the uid cannot be parsed correctly + if let Some(uid) = parts.next().and_then(parse_id) { + if let Some(group_id) = parts.next().and_then(parse_id) { + if let Some(command) = parts.last() { + if command.is_empty() + || command.ends_with("/false") + || command.ends_with("/nologin") + { + // We don't want "fake" users so in case the user command is "bad", we + // ignore this user. + return None; + } + let mut c_user = username.as_bytes().to_vec(); + c_user.push(0); + loop { + let mut current = ngroups; + + unsafe { + if getgrouplist( + c_user.as_ptr() as *const _, + group_id, + groups.as_mut_ptr(), + &mut current, + ) == -1 + { + if current > ngroups { + groups.resize(current as _, 0); + ngroups = current; + continue; + } + // It really failed, let's move on... + return None; + } + // Let's get all the group names! + return Some(User { + uid: Uid(uid), + gid: Gid(group_id), + name: username.to_owned(), + groups: groups[..current as usize] + .iter() + .filter_map(|id| { + let g = getgrgid(*id as _); + if g.is_null() { + return None; + } + let mut group_name = Vec::new(); + let c_group_name = (*g).gr_name; + let mut x = 0; + loop { + let c = *c_group_name.offset(x); + if c == 0 { + break; + } + group_name.push(c as u8); + x += 1; + } + String::from_utf8(group_name).ok() + }) + .collect(), + }); + } + } + } + } + } + } + None + }) + .collect() +} + +#[inline] +fn parse_id(id: &str) -> Option<u32> { + id.parse::<u32>().ok() +} diff --git a/vendor/sysinfo/src/utils.rs b/vendor/sysinfo/src/utils.rs new file mode 100644 index 000000000..578ab61c6 --- /dev/null +++ b/vendor/sysinfo/src/utils.rs @@ -0,0 +1,61 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +/* convert a path to a NUL-terminated Vec<u8> suitable for use with C functions */ +#[cfg(all( + not(feature = "unknown-ci"), + any(target_os = "linux", target_os = "android", target_vendor = "apple") +))] +pub(crate) fn to_cpath(path: &std::path::Path) -> Vec<u8> { + use std::{ffi::OsStr, os::unix::ffi::OsStrExt}; + + let path_os: &OsStr = path.as_ref(); + let mut cpath = path_os.as_bytes().to_vec(); + cpath.push(0); + cpath +} + +/// Converts the value into a parallel iterator (if the multithread feature is enabled) +/// Uses the rayon::iter::IntoParallelIterator trait +#[cfg(all( + all( + any( + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "windows", + target_os = "freebsd", + ), + feature = "multithread" + ), + not(feature = "apple-sandbox"), + not(feature = "unknown-ci") +))] +pub(crate) fn into_iter<T>(val: T) -> T::Iter +where + T: rayon::iter::IntoParallelIterator, +{ + val.into_par_iter() +} + +/// Converts the value into a sequential iterator (if the multithread feature is disabled) +/// Uses the std::iter::IntoIterator trait +#[cfg(all( + all( + any( + target_os = "linux", + target_os = "android", + target_os = "macos", + target_os = "windows", + target_os = "freebsd", + ), + not(feature = "multithread") + ), + not(feature = "unknown-ci"), + not(feature = "apple-sandbox") +))] +pub(crate) fn into_iter<T>(val: T) -> T::IntoIter +where + T: IntoIterator, +{ + val.into_iter() +} diff --git a/vendor/sysinfo/src/windows/component.rs b/vendor/sysinfo/src/windows/component.rs new file mode 100644 index 000000000..502594e38 --- /dev/null +++ b/vendor/sysinfo/src/windows/component.rs @@ -0,0 +1,374 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::ComponentExt; + +use std::ptr::null_mut; + +use winapi::shared::rpcdce::{ + RPC_C_AUTHN_LEVEL_CALL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, + RPC_C_IMP_LEVEL_IMPERSONATE, +}; +use winapi::shared::winerror::{FAILED, SUCCEEDED, S_FALSE, S_OK}; +use winapi::shared::wtypesbase::CLSCTX_INPROC_SERVER; +use winapi::um::combaseapi::{ + CoCreateInstance, CoInitializeEx, CoInitializeSecurity, CoSetProxyBlanket, CoUninitialize, +}; +use winapi::um::oaidl::VARIANT; +use winapi::um::objidl::EOAC_NONE; +use winapi::um::oleauto::{SysAllocString, SysFreeString, VariantClear}; +use winapi::um::wbemcli::{ + CLSID_WbemLocator, IEnumWbemClassObject, IID_IWbemLocator, IWbemClassObject, IWbemLocator, + IWbemServices, WBEM_FLAG_FORWARD_ONLY, WBEM_FLAG_NONSYSTEM_ONLY, WBEM_FLAG_RETURN_IMMEDIATELY, +}; + +#[doc = include_str!("../../md_doc/component.md")] +pub struct Component { + temperature: f32, + max: f32, + critical: Option<f32>, + label: String, + connection: Option<Connection>, +} + +impl Component { + /// Creates a new `Component` with the given information. + fn new() -> Option<Component> { + let mut c = Connection::new() + .and_then(|x| x.initialize_security()) + .and_then(|x| x.create_instance()) + .and_then(|x| x.connect_server()) + .and_then(|x| x.set_proxy_blanket()) + .and_then(|x| x.exec_query())?; + + c.temperature(true) + .map(|(temperature, critical)| Component { + temperature, + label: "Computer".to_owned(), + max: temperature, + critical, + connection: Some(c), + }) + } +} + +impl ComponentExt for Component { + fn temperature(&self) -> f32 { + self.temperature + } + + fn max(&self) -> f32 { + self.max + } + + fn critical(&self) -> Option<f32> { + self.critical + } + + fn label(&self) -> &str { + &self.label + } + + fn refresh(&mut self) { + if self.connection.is_none() { + self.connection = Connection::new() + .and_then(|x| x.initialize_security()) + .and_then(|x| x.create_instance()) + .and_then(|x| x.connect_server()) + .and_then(|x| x.set_proxy_blanket()); + } + self.connection = if let Some(x) = self.connection.take() { + x.exec_query() + } else { + None + }; + if let Some(ref mut connection) = self.connection { + if let Some((temperature, _)) = connection.temperature(false) { + self.temperature = temperature; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } + } +} + +pub(crate) fn get_components() -> Vec<Component> { + match Component::new() { + Some(c) => vec![c], + None => Vec::new(), + } +} + +struct Instance(*mut IWbemLocator); + +impl Drop for Instance { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +struct ServerConnection(*mut IWbemServices); + +impl Drop for ServerConnection { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +struct Enumerator(*mut IEnumWbemClassObject); + +impl Drop for Enumerator { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + (*self.0).Release(); + } + } + } +} + +macro_rules! bstr { + ($($x:expr),*) => {{ + let x: &[u16] = &[$($x as u16),*, 0]; + SysAllocString(x.as_ptr()) + }} +} + +struct Connection { + instance: Option<Instance>, + server_connection: Option<ServerConnection>, + enumerator: Option<Enumerator>, + initialized: bool, +} + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for Connection {} +unsafe impl Sync for Connection {} + +impl Connection { + #[allow(clippy::unnecessary_wraps)] + fn new() -> Option<Connection> { + unsafe { + let val = CoInitializeEx(null_mut(), 0); + Some(Connection { + instance: None, + server_connection: None, + enumerator: None, + initialized: val == S_OK || val == S_FALSE, + }) + } + } + + fn initialize_security(self) -> Option<Connection> { + unsafe { + if FAILED(CoInitializeSecurity( + null_mut(), + -1, + null_mut(), + null_mut(), + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + null_mut(), + EOAC_NONE, + null_mut(), + )) { + None + } else { + Some(self) + } + } + } + + fn create_instance(mut self) -> Option<Connection> { + let mut p_loc = null_mut(); + + unsafe { + if FAILED(CoCreateInstance( + &CLSID_WbemLocator as *const _, + null_mut(), + CLSCTX_INPROC_SERVER, + &IID_IWbemLocator as *const _, + &mut p_loc as *mut _ as *mut _, + )) { + None + } else { + self.instance = Some(Instance(p_loc)); + Some(self) + } + } + } + + fn connect_server(mut self) -> Option<Connection> { + let mut p_svc = null_mut(); + + if let Some(ref instance) = self.instance { + unsafe { + // "root\WMI" + let s = bstr!('r', 'o', 'o', 't', '\\', 'W', 'M', 'I'); + let res = (*instance.0).ConnectServer( + s, + null_mut(), + null_mut(), + null_mut(), + 0, + null_mut(), + null_mut(), + &mut p_svc as *mut _, + ); + SysFreeString(s); + if FAILED(res) { + return None; + } + } + } else { + return None; + } + self.server_connection = Some(ServerConnection(p_svc)); + Some(self) + } + + fn set_proxy_blanket(self) -> Option<Connection> { + if let Some(ref server_connection) = self.server_connection { + unsafe { + if FAILED(CoSetProxyBlanket( + server_connection.0 as *mut _, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + null_mut(), + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + null_mut(), + EOAC_NONE, + )) { + return None; + } + } + } else { + return None; + } + Some(self) + } + + fn exec_query(mut self) -> Option<Connection> { + let mut p_enumerator = null_mut(); + + if let Some(ref server_connection) = self.server_connection { + unsafe { + // "WQL" + let s = bstr!('W', 'Q', 'L'); // query kind + // "SELECT * FROM MSAcpi_ThermalZoneTemperature" + let query = bstr!( + 'S', 'E', 'L', 'E', 'C', 'T', ' ', '*', ' ', 'F', 'R', 'O', 'M', ' ', 'M', 'S', + 'A', 'c', 'p', 'i', '_', 'T', 'h', 'e', 'r', 'm', 'a', 'l', 'Z', 'o', 'n', 'e', + 'T', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', 'r', 'e' + ); + let hres = (*server_connection.0).ExecQuery( + s, + query, + (WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY) as _, + null_mut(), + &mut p_enumerator as *mut _, + ); + SysFreeString(s); + SysFreeString(query); + if FAILED(hres) { + return None; + } + } + } else { + return None; + } + self.enumerator = Some(Enumerator(p_enumerator)); + Some(self) + } + + fn temperature(&mut self, get_critical: bool) -> Option<(f32, Option<f32>)> { + use winapi::um::wbemcli::WBEM_INFINITE; + + let p_enum = match self.enumerator.take() { + Some(x) => x, + None => { + return None; + } + }; + let mut p_obj: *mut IWbemClassObject = null_mut(); + let mut nb_returned = 0; + + unsafe { + (*p_enum.0).Next( + WBEM_INFINITE as _, // Time out + 1, // One object + &mut p_obj as *mut _, + &mut nb_returned, + ); + + if nb_returned == 0 { + return None; // not enough rights I suppose... + } + + (*p_obj).BeginEnumeration(WBEM_FLAG_NONSYSTEM_ONLY as _); + + let mut p_val = std::mem::MaybeUninit::<VARIANT>::uninit(); + // "CurrentTemperature" + let temp = bstr!( + 'C', 'u', 'r', 'r', 'e', 'n', 't', 'T', 'e', 'm', 'p', 'e', 'r', 'a', 't', 'u', + 'r', 'e' + ); + let res = (*p_obj).Get(temp, 0, p_val.as_mut_ptr(), null_mut(), null_mut()); + let mut p_val = p_val.assume_init(); + + SysFreeString(temp); + VariantClear(&mut p_val as *mut _ as *mut _); + + let temp = if SUCCEEDED(res) { + // temperature is given in tenth of degrees Kelvin + (p_val.n1.decVal().Lo64 / 10) as f32 - 273.15 + } else { + (*p_obj).Release(); + return None; + }; + + let mut critical = None; + if get_critical { + // "CriticalPoint" + let crit = bstr!( + 'C', 'r', 'i', 't', 'i', 'c', 'a', 'l', 'T', 'r', 'i', 'p', 'P', 'o', 'i', 'n', + 't' + ); + let res = (*p_obj).Get(crit, 0, &mut p_val, null_mut(), null_mut()); + + SysFreeString(crit); + VariantClear(&mut p_val as *mut _ as *mut _); + + if SUCCEEDED(res) { + // temperature is given in tenth of degrees Kelvin + critical = Some((p_val.n1.decVal().Lo64 / 10) as f32 - 273.15); + } + } + (*p_obj).Release(); + Some((temp, critical)) + } + } +} + +impl Drop for Connection { + fn drop(&mut self) { + // Those three calls are here to enforce that they get dropped in the good order. + self.enumerator.take(); + self.server_connection.take(); + self.instance.take(); + if self.initialized { + unsafe { + CoUninitialize(); + } + } + } +} diff --git a/vendor/sysinfo/src/windows/cpu.rs b/vendor/sysinfo/src/windows/cpu.rs new file mode 100644 index 000000000..bbaa27ad7 --- /dev/null +++ b/vendor/sysinfo/src/windows/cpu.rs @@ -0,0 +1,548 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::tools::KeyHandler; +use crate::{CpuExt, CpuRefreshKind, LoadAvg}; + +use std::collections::HashMap; +use std::io::Error; +use std::mem; +use std::ops::DerefMut; +use std::ptr::null_mut; +use std::sync::Mutex; + +use ntapi::ntpoapi::PROCESSOR_POWER_INFORMATION; + +use winapi::shared::minwindef::FALSE; +use winapi::shared::winerror::{ERROR_INSUFFICIENT_BUFFER, ERROR_SUCCESS}; +use winapi::um::handleapi::CloseHandle; +use winapi::um::pdh::{ + PdhAddEnglishCounterA, PdhAddEnglishCounterW, PdhCloseQuery, PdhCollectQueryData, + PdhCollectQueryDataEx, PdhGetFormattedCounterValue, PdhOpenQueryA, PdhRemoveCounter, + PDH_FMT_COUNTERVALUE, PDH_FMT_DOUBLE, PDH_HCOUNTER, PDH_HQUERY, +}; +use winapi::um::powerbase::CallNtPowerInformation; +use winapi::um::synchapi::CreateEventA; +use winapi::um::sysinfoapi::GetLogicalProcessorInformationEx; +use winapi::um::sysinfoapi::SYSTEM_INFO; +use winapi::um::winbase::{RegisterWaitForSingleObject, INFINITE}; +use winapi::um::winnt::{ + ProcessorInformation, RelationAll, RelationProcessorCore, BOOLEAN, HANDLE, + PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX, PVOID, WT_EXECUTEDEFAULT, +}; + +// This formula comes from linux's include/linux/sched/loadavg.h +// https://github.com/torvalds/linux/blob/345671ea0f9258f410eb057b9ced9cefbbe5dc78/include/linux/sched/loadavg.h#L20-L23 +#[allow(clippy::excessive_precision)] +const LOADAVG_FACTOR_1F: f64 = 0.9200444146293232478931553241; +#[allow(clippy::excessive_precision)] +const LOADAVG_FACTOR_5F: f64 = 0.9834714538216174894737477501; +#[allow(clippy::excessive_precision)] +const LOADAVG_FACTOR_15F: f64 = 0.9944598480048967508795473394; +// The time interval in seconds between taking load counts, same as Linux +const SAMPLING_INTERVAL: usize = 5; + +// maybe use a read/write lock instead? +static LOAD_AVG: once_cell::sync::Lazy<Mutex<Option<LoadAvg>>> = + once_cell::sync::Lazy::new(|| unsafe { init_load_avg() }); + +pub(crate) fn get_load_average() -> LoadAvg { + if let Ok(avg) = LOAD_AVG.lock() { + if let Some(avg) = &*avg { + return avg.clone(); + } + } + LoadAvg::default() +} + +unsafe extern "system" fn load_avg_callback(counter: PVOID, _: BOOLEAN) { + let mut display_value = mem::MaybeUninit::<PDH_FMT_COUNTERVALUE>::uninit(); + + if PdhGetFormattedCounterValue( + counter as _, + PDH_FMT_DOUBLE, + null_mut(), + display_value.as_mut_ptr(), + ) != ERROR_SUCCESS as _ + { + return; + } + let display_value = display_value.assume_init(); + if let Ok(mut avg) = LOAD_AVG.lock() { + if let Some(avg) = avg.deref_mut() { + let current_load = display_value.u.doubleValue(); + + avg.one = avg.one * LOADAVG_FACTOR_1F + current_load * (1.0 - LOADAVG_FACTOR_1F); + avg.five = avg.five * LOADAVG_FACTOR_5F + current_load * (1.0 - LOADAVG_FACTOR_5F); + avg.fifteen = + avg.fifteen * LOADAVG_FACTOR_15F + current_load * (1.0 - LOADAVG_FACTOR_15F); + } + } +} + +unsafe fn init_load_avg() -> Mutex<Option<LoadAvg>> { + // You can see the original implementation here: https://github.com/giampaolo/psutil + let mut query = null_mut(); + + if PdhOpenQueryA(null_mut(), 0, &mut query) != ERROR_SUCCESS as _ { + sysinfo_debug!("init_load_avg: PdhOpenQueryA failed"); + return Mutex::new(None); + } + + let mut counter: PDH_HCOUNTER = mem::zeroed(); + if PdhAddEnglishCounterA( + query, + b"\\System\\Cpu Queue Length\0".as_ptr() as _, + 0, + &mut counter, + ) != ERROR_SUCCESS as _ + { + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: failed to get CPU queue length"); + return Mutex::new(None); + } + + let event = CreateEventA(null_mut(), FALSE, FALSE, b"LoadUpdateEvent\0".as_ptr() as _); + if event.is_null() { + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: failed to create event `LoadUpdateEvent`"); + return Mutex::new(None); + } + + if PdhCollectQueryDataEx(query, SAMPLING_INTERVAL as _, event) != ERROR_SUCCESS as _ { + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: PdhCollectQueryDataEx failed"); + return Mutex::new(None); + } + + let mut wait_handle = null_mut(); + if RegisterWaitForSingleObject( + &mut wait_handle, + event, + Some(load_avg_callback), + counter as _, + INFINITE, + WT_EXECUTEDEFAULT, + ) == 0 + { + PdhRemoveCounter(counter); + PdhCloseQuery(query); + sysinfo_debug!("init_load_avg: RegisterWaitForSingleObject failed"); + Mutex::new(None) + } else { + Mutex::new(Some(LoadAvg::default())) + } +} + +struct InternalQuery { + query: PDH_HQUERY, + event: HANDLE, + data: HashMap<String, PDH_HCOUNTER>, +} + +unsafe impl Send for InternalQuery {} +unsafe impl Sync for InternalQuery {} + +impl Drop for InternalQuery { + fn drop(&mut self) { + unsafe { + for (_, counter) in self.data.iter() { + PdhRemoveCounter(*counter); + } + + if !self.event.is_null() { + CloseHandle(self.event); + } + + if !self.query.is_null() { + PdhCloseQuery(self.query); + } + } + } +} + +pub(crate) struct Query { + internal: InternalQuery, +} + +impl Query { + pub fn new() -> Option<Query> { + let mut query = null_mut(); + unsafe { + if PdhOpenQueryA(null_mut(), 0, &mut query) == ERROR_SUCCESS as i32 { + let q = InternalQuery { + query, + event: null_mut(), + data: HashMap::new(), + }; + Some(Query { internal: q }) + } else { + sysinfo_debug!("Query::new: PdhOpenQueryA failed"); + None + } + } + } + + #[allow(clippy::ptr_arg)] + pub fn get(&self, name: &String) -> Option<f32> { + if let Some(counter) = self.internal.data.get(name) { + unsafe { + let mut display_value = mem::MaybeUninit::<PDH_FMT_COUNTERVALUE>::uninit(); + let counter: PDH_HCOUNTER = *counter; + + let ret = PdhGetFormattedCounterValue( + counter, + PDH_FMT_DOUBLE, + null_mut(), + display_value.as_mut_ptr(), + ) as u32; + let display_value = display_value.assume_init(); + return if ret == ERROR_SUCCESS as _ { + let data = *display_value.u.doubleValue(); + Some(data as f32) + } else { + sysinfo_debug!("Query::get: PdhGetFormattedCounterValue failed"); + Some(0.) + }; + } + } + None + } + + #[allow(clippy::ptr_arg)] + pub fn add_english_counter(&mut self, name: &String, getter: Vec<u16>) -> bool { + if self.internal.data.contains_key(name) { + sysinfo_debug!("Query::add_english_counter: doesn't have key `{:?}`", name); + return false; + } + unsafe { + let mut counter: PDH_HCOUNTER = std::mem::zeroed(); + let ret = PdhAddEnglishCounterW(self.internal.query, getter.as_ptr(), 0, &mut counter); + if ret == ERROR_SUCCESS as _ { + self.internal.data.insert(name.clone(), counter); + } else { + sysinfo_debug!( + "Query::add_english_counter: failed to add counter '{}': {:x}...", + name, + ret, + ); + return false; + } + } + true + } + + pub fn refresh(&self) { + unsafe { + if PdhCollectQueryData(self.internal.query) != ERROR_SUCCESS as _ { + sysinfo_debug!("failed to refresh CPU data"); + } + } + } +} + +pub(crate) struct CpusWrapper { + global: Cpu, + cpus: Vec<Cpu>, + got_cpu_frequency: bool, +} + +impl CpusWrapper { + pub fn new() -> Self { + Self { + global: Cpu::new_with_values("Total CPU".to_owned(), String::new(), String::new(), 0), + cpus: Vec::new(), + got_cpu_frequency: false, + } + } + + pub fn global_cpu(&self) -> &Cpu { + &self.global + } + + pub fn global_cpu_mut(&mut self) -> &mut Cpu { + &mut self.global + } + + pub fn cpus(&self) -> &[Cpu] { + &self.cpus + } + + fn init_if_needed(&mut self, refresh_kind: CpuRefreshKind) { + if self.cpus.is_empty() { + let (cpus, vendor_id, brand) = super::tools::init_cpus(refresh_kind); + self.cpus = cpus; + self.global.vendor_id = vendor_id; + self.global.brand = brand; + self.got_cpu_frequency = refresh_kind.frequency(); + } + } + + pub fn len(&mut self) -> usize { + self.init_if_needed(CpuRefreshKind::new()); + self.cpus.len() + } + + pub fn iter_mut(&mut self, refresh_kind: CpuRefreshKind) -> impl Iterator<Item = &mut Cpu> { + self.init_if_needed(refresh_kind); + self.cpus.iter_mut() + } + + pub fn get_frequencies(&mut self) { + if self.got_cpu_frequency { + return; + } + let frequencies = get_frequencies(self.cpus.len()); + + for (cpu, frequency) in self.cpus.iter_mut().zip(frequencies) { + cpu.set_frequency(frequency); + } + self.got_cpu_frequency = true; + } +} + +#[doc = include_str!("../../md_doc/cpu.md")] +pub struct Cpu { + name: String, + cpu_usage: f32, + key_used: Option<KeyHandler>, + vendor_id: String, + brand: String, + frequency: u64, +} + +impl CpuExt for Cpu { + fn cpu_usage(&self) -> f32 { + self.cpu_usage + } + + fn name(&self) -> &str { + &self.name + } + + fn frequency(&self) -> u64 { + self.frequency + } + + fn vendor_id(&self) -> &str { + &self.vendor_id + } + + fn brand(&self) -> &str { + &self.brand + } +} + +impl Cpu { + pub(crate) fn new_with_values( + name: String, + vendor_id: String, + brand: String, + frequency: u64, + ) -> Cpu { + Cpu { + name, + cpu_usage: 0f32, + key_used: None, + vendor_id, + brand, + frequency, + } + } + + pub(crate) fn set_cpu_usage(&mut self, value: f32) { + self.cpu_usage = value; + } + + pub(crate) fn set_frequency(&mut self, value: u64) { + self.frequency = value; + } +} + +fn get_vendor_id_not_great(info: &SYSTEM_INFO) -> String { + use winapi::um::winnt; + // https://docs.microsoft.com/fr-fr/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info + unsafe { + match info.u.s().wProcessorArchitecture { + winnt::PROCESSOR_ARCHITECTURE_INTEL => "Intel x86", + winnt::PROCESSOR_ARCHITECTURE_MIPS => "MIPS", + winnt::PROCESSOR_ARCHITECTURE_ALPHA => "RISC Alpha", + winnt::PROCESSOR_ARCHITECTURE_PPC => "PPC", + winnt::PROCESSOR_ARCHITECTURE_SHX => "SHX", + winnt::PROCESSOR_ARCHITECTURE_ARM => "ARM", + winnt::PROCESSOR_ARCHITECTURE_IA64 => "Intel Itanium-based x64", + winnt::PROCESSOR_ARCHITECTURE_ALPHA64 => "RISC Alpha x64", + winnt::PROCESSOR_ARCHITECTURE_MSIL => "MSIL", + winnt::PROCESSOR_ARCHITECTURE_AMD64 => "(Intel or AMD) x64", + winnt::PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 => "Intel Itanium-based x86", + winnt::PROCESSOR_ARCHITECTURE_NEUTRAL => "unknown", + winnt::PROCESSOR_ARCHITECTURE_ARM64 => "ARM x64", + winnt::PROCESSOR_ARCHITECTURE_ARM32_ON_WIN64 => "ARM", + winnt::PROCESSOR_ARCHITECTURE_IA32_ON_ARM64 => "Intel Itanium-based x86", + _ => "unknown", + } + .to_owned() + } +} + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub(crate) fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) { + #[cfg(target_arch = "x86")] + use std::arch::x86::__cpuid; + #[cfg(target_arch = "x86_64")] + use std::arch::x86_64::__cpuid; + + unsafe fn add_u32(v: &mut Vec<u8>, i: u32) { + let i = &i as *const u32 as *const u8; + v.push(*i); + v.push(*i.offset(1)); + v.push(*i.offset(2)); + v.push(*i.offset(3)); + } + + unsafe { + // First, we try to get the complete name. + let res = __cpuid(0x80000000); + let n_ex_ids = res.eax; + let brand = if n_ex_ids >= 0x80000004 { + let mut extdata = Vec::with_capacity(5); + + for i in 0x80000000..=n_ex_ids { + extdata.push(__cpuid(i)); + } + + // 4 * u32 * nb_entries + let mut out = Vec::with_capacity(4 * std::mem::size_of::<u32>() * 3); + for data in extdata.iter().take(5).skip(2) { + add_u32(&mut out, data.eax); + add_u32(&mut out, data.ebx); + add_u32(&mut out, data.ecx); + add_u32(&mut out, data.edx); + } + let mut pos = 0; + for e in out.iter() { + if *e == 0 { + break; + } + pos += 1; + } + match std::str::from_utf8(&out[..pos]) { + Ok(s) => s.to_owned(), + _ => String::new(), + } + } else { + String::new() + }; + + // Failed to get full name, let's retry for the short version! + let res = __cpuid(0); + let mut x = Vec::with_capacity(3 * std::mem::size_of::<u32>()); + add_u32(&mut x, res.ebx); + add_u32(&mut x, res.edx); + add_u32(&mut x, res.ecx); + let mut pos = 0; + for e in x.iter() { + if *e == 0 { + break; + } + pos += 1; + } + let vendor_id = match std::str::from_utf8(&x[..pos]) { + Ok(s) => s.to_owned(), + Err(_) => get_vendor_id_not_great(info), + }; + (vendor_id, brand) + } +} + +#[cfg(all(not(target_arch = "x86_64"), not(target_arch = "x86")))] +pub(crate) fn get_vendor_id_and_brand(info: &SYSTEM_INFO) -> (String, String) { + (get_vendor_id_not_great(info), String::new()) +} + +pub(crate) fn get_key_used(p: &mut Cpu) -> &mut Option<KeyHandler> { + &mut p.key_used +} + +// From https://stackoverflow.com/a/43813138: +// +// If your PC has 64 or fewer logical cpus installed, the above code will work fine. However, +// if your PC has more than 64 logical cpus installed, use GetActiveCpuCount() or +// GetLogicalCpuInformation() to determine the total number of logical cpus installed. +pub(crate) fn get_frequencies(nb_cpus: usize) -> Vec<u64> { + let size = nb_cpus * mem::size_of::<PROCESSOR_POWER_INFORMATION>(); + let mut infos: Vec<PROCESSOR_POWER_INFORMATION> = Vec::with_capacity(nb_cpus); + + unsafe { + if CallNtPowerInformation( + ProcessorInformation, + null_mut(), + 0, + infos.as_mut_ptr() as _, + size as _, + ) == 0 + { + infos.set_len(nb_cpus); + // infos.Number + return infos + .into_iter() + .map(|i| i.CurrentMhz as u64) + .collect::<Vec<_>>(); + } + } + sysinfo_debug!("get_frequencies: CallNtPowerInformation failed"); + vec![0; nb_cpus] +} + +pub(crate) fn get_physical_core_count() -> Option<usize> { + // we cannot use the number of cpus here to pre calculate the buf size + // GetLogicalCpuInformationEx with RelationProcessorCore passed to it not only returns + // the logical cores but also numa nodes + // + // GetLogicalCpuInformationEx: https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getlogicalprocessorinformationex + + let mut needed_size = 0; + unsafe { + GetLogicalProcessorInformationEx(RelationAll, null_mut(), &mut needed_size); + + let mut buf: Vec<u8> = Vec::with_capacity(needed_size as _); + + loop { + if GetLogicalProcessorInformationEx( + RelationAll, + buf.as_mut_ptr() as *mut _, + &mut needed_size, + ) == FALSE + { + let e = Error::last_os_error(); + // For some reasons, the function might return a size not big enough... + match e.raw_os_error() { + Some(value) if value == ERROR_INSUFFICIENT_BUFFER as _ => {} + _ => { + sysinfo_debug!( + "get_physical_core_count: GetLogicalCpuInformationEx failed" + ); + return None; + } + } + } else { + break; + } + buf.reserve(needed_size as usize - buf.capacity()); + } + + buf.set_len(needed_size as _); + + let mut i = 0; + let raw_buf = buf.as_ptr(); + let mut count = 0; + while i < buf.len() { + let p = &*(raw_buf.add(i) as PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX); + i += p.Size as usize; + if p.Relationship == RelationProcessorCore { + // Only count the physical cores. + count += 1; + } + } + Some(count) + } +} diff --git a/vendor/sysinfo/src/windows/disk.rs b/vendor/sysinfo/src/windows/disk.rs new file mode 100644 index 000000000..ae393afb2 --- /dev/null +++ b/vendor/sysinfo/src/windows/disk.rs @@ -0,0 +1,249 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{DiskExt, DiskType}; + +use std::ffi::{OsStr, OsString}; +use std::mem::size_of; +use std::path::Path; + +use winapi::ctypes::c_void; +use winapi::shared::minwindef::{DWORD, MAX_PATH}; +use winapi::um::fileapi::{ + CreateFileW, GetDiskFreeSpaceExW, GetDriveTypeW, GetLogicalDrives, GetVolumeInformationW, + OPEN_EXISTING, +}; +use winapi::um::handleapi::{CloseHandle, INVALID_HANDLE_VALUE}; +use winapi::um::ioapiset::DeviceIoControl; +use winapi::um::winbase::{DRIVE_FIXED, DRIVE_REMOVABLE}; +use winapi::um::winioctl::{ + DEVICE_TRIM_DESCRIPTOR, IOCTL_STORAGE_QUERY_PROPERTY, STORAGE_PROPERTY_QUERY, +}; +use winapi::um::winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, HANDLE, ULARGE_INTEGER}; + +#[doc = include_str!("../../md_doc/disk.md")] +pub struct Disk { + type_: DiskType, + name: OsString, + file_system: Vec<u8>, + mount_point: Vec<u16>, + s_mount_point: String, + total_space: u64, + available_space: u64, + is_removable: bool, +} + +impl DiskExt for Disk { + fn type_(&self) -> DiskType { + self.type_ + } + + fn name(&self) -> &OsStr { + &self.name + } + + fn file_system(&self) -> &[u8] { + &self.file_system + } + + fn mount_point(&self) -> &Path { + Path::new(&self.s_mount_point) + } + + fn total_space(&self) -> u64 { + self.total_space + } + + fn available_space(&self) -> u64 { + self.available_space + } + + fn is_removable(&self) -> bool { + self.is_removable + } + + fn refresh(&mut self) -> bool { + if self.total_space != 0 { + unsafe { + let mut tmp: ULARGE_INTEGER = std::mem::zeroed(); + if GetDiskFreeSpaceExW( + self.mount_point.as_ptr(), + std::ptr::null_mut(), + std::ptr::null_mut(), + &mut tmp, + ) != 0 + { + self.available_space = *tmp.QuadPart(); + return true; + } + } + } + false + } +} + +struct HandleWrapper(HANDLE); + +impl HandleWrapper { + unsafe fn new(drive_name: &[u16], open_rights: DWORD) -> Option<Self> { + let handle = CreateFileW( + drive_name.as_ptr(), + open_rights, + FILE_SHARE_READ | FILE_SHARE_WRITE, + std::ptr::null_mut(), + OPEN_EXISTING, + 0, + std::ptr::null_mut(), + ); + if handle == INVALID_HANDLE_VALUE { + CloseHandle(handle); + None + } else { + Some(Self(handle)) + } + } +} + +impl Drop for HandleWrapper { + fn drop(&mut self) { + unsafe { + CloseHandle(self.0); + } + } +} + +unsafe fn get_drive_size(mount_point: &[u16]) -> Option<(u64, u64)> { + let mut total_size: ULARGE_INTEGER = std::mem::zeroed(); + let mut available_space: ULARGE_INTEGER = std::mem::zeroed(); + if GetDiskFreeSpaceExW( + mount_point.as_ptr(), + std::ptr::null_mut(), + &mut total_size, + &mut available_space, + ) != 0 + { + Some(( + *total_size.QuadPart() as u64, + *available_space.QuadPart() as u64, + )) + } else { + None + } +} + +pub(crate) unsafe fn get_disks() -> Vec<Disk> { + let drives = GetLogicalDrives(); + if drives == 0 { + return Vec::new(); + } + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + crate::utils::into_iter(0..DWORD::BITS) + .filter_map(|x| { + if (drives >> x) & 1 == 0 { + return None; + } + let mount_point = [b'A' as u16 + x as u16, b':' as u16, b'\\' as u16, 0]; + + let drive_type = GetDriveTypeW(mount_point.as_ptr()); + + let is_removable = drive_type == DRIVE_REMOVABLE; + + if drive_type != DRIVE_FIXED && drive_type != DRIVE_REMOVABLE { + return None; + } + let mut name = [0u16; MAX_PATH + 1]; + let mut file_system = [0u16; 32]; + if GetVolumeInformationW( + mount_point.as_ptr(), + name.as_mut_ptr(), + name.len() as DWORD, + std::ptr::null_mut(), + std::ptr::null_mut(), + std::ptr::null_mut(), + file_system.as_mut_ptr(), + file_system.len() as DWORD, + ) == 0 + { + return None; + } + let mut pos = 0; + for x in name.iter() { + if *x == 0 { + break; + } + pos += 1; + } + let name = String::from_utf16_lossy(&name[..pos]); + let name = OsStr::new(&name); + + pos = 0; + for x in file_system.iter() { + if *x == 0 { + break; + } + pos += 1; + } + let file_system: Vec<u8> = file_system[..pos].iter().map(|x| *x as u8).collect(); + + let drive_name = [ + b'\\' as u16, + b'\\' as u16, + b'.' as u16, + b'\\' as u16, + b'A' as u16 + x as u16, + b':' as u16, + 0, + ]; + let handle = HandleWrapper::new(&drive_name, 0)?; + let (total_space, available_space) = get_drive_size(&mount_point)?; + if total_space == 0 { + return None; + } + /*let mut spq_trim: STORAGE_PROPERTY_QUERY = std::mem::zeroed(); + spq_trim.PropertyId = StorageDeviceTrimProperty; + spq_trim.QueryType = PropertyStandardQuery; + let mut dtd: DEVICE_TRIM_DESCRIPTOR = std::mem::zeroed();*/ + let mut spq_trim = STORAGE_PROPERTY_QUERY { + PropertyId: 8, + QueryType: 0, + AdditionalParameters: [0], + }; + let mut dtd: DEVICE_TRIM_DESCRIPTOR = std::mem::zeroed(); + + let mut dw_size = 0; + let type_ = if DeviceIoControl( + handle.0, + IOCTL_STORAGE_QUERY_PROPERTY, + &mut spq_trim as *mut STORAGE_PROPERTY_QUERY as *mut c_void, + size_of::<STORAGE_PROPERTY_QUERY>() as DWORD, + &mut dtd as *mut DEVICE_TRIM_DESCRIPTOR as *mut c_void, + size_of::<DEVICE_TRIM_DESCRIPTOR>() as DWORD, + &mut dw_size, + std::ptr::null_mut(), + ) == 0 + || dw_size != size_of::<DEVICE_TRIM_DESCRIPTOR>() as DWORD + { + DiskType::Unknown(-1) + } else { + let is_ssd = dtd.TrimEnabled != 0; + if is_ssd { + DiskType::SSD + } else { + DiskType::HDD + } + }; + Some(Disk { + type_, + name: name.to_owned(), + file_system: file_system.to_vec(), + mount_point: mount_point.to_vec(), + s_mount_point: String::from_utf16_lossy(&mount_point[..mount_point.len() - 1]), + total_space, + available_space, + is_removable, + }) + }) + .collect::<Vec<_>>() +} diff --git a/vendor/sysinfo/src/windows/macros.rs b/vendor/sysinfo/src/windows/macros.rs new file mode 100644 index 000000000..b0c024c7c --- /dev/null +++ b/vendor/sysinfo/src/windows/macros.rs @@ -0,0 +1,16 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +/// Allows to cast only when needed. +#[macro_export] +macro_rules! auto_cast { + ($t:expr, $cast:ty) => {{ + #[cfg(target_pointer_width = "32")] + { + $t as $cast + } + #[cfg(not(target_pointer_width = "32"))] + { + $t + } + }}; +} diff --git a/vendor/sysinfo/src/windows/mod.rs b/vendor/sysinfo/src/windows/mod.rs new file mode 100644 index 000000000..fa5d66beb --- /dev/null +++ b/vendor/sysinfo/src/windows/mod.rs @@ -0,0 +1,20 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +mod component; +mod cpu; +mod disk; +#[macro_use] +mod macros; +mod network; +mod process; +mod system; +mod tools; +mod users; +mod utils; + +pub use self::component::Component; +pub use self::cpu::Cpu; +pub use self::disk::Disk; +pub use self::network::{NetworkData, Networks}; +pub use self::process::Process; +pub use self::system::System; diff --git a/vendor/sysinfo/src/windows/network.rs b/vendor/sysinfo/src/windows/network.rs new file mode 100644 index 000000000..6a09a0490 --- /dev/null +++ b/vendor/sysinfo/src/windows/network.rs @@ -0,0 +1,249 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{NetworkExt, NetworksExt, NetworksIter}; + +use std::collections::{hash_map, HashMap}; + +use winapi::shared::ifdef::{MediaConnectStateDisconnected, NET_LUID}; +use winapi::shared::netioapi::{ + FreeMibTable, GetIfEntry2, GetIfTable2, MIB_IF_ROW2, PMIB_IF_TABLE2, +}; +use winapi::shared::winerror::NO_ERROR; + +macro_rules! old_and_new { + ($ty_:expr, $name:ident, $old:ident, $new_val:expr) => {{ + $ty_.$old = $ty_.$name; + $ty_.$name = $new_val; + }}; +} + +#[doc = include_str!("../../md_doc/networks.md")] +pub struct Networks { + interfaces: HashMap<String, NetworkData>, +} + +impl Networks { + pub(crate) fn new() -> Networks { + Networks { + interfaces: HashMap::new(), + } + } +} + +impl NetworksExt for Networks { + #[allow(clippy::needless_lifetimes)] + fn iter<'a>(&'a self) -> NetworksIter<'a> { + NetworksIter::new(self.interfaces.iter()) + } + + fn refresh_networks_list(&mut self) { + let mut table: PMIB_IF_TABLE2 = std::ptr::null_mut(); + + unsafe { + if GetIfTable2(&mut table) != NO_ERROR { + return; + } + + for (_, data) in self.interfaces.iter_mut() { + data.updated = false; + } + + // In here, this is tricky: we have to filter out the software interfaces to only keep + // the hardware ones. To do so, we first check the connection potential speed (if 0, not + // interesting), then we check its state: if not open, not interesting either. And finally, + // we count the members of a same group: if there is more than 1, then it's software level. + let mut groups = HashMap::new(); + let mut indexes = Vec::new(); + let ptr = (*table).Table.as_ptr(); + for i in 0..(*table).NumEntries { + let ptr = &*ptr.offset(i as _); + if (ptr.TransmitLinkSpeed == 0 && ptr.ReceiveLinkSpeed == 0) + || ptr.MediaConnectState == MediaConnectStateDisconnected + || ptr.PhysicalAddressLength == 0 + { + continue; + } + let id = vec![ + ptr.InterfaceGuid.Data2, + ptr.InterfaceGuid.Data3, + ptr.InterfaceGuid.Data4[0] as _, + ptr.InterfaceGuid.Data4[1] as _, + ptr.InterfaceGuid.Data4[2] as _, + ptr.InterfaceGuid.Data4[3] as _, + ptr.InterfaceGuid.Data4[4] as _, + ptr.InterfaceGuid.Data4[5] as _, + ptr.InterfaceGuid.Data4[6] as _, + ptr.InterfaceGuid.Data4[7] as _, + ]; + let entry = groups.entry(id.clone()).or_insert(0); + *entry += 1; + if *entry > 1 { + continue; + } + indexes.push((i, id)); + } + for (i, id) in indexes { + let ptr = &*ptr.offset(i as _); + if *groups.get(&id).unwrap_or(&0) > 1 { + continue; + } + let mut pos = 0; + for x in ptr.Alias.iter() { + if *x == 0 { + break; + } + pos += 1; + } + let interface_name = match String::from_utf16(&ptr.Alias[..pos]) { + Ok(s) => s, + _ => continue, + }; + match self.interfaces.entry(interface_name) { + hash_map::Entry::Occupied(mut e) => { + let mut interface = e.get_mut(); + old_and_new!(interface, current_out, old_out, ptr.OutOctets); + old_and_new!(interface, current_in, old_in, ptr.InOctets); + old_and_new!( + interface, + packets_in, + old_packets_in, + ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts) + ); + old_and_new!( + interface, + packets_out, + old_packets_out, + ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts) + ); + old_and_new!(interface, errors_in, old_errors_in, ptr.InErrors); + old_and_new!(interface, errors_out, old_errors_out, ptr.OutErrors); + interface.updated = true; + } + hash_map::Entry::Vacant(e) => { + let packets_in = ptr.InUcastPkts.saturating_add(ptr.InNUcastPkts); + let packets_out = ptr.OutUcastPkts.saturating_add(ptr.OutNUcastPkts); + + e.insert(NetworkData { + id: ptr.InterfaceLuid, + current_out: ptr.OutOctets, + old_out: ptr.OutOctets, + current_in: ptr.InOctets, + old_in: ptr.InOctets, + packets_in, + old_packets_in: packets_in, + packets_out, + old_packets_out: packets_out, + errors_in: ptr.InErrors, + old_errors_in: ptr.InErrors, + errors_out: ptr.OutErrors, + old_errors_out: ptr.OutErrors, + updated: true, + }); + } + } + } + FreeMibTable(table as _); + } + // Remove interfaces which are gone. + self.interfaces.retain(|_, d| d.updated); + } + + fn refresh(&mut self) { + let entry = std::mem::MaybeUninit::<MIB_IF_ROW2>::zeroed(); + + unsafe { + let mut entry = entry.assume_init(); + for (_, interface) in self.interfaces.iter_mut() { + entry.InterfaceLuid = interface.id; + entry.InterfaceIndex = 0; // to prevent the function to pick this one as index + if GetIfEntry2(&mut entry) != NO_ERROR { + continue; + } + old_and_new!(interface, current_out, old_out, entry.OutOctets); + old_and_new!(interface, current_in, old_in, entry.InOctets); + old_and_new!( + interface, + packets_in, + old_packets_in, + entry.InUcastPkts.saturating_add(entry.InNUcastPkts) + ); + old_and_new!( + interface, + packets_out, + old_packets_out, + entry.OutUcastPkts.saturating_add(entry.OutNUcastPkts) + ); + old_and_new!(interface, errors_in, old_errors_in, entry.InErrors); + old_and_new!(interface, errors_out, old_errors_out, entry.OutErrors); + } + } + } +} + +#[doc = include_str!("../../md_doc/network_data.md")] +pub struct NetworkData { + id: NET_LUID, + current_out: u64, + old_out: u64, + current_in: u64, + old_in: u64, + packets_in: u64, + old_packets_in: u64, + packets_out: u64, + old_packets_out: u64, + errors_in: u64, + old_errors_in: u64, + errors_out: u64, + old_errors_out: u64, + updated: bool, +} + +impl NetworkExt for NetworkData { + fn received(&self) -> u64 { + self.current_in.saturating_sub(self.old_in) + } + + fn total_received(&self) -> u64 { + self.current_in + } + + fn transmitted(&self) -> u64 { + self.current_out.saturating_sub(self.old_out) + } + + fn total_transmitted(&self) -> u64 { + self.current_out + } + + fn packets_received(&self) -> u64 { + self.packets_in.saturating_sub(self.old_packets_in) + } + + fn total_packets_received(&self) -> u64 { + self.packets_in + } + + fn packets_transmitted(&self) -> u64 { + self.packets_out.saturating_sub(self.old_packets_out) + } + + fn total_packets_transmitted(&self) -> u64 { + self.packets_out + } + + fn errors_on_received(&self) -> u64 { + self.errors_in.saturating_sub(self.old_errors_in) + } + + fn total_errors_on_received(&self) -> u64 { + self.errors_in + } + + fn errors_on_transmitted(&self) -> u64 { + self.errors_out.saturating_sub(self.old_errors_out) + } + + fn total_errors_on_transmitted(&self) -> u64 { + self.errors_out + } +} diff --git a/vendor/sysinfo/src/windows/process.rs b/vendor/sysinfo/src/windows/process.rs new file mode 100644 index 000000000..bdc35c53e --- /dev/null +++ b/vendor/sysinfo/src/windows/process.rs @@ -0,0 +1,1019 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::utils::to_str; +use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; + +use std::ffi::OsString; +use std::fmt; +use std::mem::{size_of, zeroed, MaybeUninit}; +use std::ops::Deref; +use std::os::windows::ffi::OsStringExt; +use std::os::windows::process::CommandExt; +use std::path::{Path, PathBuf}; +use std::process; +use std::ptr::null_mut; +use std::str; +use std::sync::Arc; + +use libc::{c_void, memcpy}; + +use ntapi::ntpebteb::PEB; +use ntapi::ntwow64::{PEB32, PRTL_USER_PROCESS_PARAMETERS32, RTL_USER_PROCESS_PARAMETERS32}; +use once_cell::sync::Lazy; + +use ntapi::ntpsapi::{ + NtQueryInformationProcess, ProcessBasicInformation, ProcessCommandLineInformation, + ProcessWow64Information, PROCESSINFOCLASS, PROCESS_BASIC_INFORMATION, +}; +use ntapi::ntrtl::{RtlGetVersion, PRTL_USER_PROCESS_PARAMETERS, RTL_USER_PROCESS_PARAMETERS}; +use winapi::shared::basetsd::SIZE_T; +use winapi::shared::minwindef::{DWORD, FALSE, FILETIME, LPVOID, MAX_PATH, TRUE, ULONG}; +use winapi::shared::ntdef::{NT_SUCCESS, UNICODE_STRING}; +use winapi::shared::ntstatus::{ + STATUS_BUFFER_OVERFLOW, STATUS_BUFFER_TOO_SMALL, STATUS_INFO_LENGTH_MISMATCH, +}; +use winapi::shared::winerror::ERROR_INSUFFICIENT_BUFFER; +use winapi::um::errhandlingapi::GetLastError; +use winapi::um::handleapi::CloseHandle; +use winapi::um::heapapi::{GetProcessHeap, HeapAlloc, HeapFree}; +use winapi::um::memoryapi::{ReadProcessMemory, VirtualQueryEx}; +use winapi::um::processthreadsapi::{ + GetProcessTimes, GetSystemTimes, OpenProcess, OpenProcessToken, +}; +use winapi::um::psapi::{ + EnumProcessModulesEx, GetModuleBaseNameW, GetModuleFileNameExW, GetProcessMemoryInfo, + LIST_MODULES_ALL, PROCESS_MEMORY_COUNTERS, PROCESS_MEMORY_COUNTERS_EX, +}; +use winapi::um::securitybaseapi::GetTokenInformation; +use winapi::um::winbase::{GetProcessIoCounters, CREATE_NO_WINDOW}; +use winapi::um::winnt::{ + TokenUser, HANDLE, HEAP_ZERO_MEMORY, IO_COUNTERS, MEMORY_BASIC_INFORMATION, + PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, RTL_OSVERSIONINFOEXW, TOKEN_QUERY, TOKEN_USER, + ULARGE_INTEGER, +}; + +impl fmt::Display for ProcessStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(match *self { + ProcessStatus::Run => "Runnable", + _ => "Unknown", + }) + } +} + +fn get_process_handler(pid: Pid) -> Option<HandleWrapper> { + if pid.0 == 0 { + return None; + } + let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; + + unsafe { HandleWrapper::new(OpenProcess(options, FALSE, pid.0 as DWORD)) } +} + +unsafe fn get_process_user_id( + handle: &HandleWrapper, + refresh_kind: ProcessRefreshKind, +) -> Option<Uid> { + struct HeapWrap<T>(*mut T); + + impl<T> HeapWrap<T> { + unsafe fn new(size: DWORD) -> Option<Self> { + let ptr = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size as _) as *mut T; + if ptr.is_null() { + sysinfo_debug!("HeapAlloc failed"); + None + } else { + Some(Self(ptr)) + } + } + } + + impl<T> Drop for HeapWrap<T> { + fn drop(&mut self) { + if !self.0.is_null() { + unsafe { + HeapFree(GetProcessHeap(), 0, self.0 as *mut _); + } + } + } + } + + if !refresh_kind.user() { + return None; + } + + let mut token = null_mut(); + + if OpenProcessToken(**handle, TOKEN_QUERY, &mut token) == 0 { + sysinfo_debug!("OpenProcessToken failed"); + return None; + } + + let token = HandleWrapper::new(token)?; + + let mut size = 0; + + if GetTokenInformation(*token, TokenUser, null_mut(), 0, &mut size) == 0 { + let err = GetLastError(); + if err != ERROR_INSUFFICIENT_BUFFER { + sysinfo_debug!("GetTokenInformation failed, error: {:?}", err); + return None; + } + } + + let ptu: HeapWrap<TOKEN_USER> = HeapWrap::new(size)?; + + if GetTokenInformation(*token, TokenUser, ptu.0 as *mut _, size, &mut size) == 0 { + sysinfo_debug!("GetTokenInformation failed, error: {:?}", GetLastError()); + return None; + } + + let mut name_use = 0; + let mut name = [0u16; 256]; + let mut domain_name = [0u16; 256]; + let mut size = 256; + + if winapi::um::winbase::LookupAccountSidW( + std::ptr::null_mut(), + (*ptu.0).User.Sid, + name.as_mut_ptr(), + &mut size, + domain_name.as_mut_ptr(), + &mut size, + &mut name_use, + ) == 0 + { + sysinfo_debug!( + "LookupAccountSidW failed: {:?}", + winapi::um::errhandlingapi::GetLastError(), + ); + None + } else { + Some(Uid(to_str(name.as_mut_ptr()).into_boxed_str())) + } +} + +struct HandleWrapper(HANDLE); + +impl HandleWrapper { + fn new(handle: HANDLE) -> Option<Self> { + if handle.is_null() { + None + } else { + Some(Self(handle)) + } + } +} + +impl Deref for HandleWrapper { + type Target = HANDLE; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for HandleWrapper { + fn drop(&mut self) { + unsafe { + CloseHandle(self.0); + } + } +} + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl Send for HandleWrapper {} +unsafe impl Sync for HandleWrapper {} + +#[doc = include_str!("../../md_doc/process.md")] +pub struct Process { + name: String, + cmd: Vec<String>, + exe: PathBuf, + pid: Pid, + user_id: Option<Uid>, + environ: Vec<String>, + cwd: PathBuf, + root: PathBuf, + pub(crate) memory: u64, + pub(crate) virtual_memory: u64, + parent: Option<Pid>, + status: ProcessStatus, + handle: Option<Arc<HandleWrapper>>, + cpu_calc_values: CPUsageCalculationValues, + start_time: u64, + pub(crate) run_time: u64, + cpu_usage: f32, + pub(crate) updated: bool, + old_read_bytes: u64, + old_written_bytes: u64, + read_bytes: u64, + written_bytes: u64, +} + +struct CPUsageCalculationValues { + old_process_sys_cpu: u64, + old_process_user_cpu: u64, + old_system_sys_cpu: u64, + old_system_user_cpu: u64, +} + +impl CPUsageCalculationValues { + fn new() -> Self { + CPUsageCalculationValues { + old_process_sys_cpu: 0, + old_process_user_cpu: 0, + old_system_sys_cpu: 0, + old_system_user_cpu: 0, + } + } +} +static WINDOWS_8_1_OR_NEWER: Lazy<bool> = Lazy::new(|| unsafe { + let mut version_info: RTL_OSVERSIONINFOEXW = MaybeUninit::zeroed().assume_init(); + + version_info.dwOSVersionInfoSize = std::mem::size_of::<RTL_OSVERSIONINFOEXW>() as u32; + if !NT_SUCCESS(RtlGetVersion( + &mut version_info as *mut RTL_OSVERSIONINFOEXW as *mut _, + )) { + return true; + } + + // Windows 8.1 is 6.3 + version_info.dwMajorVersion > 6 + || version_info.dwMajorVersion == 6 && version_info.dwMinorVersion >= 3 +}); + +unsafe fn get_process_name(process_handler: &HandleWrapper, h_mod: *mut c_void) -> String { + let mut process_name = [0u16; MAX_PATH + 1]; + + GetModuleBaseNameW( + **process_handler, + h_mod as _, + process_name.as_mut_ptr(), + MAX_PATH as DWORD + 1, + ); + null_terminated_wchar_to_string(&process_name) +} + +unsafe fn get_h_mod(process_handler: &HandleWrapper, h_mod: &mut *mut c_void) -> bool { + let mut cb_needed = 0; + EnumProcessModulesEx( + **process_handler, + h_mod as *mut *mut c_void as _, + size_of::<DWORD>() as DWORD, + &mut cb_needed, + LIST_MODULES_ALL, + ) != 0 +} + +unsafe fn get_exe(process_handler: &HandleWrapper, h_mod: *mut c_void) -> PathBuf { + let mut exe_buf = [0u16; MAX_PATH + 1]; + GetModuleFileNameExW( + **process_handler, + h_mod as _, + exe_buf.as_mut_ptr(), + MAX_PATH as DWORD + 1, + ); + + PathBuf::from(null_terminated_wchar_to_string(&exe_buf)) +} + +impl Process { + pub(crate) fn new_from_pid( + pid: Pid, + now: u64, + refresh_kind: ProcessRefreshKind, + ) -> Option<Process> { + unsafe { + let process_handler = get_process_handler(pid)?; + let mut info: MaybeUninit<PROCESS_BASIC_INFORMATION> = MaybeUninit::uninit(); + if NtQueryInformationProcess( + *process_handler, + ProcessBasicInformation, + info.as_mut_ptr() as *mut _, + size_of::<PROCESS_BASIC_INFORMATION>() as _, + null_mut(), + ) != 0 + { + return None; + } + let info = info.assume_init(); + let mut h_mod = null_mut(); + + let name = if get_h_mod(&process_handler, &mut h_mod) { + get_process_name(&process_handler, h_mod) + } else { + String::new() + }; + + let exe = get_exe(&process_handler, h_mod); + let mut root = exe.clone(); + root.pop(); + let (cmd, environ, cwd) = match get_process_params(&process_handler) { + Ok(args) => args, + Err(_e) => { + sysinfo_debug!("Failed to get process parameters: {}", _e); + (Vec::new(), Vec::new(), PathBuf::new()) + } + }; + let (start_time, run_time) = get_start_and_run_time(&process_handler, now); + let parent = if info.InheritedFromUniqueProcessId as usize != 0 { + Some(Pid(info.InheritedFromUniqueProcessId as _)) + } else { + None + }; + let user_id = get_process_user_id(&process_handler, refresh_kind); + Some(Process { + handle: Some(Arc::new(process_handler)), + name, + pid, + parent, + user_id, + cmd, + environ, + exe, + cwd, + root, + status: ProcessStatus::Run, + memory: 0, + virtual_memory: 0, + cpu_usage: 0., + cpu_calc_values: CPUsageCalculationValues::new(), + start_time, + run_time, + updated: true, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + }) + } + } + + pub(crate) fn new_full( + pid: Pid, + parent: Option<Pid>, + memory: u64, + virtual_memory: u64, + name: String, + now: u64, + refresh_kind: ProcessRefreshKind, + ) -> Process { + if let Some(handle) = get_process_handler(pid) { + let mut h_mod = null_mut(); + + unsafe { + let exe = if get_h_mod(&handle, &mut h_mod) { + get_exe(&handle, h_mod) + } else { + PathBuf::new() + }; + let mut root = exe.clone(); + root.pop(); + let (cmd, environ, cwd) = match get_process_params(&handle) { + Ok(args) => args, + Err(_e) => { + sysinfo_debug!("Failed to get process parameters: {}", _e); + (Vec::new(), Vec::new(), PathBuf::new()) + } + }; + let (start_time, run_time) = get_start_and_run_time(&handle, now); + let user_id = get_process_user_id(&handle, refresh_kind); + Process { + handle: Some(Arc::new(handle)), + name, + pid, + user_id, + parent, + cmd, + environ, + exe, + cwd, + root, + status: ProcessStatus::Run, + memory, + virtual_memory, + cpu_usage: 0., + cpu_calc_values: CPUsageCalculationValues::new(), + start_time, + run_time, + updated: true, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + } + } + } else { + Process { + handle: None, + name, + pid, + user_id: None, + parent, + cmd: Vec::new(), + environ: Vec::new(), + exe: get_executable_path(pid), + cwd: PathBuf::new(), + root: PathBuf::new(), + status: ProcessStatus::Run, + memory, + virtual_memory, + cpu_usage: 0., + cpu_calc_values: CPUsageCalculationValues::new(), + start_time: 0, + run_time: 0, + updated: true, + old_read_bytes: 0, + old_written_bytes: 0, + read_bytes: 0, + written_bytes: 0, + } + } + } + + pub(crate) fn update( + &mut self, + refresh_kind: crate::ProcessRefreshKind, + nb_cpus: u64, + now: u64, + ) { + if refresh_kind.cpu() { + compute_cpu_usage(self, nb_cpus); + } + if refresh_kind.disk_usage() { + update_disk_usage(self); + } + self.run_time = now.saturating_sub(self.start_time()); + self.updated = true; + } + + pub(crate) fn get_handle(&self) -> Option<HANDLE> { + self.handle.as_ref().map(|h| ***h) + } +} + +impl ProcessExt for Process { + fn kill_with(&self, signal: Signal) -> Option<bool> { + super::system::convert_signal(signal)?; + let mut kill = process::Command::new("taskkill.exe"); + kill.arg("/PID").arg(self.pid.to_string()).arg("/F"); + kill.creation_flags(CREATE_NO_WINDOW); + match kill.output() { + Ok(o) => Some(o.status.success()), + Err(_) => Some(false), + } + } + + 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 - self.old_written_bytes, + total_written_bytes: self.written_bytes, + read_bytes: self.read_bytes - 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> { + None + } +} + +unsafe fn get_start_and_run_time(handle: &HandleWrapper, now: u64) -> (u64, u64) { + let mut fstart: FILETIME = zeroed(); + let mut x = zeroed(); + + GetProcessTimes( + **handle, + &mut fstart as *mut FILETIME, + &mut x as *mut FILETIME, + &mut x as *mut FILETIME, + &mut x as *mut FILETIME, + ); + let tmp = super::utils::filetime_to_u64(fstart); + // 11_644_473_600 is the number of seconds between the Windows epoch (1601-01-01) and + // the linux epoch (1970-01-01). + let start = tmp / 10_000_000 - 11_644_473_600; + let run_time = check_sub(now, start); + (start, run_time) +} + +#[allow(clippy::uninit_vec)] +unsafe fn ph_query_process_variable_size( + process_handle: &HandleWrapper, + process_information_class: PROCESSINFOCLASS, +) -> Option<Vec<u16>> { + let mut return_length = MaybeUninit::<ULONG>::uninit(); + + let mut status = NtQueryInformationProcess( + **process_handle, + process_information_class, + null_mut(), + 0, + return_length.as_mut_ptr() as *mut _, + ); + + if status != STATUS_BUFFER_OVERFLOW + && status != STATUS_BUFFER_TOO_SMALL + && status != STATUS_INFO_LENGTH_MISMATCH + { + return None; + } + + let mut return_length = return_length.assume_init(); + let buf_len = (return_length as usize) / 2; + let mut buffer: Vec<u16> = Vec::with_capacity(buf_len + 1); + buffer.set_len(buf_len); + + status = NtQueryInformationProcess( + **process_handle, + process_information_class, + buffer.as_mut_ptr() as *mut _, + return_length, + &mut return_length as *mut _, + ); + if !NT_SUCCESS(status) { + return None; + } + buffer.push(0); + Some(buffer) +} + +unsafe fn get_cmdline_from_buffer(buffer: *const u16) -> Vec<String> { + // Get argc and argv from the command line + let mut argc = MaybeUninit::<i32>::uninit(); + let argv_p = winapi::um::shellapi::CommandLineToArgvW(buffer, argc.as_mut_ptr()); + if argv_p.is_null() { + return Vec::new(); + } + let argc = argc.assume_init(); + let argv = std::slice::from_raw_parts(argv_p, argc as usize); + + let mut res = Vec::new(); + for arg in argv { + let len = libc::wcslen(*arg); + let str_slice = std::slice::from_raw_parts(*arg, len); + res.push(String::from_utf16_lossy(str_slice)); + } + + winapi::um::winbase::LocalFree(argv_p as *mut _); + + res +} + +unsafe fn get_region_size(handle: &HandleWrapper, ptr: LPVOID) -> Result<usize, &'static str> { + let mut meminfo = MaybeUninit::<MEMORY_BASIC_INFORMATION>::uninit(); + if VirtualQueryEx( + **handle, + ptr, + meminfo.as_mut_ptr() as *mut _, + size_of::<MEMORY_BASIC_INFORMATION>(), + ) == 0 + { + return Err("Unable to read process memory information"); + } + let meminfo = meminfo.assume_init(); + Ok((meminfo.RegionSize as isize - ptr.offset_from(meminfo.BaseAddress)) as usize) +} + +#[allow(clippy::uninit_vec)] +unsafe fn get_process_data( + handle: &HandleWrapper, + ptr: LPVOID, + size: usize, +) -> Result<Vec<u16>, &'static str> { + let mut buffer: Vec<u16> = Vec::with_capacity(size / 2 + 1); + buffer.set_len(size / 2); + if ReadProcessMemory( + **handle, + ptr as *mut _, + buffer.as_mut_ptr() as *mut _, + size, + null_mut(), + ) != TRUE + { + return Err("Unable to read process data"); + } + Ok(buffer) +} + +trait RtlUserProcessParameters { + fn get_cmdline(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str>; + fn get_cwd(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str>; + fn get_environ(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str>; +} + +macro_rules! impl_RtlUserProcessParameters { + ($t:ty) => { + impl RtlUserProcessParameters for $t { + fn get_cmdline(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str> { + let ptr = self.CommandLine.Buffer; + let size = self.CommandLine.Length; + unsafe { get_process_data(handle, ptr as _, size as _) } + } + fn get_cwd(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str> { + let ptr = self.CurrentDirectory.DosPath.Buffer; + let size = self.CurrentDirectory.DosPath.Length; + unsafe { get_process_data(handle, ptr as _, size as _) } + } + fn get_environ(&self, handle: &HandleWrapper) -> Result<Vec<u16>, &'static str> { + let ptr = self.Environment; + unsafe { + let size = get_region_size(handle, ptr as LPVOID)?; + get_process_data(handle, ptr as _, size as _) + } + } + } + }; +} + +impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS32); +impl_RtlUserProcessParameters!(RTL_USER_PROCESS_PARAMETERS); + +unsafe fn get_process_params( + handle: &HandleWrapper, +) -> Result<(Vec<String>, Vec<String>, PathBuf), &'static str> { + if !cfg!(target_pointer_width = "64") { + return Err("Non 64 bit targets are not supported"); + } + + // First check if target process is running in wow64 compatibility emulator + let mut pwow32info = MaybeUninit::<LPVOID>::uninit(); + let result = NtQueryInformationProcess( + **handle, + ProcessWow64Information, + pwow32info.as_mut_ptr() as *mut _, + size_of::<LPVOID>() as u32, + null_mut(), + ); + if !NT_SUCCESS(result) { + return Err("Unable to check WOW64 information about the process"); + } + let pwow32info = pwow32info.assume_init(); + + if pwow32info.is_null() { + // target is a 64 bit process + + let mut pbasicinfo = MaybeUninit::<PROCESS_BASIC_INFORMATION>::uninit(); + let result = NtQueryInformationProcess( + **handle, + ProcessBasicInformation, + pbasicinfo.as_mut_ptr() as *mut _, + size_of::<PROCESS_BASIC_INFORMATION>() as u32, + null_mut(), + ); + if !NT_SUCCESS(result) { + return Err("Unable to get basic process information"); + } + let pinfo = pbasicinfo.assume_init(); + + let mut peb = MaybeUninit::<PEB>::uninit(); + if ReadProcessMemory( + **handle, + pinfo.PebBaseAddress as *mut _, + peb.as_mut_ptr() as *mut _, + size_of::<PEB>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read process PEB"); + } + + let peb = peb.assume_init(); + + let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS>::uninit(); + if ReadProcessMemory( + **handle, + peb.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS as *mut _, + proc_params.as_mut_ptr() as *mut _, + size_of::<RTL_USER_PROCESS_PARAMETERS>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read process parameters"); + } + + let proc_params = proc_params.assume_init(); + return Ok(( + get_cmd_line(&proc_params, handle), + get_proc_env(&proc_params, handle), + get_cwd(&proc_params, handle), + )); + } + // target is a 32 bit process in wow64 mode + + let mut peb32 = MaybeUninit::<PEB32>::uninit(); + if ReadProcessMemory( + **handle, + pwow32info, + peb32.as_mut_ptr() as *mut _, + size_of::<PEB32>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read PEB32"); + } + let peb32 = peb32.assume_init(); + + let mut proc_params = MaybeUninit::<RTL_USER_PROCESS_PARAMETERS32>::uninit(); + if ReadProcessMemory( + **handle, + peb32.ProcessParameters as *mut PRTL_USER_PROCESS_PARAMETERS32 as *mut _, + proc_params.as_mut_ptr() as *mut _, + size_of::<RTL_USER_PROCESS_PARAMETERS32>() as SIZE_T, + null_mut(), + ) != TRUE + { + return Err("Unable to read 32 bit process parameters"); + } + let proc_params = proc_params.assume_init(); + Ok(( + get_cmd_line(&proc_params, handle), + get_proc_env(&proc_params, handle), + get_cwd(&proc_params, handle), + )) +} + +fn get_cwd<T: RtlUserProcessParameters>(params: &T, handle: &HandleWrapper) -> PathBuf { + match params.get_cwd(handle) { + Ok(buffer) => unsafe { PathBuf::from(null_terminated_wchar_to_string(buffer.as_slice())) }, + Err(_e) => { + sysinfo_debug!("get_cwd failed to get data: {}", _e); + PathBuf::new() + } + } +} + +unsafe fn null_terminated_wchar_to_string(slice: &[u16]) -> String { + match slice.iter().position(|&x| x == 0) { + Some(pos) => OsString::from_wide(&slice[..pos]) + .to_string_lossy() + .into_owned(), + None => OsString::from_wide(slice).to_string_lossy().into_owned(), + } +} + +fn get_cmd_line_old<T: RtlUserProcessParameters>( + params: &T, + handle: &HandleWrapper, +) -> Vec<String> { + match params.get_cmdline(handle) { + Ok(buffer) => unsafe { get_cmdline_from_buffer(buffer.as_ptr()) }, + Err(_e) => { + sysinfo_debug!("get_cmd_line_old failed to get data: {}", _e); + Vec::new() + } + } +} + +#[allow(clippy::cast_ptr_alignment)] +fn get_cmd_line_new(handle: &HandleWrapper) -> Vec<String> { + unsafe { + if let Some(buffer) = ph_query_process_variable_size(handle, ProcessCommandLineInformation) + { + let buffer = (*(buffer.as_ptr() as *const UNICODE_STRING)).Buffer; + + get_cmdline_from_buffer(buffer) + } else { + vec![] + } + } +} + +fn get_cmd_line<T: RtlUserProcessParameters>(params: &T, handle: &HandleWrapper) -> Vec<String> { + if *WINDOWS_8_1_OR_NEWER { + get_cmd_line_new(handle) + } else { + get_cmd_line_old(params, handle) + } +} + +fn get_proc_env<T: RtlUserProcessParameters>(params: &T, handle: &HandleWrapper) -> Vec<String> { + match params.get_environ(handle) { + Ok(buffer) => { + let equals = "=".encode_utf16().next().unwrap(); + let raw_env = buffer; + let mut result = Vec::new(); + let mut begin = 0; + while let Some(offset) = raw_env[begin..].iter().position(|&c| c == 0) { + let end = begin + offset; + if raw_env[begin..end].iter().any(|&c| c == equals) { + result.push( + OsString::from_wide(&raw_env[begin..end]) + .to_string_lossy() + .into_owned(), + ); + begin = end + 1; + } else { + break; + } + } + result + } + Err(_e) => { + sysinfo_debug!("get_proc_env failed to get data: {}", _e); + Vec::new() + } + } +} + +pub(crate) fn get_executable_path(_pid: Pid) -> PathBuf { + /*let where_req = format!("ProcessId={}", pid); + + if let Some(ret) = run_wmi(&["process", "where", &where_req, "get", "ExecutablePath"]) { + for line in ret.lines() { + if line.is_empty() || line == "ExecutablePath" { + continue + } + return line.to_owned(); + } + }*/ + PathBuf::new() +} + +#[inline] +fn check_sub(a: u64, b: u64) -> u64 { + if a < b { + a + } else { + a - b + } +} + +/// Before changing this function, you must consider the following: +/// https://github.com/GuillaumeGomez/sysinfo/issues/459 +pub(crate) fn compute_cpu_usage(p: &mut Process, nb_cpus: u64) { + unsafe { + let mut ftime: FILETIME = zeroed(); + let mut fsys: FILETIME = zeroed(); + let mut fuser: FILETIME = zeroed(); + let mut fglobal_idle_time: FILETIME = zeroed(); + let mut fglobal_kernel_time: FILETIME = zeroed(); // notice that it includes idle time + let mut fglobal_user_time: FILETIME = zeroed(); + + if let Some(handle) = p.get_handle() { + GetProcessTimes( + handle, + &mut ftime as *mut FILETIME, + &mut ftime as *mut FILETIME, + &mut fsys as *mut FILETIME, + &mut fuser as *mut FILETIME, + ); + } + GetSystemTimes( + &mut fglobal_idle_time as *mut FILETIME, + &mut fglobal_kernel_time as *mut FILETIME, + &mut fglobal_user_time as *mut FILETIME, + ); + + let mut sys: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut sys as *mut ULARGE_INTEGER as *mut c_void, + &mut fsys as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + let mut user: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut user as *mut ULARGE_INTEGER as *mut c_void, + &mut fuser as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + let mut global_kernel_time: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut global_kernel_time as *mut ULARGE_INTEGER as *mut c_void, + &mut fglobal_kernel_time as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + let mut global_user_time: ULARGE_INTEGER = std::mem::zeroed(); + memcpy( + &mut global_user_time as *mut ULARGE_INTEGER as *mut c_void, + &mut fglobal_user_time as *mut FILETIME as *mut c_void, + size_of::<FILETIME>(), + ); + + let sys = *sys.QuadPart(); + let user = *user.QuadPart(); + let global_kernel_time = *global_kernel_time.QuadPart(); + let global_user_time = *global_user_time.QuadPart(); + + let delta_global_kernel_time = + check_sub(global_kernel_time, p.cpu_calc_values.old_system_sys_cpu); + let delta_global_user_time = + check_sub(global_user_time, p.cpu_calc_values.old_system_user_cpu); + let delta_user_time = check_sub(user, p.cpu_calc_values.old_process_user_cpu); + let delta_sys_time = check_sub(sys, p.cpu_calc_values.old_process_sys_cpu); + + p.cpu_calc_values.old_process_user_cpu = user; + p.cpu_calc_values.old_process_sys_cpu = sys; + p.cpu_calc_values.old_system_user_cpu = global_user_time; + p.cpu_calc_values.old_system_sys_cpu = global_kernel_time; + + let denominator = delta_global_user_time.saturating_add(delta_global_kernel_time) as f32; + + if denominator < 0.00001 { + p.cpu_usage = 0.; + return; + } + + p.cpu_usage = 100.0 + * (delta_user_time.saturating_add(delta_sys_time) as f32 / denominator as f32) + * nb_cpus as f32; + } +} + +pub(crate) fn update_disk_usage(p: &mut Process) { + let mut counters = MaybeUninit::<IO_COUNTERS>::uninit(); + + if let Some(handle) = p.get_handle() { + unsafe { + let ret = GetProcessIoCounters(handle, counters.as_mut_ptr()); + if ret == 0 { + sysinfo_debug!("GetProcessIoCounters call failed on process {}", p.pid()); + } else { + let counters = counters.assume_init(); + p.old_read_bytes = p.read_bytes; + p.old_written_bytes = p.written_bytes; + p.read_bytes = counters.ReadTransferCount; + p.written_bytes = counters.WriteTransferCount; + } + } + } +} + +pub(crate) fn update_memory(p: &mut Process) { + if let Some(handle) = p.get_handle() { + unsafe { + let mut pmc: PROCESS_MEMORY_COUNTERS_EX = zeroed(); + if GetProcessMemoryInfo( + handle, + &mut pmc as *mut PROCESS_MEMORY_COUNTERS_EX as *mut c_void + as *mut PROCESS_MEMORY_COUNTERS, + size_of::<PROCESS_MEMORY_COUNTERS_EX>() as DWORD, + ) != 0 + { + p.memory = (pmc.WorkingSetSize as u64) / 1_000; + p.virtual_memory = (pmc.PrivateUsage as u64) / 1_000; + } + } + } +} diff --git a/vendor/sysinfo/src/windows/system.rs b/vendor/sysinfo/src/windows/system.rs new file mode 100644 index 000000000..6abd30db5 --- /dev/null +++ b/vendor/sysinfo/src/windows/system.rs @@ -0,0 +1,623 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::{ + CpuRefreshKind, LoadAvg, Networks, Pid, ProcessExt, ProcessRefreshKind, RefreshKind, SystemExt, + User, +}; +use winapi::um::winreg::HKEY_LOCAL_MACHINE; + +use crate::sys::component::{self, Component}; +use crate::sys::cpu::*; +use crate::sys::disk::{get_disks, Disk}; +use crate::sys::process::{update_memory, Process}; +use crate::sys::tools::*; +use crate::sys::users::get_users; +use crate::sys::utils::get_now; + +use crate::utils::into_iter; + +use std::cell::UnsafeCell; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::mem::{size_of, zeroed}; +use std::os::windows::ffi::OsStrExt; +use std::slice::from_raw_parts; +use std::time::SystemTime; + +use ntapi::ntexapi::{ + NtQuerySystemInformation, SystemProcessInformation, SYSTEM_PROCESS_INFORMATION, +}; +use winapi::ctypes::wchar_t; +use winapi::shared::minwindef::{DWORD, FALSE, HKEY, LPBYTE, TRUE}; +use winapi::shared::ntdef::{PVOID, ULONG}; +use winapi::shared::ntstatus::STATUS_INFO_LENGTH_MISMATCH; +use winapi::shared::winerror; +use winapi::um::minwinbase::STILL_ACTIVE; +use winapi::um::processthreadsapi::GetExitCodeProcess; +use winapi::um::psapi::{GetPerformanceInfo, PERFORMANCE_INFORMATION}; +use winapi::um::sysinfoapi::{ + ComputerNamePhysicalDnsHostname, GetComputerNameExW, GetTickCount64, GlobalMemoryStatusEx, + MEMORYSTATUSEX, +}; +use winapi::um::winnt::{HANDLE, KEY_READ}; +use winapi::um::winreg::{RegOpenKeyExW, RegQueryValueExW}; + +declare_signals! { + (), + Signal::Kill => (), + _ => None, +} + +#[doc = include_str!("../../md_doc/system.md")] +pub struct System { + process_list: HashMap<Pid, Process>, + mem_total: u64, + mem_available: u64, + swap_total: u64, + swap_used: u64, + cpus: CpusWrapper, + components: Vec<Component>, + disks: Vec<Disk>, + query: Option<Query>, + networks: Networks, + boot_time: u64, + users: Vec<User>, +} + +// Useful for parallel iterations. +struct Wrap<T>(T); + +#[allow(clippy::non_send_fields_in_send_ty)] +unsafe impl<T> Send for Wrap<T> {} +unsafe impl<T> Sync for Wrap<T> {} + +unsafe fn boot_time() -> u64 { + match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { + Ok(n) => n.as_secs().saturating_sub(GetTickCount64()) / 1000, + Err(_e) => { + sysinfo_debug!("Failed to compute boot time: {:?}", _e); + 0 + } + } +} + +impl SystemExt for System { + const IS_SUPPORTED: bool = true; + const SUPPORTED_SIGNALS: &'static [Signal] = supported_signals(); + + #[allow(non_snake_case)] + fn new_with_specifics(refreshes: RefreshKind) -> System { + let mut s = System { + process_list: HashMap::with_capacity(500), + mem_total: 0, + mem_available: 0, + swap_total: 0, + swap_used: 0, + cpus: CpusWrapper::new(), + components: Vec::new(), + disks: Vec::with_capacity(2), + query: None, + networks: Networks::new(), + boot_time: unsafe { boot_time() }, + users: Vec::new(), + }; + s.refresh_specifics(refreshes); + s + } + + fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { + if self.query.is_none() { + self.query = Query::new(); + if let Some(ref mut query) = self.query { + add_english_counter( + r"\Processor(_Total)\% Processor Time".to_string(), + query, + get_key_used(self.cpus.global_cpu_mut()), + "tot_0".to_owned(), + ); + for (pos, proc_) in self.cpus.iter_mut(refresh_kind).enumerate() { + add_english_counter( + format!(r"\Processor({})\% Processor Time", pos), + query, + get_key_used(proc_), + format!("{}_0", pos), + ); + } + } + } + if let Some(ref mut query) = self.query { + query.refresh(); + let mut used_time = None; + if let Some(ref key_used) = *get_key_used(self.cpus.global_cpu_mut()) { + used_time = Some( + query + .get(&key_used.unique_id) + .expect("global_key_idle disappeared"), + ); + } + if let Some(used_time) = used_time { + self.cpus.global_cpu_mut().set_cpu_usage(used_time); + } + for p in self.cpus.iter_mut(refresh_kind) { + let mut used_time = None; + if let Some(ref key_used) = *get_key_used(p) { + used_time = Some( + query + .get(&key_used.unique_id) + .expect("key_used disappeared"), + ); + } + if let Some(used_time) = used_time { + p.set_cpu_usage(used_time); + } + } + if refresh_kind.frequency() { + self.cpus.get_frequencies(); + } + } + } + + fn refresh_memory(&mut self) { + unsafe { + let mut mem_info: MEMORYSTATUSEX = zeroed(); + mem_info.dwLength = size_of::<MEMORYSTATUSEX>() as u32; + GlobalMemoryStatusEx(&mut mem_info); + self.mem_total = auto_cast!(mem_info.ullTotalPhys, u64) / 1_000; + self.mem_available = auto_cast!(mem_info.ullAvailPhys, u64) / 1_000; + let mut perf_info: PERFORMANCE_INFORMATION = zeroed(); + if GetPerformanceInfo(&mut perf_info, size_of::<PERFORMANCE_INFORMATION>() as u32) + == TRUE + { + let swap_total = perf_info.PageSize.saturating_mul( + perf_info + .CommitLimit + .saturating_sub(perf_info.PhysicalTotal), + ); + let swap_used = perf_info.PageSize.saturating_mul( + perf_info + .CommitTotal + .saturating_sub(perf_info.PhysicalTotal), + ); + self.swap_total = (swap_total / 1000) as u64; + self.swap_used = (swap_used / 1000) as u64; + } + } + } + + fn refresh_components_list(&mut self) { + self.components = component::get_components(); + } + + #[allow(clippy::map_entry)] + fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + if self.process_list.contains_key(&pid) { + return refresh_existing_process(self, pid, refresh_kind); + } + let now = get_now(); + if let Some(mut p) = Process::new_from_pid(pid, now, refresh_kind) { + p.update(refresh_kind, self.cpus.len() as u64, now); + p.updated = false; + self.process_list.insert(pid, p); + true + } else { + false + } + } + + #[allow(clippy::cast_ptr_alignment)] + fn refresh_processes_specifics(&mut self, refresh_kind: ProcessRefreshKind) { + // Windows 10 notebook requires at least 512KiB of memory to make it in one go + let mut buffer_size: usize = 512 * 1024; + let now = get_now(); + + loop { + let mut process_information: Vec<u8> = Vec::with_capacity(buffer_size); + let mut cb_needed = 0; + + unsafe { + process_information.set_len(buffer_size); + let ntstatus = NtQuerySystemInformation( + SystemProcessInformation, + process_information.as_mut_ptr() as PVOID, + buffer_size as ULONG, + &mut cb_needed, + ); + + if ntstatus != STATUS_INFO_LENGTH_MISMATCH { + if ntstatus < 0 { + sysinfo_debug!( + "Couldn't get process infos: NtQuerySystemInformation returned {}", + ntstatus + ); + } + + // Parse the data block to get process information + let mut process_ids = Vec::with_capacity(500); + let mut process_information_offset = 0; + loop { + let p = process_information + .as_ptr() + .offset(process_information_offset) + as *const SYSTEM_PROCESS_INFORMATION; + let pi = &*p; + + process_ids.push(Wrap(p)); + + if pi.NextEntryOffset == 0 { + break; + } + + process_information_offset += pi.NextEntryOffset as isize; + } + let process_list = Wrap(UnsafeCell::new(&mut self.process_list)); + let nb_cpus = if refresh_kind.cpu() { + self.cpus.len() as u64 + } else { + 0 + }; + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + // TODO: instead of using parallel iterator only here, would be better to be + // able to run it over `process_information` directly! + let processes = into_iter(process_ids) + .filter_map(|pi| { + let pi = *pi.0; + let pid = Pid(pi.UniqueProcessId as _); + if let Some(proc_) = (*process_list.0.get()).get_mut(&pid) { + proc_.memory = (pi.WorkingSetSize as u64) / 1_000; + proc_.virtual_memory = (pi.VirtualSize as u64) / 1_000; + proc_.update(refresh_kind, nb_cpus, now); + return None; + } + let name = get_process_name(&pi, pid); + let mut p = Process::new_full( + pid, + if pi.InheritedFromUniqueProcessId as usize != 0 { + Some(Pid(pi.InheritedFromUniqueProcessId as _)) + } else { + None + }, + (pi.WorkingSetSize as u64) / 1_000, + (pi.VirtualSize as u64) / 1_000, + name, + now, + refresh_kind, + ); + p.update(refresh_kind, nb_cpus, now); + Some(p) + }) + .collect::<Vec<_>>(); + for p in processes.into_iter() { + self.process_list.insert(p.pid(), p); + } + self.process_list.retain(|_, v| { + let x = v.updated; + v.updated = false; + x + }); + + break; + } + + // GetNewBufferSize + if cb_needed == 0 { + buffer_size *= 2; + continue; + } + // allocating a few more kilo bytes just in case there are some new process + // kicked in since new call to NtQuerySystemInformation + buffer_size = (cb_needed + (1024 * 10)) as usize; + } + } + } + + fn refresh_disks_list(&mut self) { + self.disks = unsafe { get_disks() }; + } + + fn refresh_users_list(&mut self) { + self.users = unsafe { get_users() }; + } + + fn processes(&self) -> &HashMap<Pid, Process> { + &self.process_list + } + + fn process(&self, pid: Pid) -> Option<&Process> { + self.process_list.get(&pid) + } + + fn global_cpu_info(&self) -> &Cpu { + self.cpus.global_cpu() + } + + fn cpus(&self) -> &[Cpu] { + self.cpus.cpus() + } + + fn physical_core_count(&self) -> Option<usize> { + get_physical_core_count() + } + + fn total_memory(&self) -> u64 { + self.mem_total + } + + fn free_memory(&self) -> u64 { + // MEMORYSTATUSEX doesn't report free memory + self.mem_available + } + + fn available_memory(&self) -> u64 { + self.mem_available + } + + fn used_memory(&self) -> u64 { + self.mem_total - self.mem_available + } + + fn total_swap(&self) -> u64 { + self.swap_total + } + + fn free_swap(&self) -> u64 { + self.swap_total - self.swap_used + } + + fn used_swap(&self) -> u64 { + self.swap_used + } + + fn components(&self) -> &[Component] { + &self.components + } + + fn components_mut(&mut self) -> &mut [Component] { + &mut self.components + } + + fn disks(&self) -> &[Disk] { + &self.disks + } + + fn disks_mut(&mut self) -> &mut [Disk] { + &mut self.disks + } + + fn users(&self) -> &[User] { + &self.users + } + + fn networks(&self) -> &Networks { + &self.networks + } + + fn networks_mut(&mut self) -> &mut Networks { + &mut self.networks + } + + fn uptime(&self) -> u64 { + unsafe { GetTickCount64() / 1000 } + } + + fn boot_time(&self) -> u64 { + self.boot_time + } + + fn load_average(&self) -> LoadAvg { + get_load_average() + } + + fn name(&self) -> Option<String> { + Some("Windows".to_owned()) + } + + fn long_os_version(&self) -> Option<String> { + get_reg_string_value( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "ProductName", + ) + } + + fn host_name(&self) -> Option<String> { + get_dns_hostname() + } + + fn kernel_version(&self) -> Option<String> { + get_reg_string_value( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "CurrentBuildNumber", + ) + } + + fn os_version(&self) -> Option<String> { + let major = get_reg_value_u32( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "CurrentMajorVersionNumber", + ); + + let build_number = get_reg_string_value( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "CurrentBuildNumber", + ); + + Some(format!( + "{} ({})", + u32::from_le_bytes(major.unwrap_or_default()), + build_number.unwrap_or_default() + )) + } +} + +impl Default for System { + fn default() -> System { + System::new() + } +} + +fn is_proc_running(handle: HANDLE) -> bool { + let mut exit_code = 0; + unsafe { + let ret = GetExitCodeProcess(handle, &mut exit_code); + !(ret == FALSE || exit_code != STILL_ACTIVE) + } +} + +fn refresh_existing_process(s: &mut System, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { + if let Some(ref mut entry) = s.process_list.get_mut(&pid) { + if let Some(handle) = entry.get_handle() { + if !is_proc_running(handle) { + return false; + } + } else { + return false; + } + update_memory(entry); + entry.update(refresh_kind, s.cpus.len() as u64, get_now()); + entry.updated = false; + true + } else { + false + } +} + +#[allow(clippy::size_of_in_element_count)] +//^ needed for "name.Length as usize / std::mem::size_of::<u16>()" +pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: Pid) -> String { + let name = &process.ImageName; + if name.Buffer.is_null() { + match process_id.0 { + 0 => "Idle".to_owned(), + 4 => "System".to_owned(), + _ => format!("<no name> Process {}", process_id), + } + } else { + unsafe { + let slice = std::slice::from_raw_parts( + name.Buffer, + // The length is in bytes, not the length of string + name.Length as usize / std::mem::size_of::<u16>(), + ); + + String::from_utf16_lossy(slice) + } + } +} + +fn utf16_str<S: AsRef<OsStr> + ?Sized>(text: &S) -> Vec<u16> { + OsStr::new(text) + .encode_wide() + .chain(Some(0).into_iter()) + .collect::<Vec<_>>() +} + +fn get_reg_string_value(hkey: HKEY, path: &str, field_name: &str) -> Option<String> { + let c_path = utf16_str(path); + let c_field_name = utf16_str(field_name); + + let mut new_hkey: HKEY = std::ptr::null_mut(); + unsafe { + if RegOpenKeyExW(hkey, c_path.as_ptr(), 0, KEY_READ, &mut new_hkey) != 0 { + return None; + } + + let mut buf_len: DWORD = 2048; + let mut buf_type: DWORD = 0; + let mut buf: Vec<u8> = Vec::with_capacity(buf_len as usize); + loop { + match RegQueryValueExW( + new_hkey, + c_field_name.as_ptr(), + std::ptr::null_mut(), + &mut buf_type, + buf.as_mut_ptr() as LPBYTE, + &mut buf_len, + ) as DWORD + { + 0 => break, + winerror::ERROR_MORE_DATA => { + buf.reserve(buf_len as _); + } + _ => return None, + } + } + + buf.set_len(buf_len as _); + + let words = from_raw_parts(buf.as_ptr() as *const u16, buf.len() / 2); + let mut s = String::from_utf16_lossy(words); + while s.ends_with('\u{0}') { + s.pop(); + } + Some(s) + } +} + +fn get_reg_value_u32(hkey: HKEY, path: &str, field_name: &str) -> Option<[u8; 4]> { + let c_path = utf16_str(path); + let c_field_name = utf16_str(field_name); + + let mut new_hkey: HKEY = std::ptr::null_mut(); + unsafe { + if RegOpenKeyExW(hkey, c_path.as_ptr(), 0, KEY_READ, &mut new_hkey) != 0 { + return None; + } + + let mut buf_len: DWORD = 4; + let mut buf_type: DWORD = 0; + let mut buf = [0u8; 4]; + + match RegQueryValueExW( + new_hkey, + c_field_name.as_ptr(), + std::ptr::null_mut(), + &mut buf_type, + buf.as_mut_ptr() as LPBYTE, + &mut buf_len, + ) as DWORD + { + 0 => Some(buf), + _ => None, + } + } +} + +fn get_dns_hostname() -> Option<String> { + let mut buffer_size = 0; + // Running this first to get the buffer size since the DNS name can be longer than MAX_COMPUTERNAME_LENGTH + // setting the `lpBuffer` to null will return the buffer size + // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw + unsafe { + GetComputerNameExW( + ComputerNamePhysicalDnsHostname, + std::ptr::null_mut(), + &mut buffer_size, + ); + + // Setting the buffer with the new length + let mut buffer = vec![0_u16; buffer_size as usize]; + + // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ne-sysinfoapi-computer_name_format + if GetComputerNameExW( + ComputerNamePhysicalDnsHostname, + buffer.as_mut_ptr() as *mut wchar_t, + &mut buffer_size, + ) == TRUE + { + if let Some(pos) = buffer.iter().position(|c| *c == 0) { + buffer.resize(pos, 0); + } + + return String::from_utf16(&buffer).ok(); + } + } + + sysinfo_debug!("Failed to get computer hostname"); + None +} diff --git a/vendor/sysinfo/src/windows/tools.rs b/vendor/sysinfo/src/windows/tools.rs new file mode 100644 index 000000000..a0c334010 --- /dev/null +++ b/vendor/sysinfo/src/windows/tools.rs @@ -0,0 +1,55 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::cpu::{self, Cpu, Query}; +use crate::CpuRefreshKind; + +use std::mem::zeroed; + +use winapi::um::sysinfoapi::{GetSystemInfo, SYSTEM_INFO}; + +pub(crate) struct KeyHandler { + pub unique_id: String, +} + +impl KeyHandler { + pub fn new(unique_id: String) -> KeyHandler { + KeyHandler { unique_id } + } +} + +pub(crate) fn init_cpus(refresh_kind: CpuRefreshKind) -> (Vec<Cpu>, String, String) { + unsafe { + let mut sys_info: SYSTEM_INFO = zeroed(); + GetSystemInfo(&mut sys_info); + let (vendor_id, brand) = cpu::get_vendor_id_and_brand(&sys_info); + let nb_cpus = sys_info.dwNumberOfProcessors as usize; + let frequencies = if refresh_kind.frequency() { + cpu::get_frequencies(nb_cpus) + } else { + vec![0; nb_cpus] + }; + let mut ret = Vec::with_capacity(nb_cpus + 1); + for (nb, frequency) in frequencies.iter().enumerate() { + ret.push(Cpu::new_with_values( + format!("CPU {}", nb + 1), + vendor_id.clone(), + brand.clone(), + *frequency, + )); + } + (ret, vendor_id, brand) + } +} + +pub(crate) fn add_english_counter( + s: String, + query: &mut Query, + keys: &mut Option<KeyHandler>, + counter_name: String, +) { + let mut full = s.encode_utf16().collect::<Vec<_>>(); + full.push(0); + if query.add_english_counter(&counter_name, full) { + *keys = Some(KeyHandler::new(counter_name)); + } +} diff --git a/vendor/sysinfo/src/windows/users.rs b/vendor/sysinfo/src/windows/users.rs new file mode 100644 index 000000000..2fef05c3f --- /dev/null +++ b/vendor/sysinfo/src/windows/users.rs @@ -0,0 +1,181 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use crate::sys::utils::to_str; +use crate::{ + common::{Gid, Uid}, + User, +}; + +use std::ptr::null_mut; +use winapi::shared::lmcons::{MAX_PREFERRED_LENGTH, NET_API_STATUS}; +use winapi::shared::minwindef::DWORD; +use winapi::shared::ntstatus::STATUS_SUCCESS; +use winapi::shared::winerror::ERROR_MORE_DATA; +use winapi::um::lmaccess::{NetUserEnum, NetUserGetLocalGroups}; +use winapi::um::lmaccess::{ + FILTER_NORMAL_ACCOUNT, LG_INCLUDE_INDIRECT, LPLOCALGROUP_USERS_INFO_0, USER_INFO_0, +}; +use winapi::um::lmapibuf::NetApiBufferFree; +use winapi::um::ntlsa::{ + LsaEnumerateLogonSessions, LsaFreeReturnBuffer, LsaGetLogonSessionData, + PSECURITY_LOGON_SESSION_DATA, +}; +use winapi::um::winnt::{LPWSTR, PLUID}; + +// FIXME: once this is mreged in winapi, it can be removed. +#[allow(non_upper_case_globals)] +const NERR_Success: NET_API_STATUS = 0; + +unsafe fn get_groups_for_user(username: LPWSTR) -> Vec<String> { + let mut buf: LPLOCALGROUP_USERS_INFO_0 = null_mut(); + let mut nb_entries = 0; + let mut total_entries = 0; + let mut groups; + + let status = NetUserGetLocalGroups( + [0u16].as_ptr(), + username, + 0, + LG_INCLUDE_INDIRECT, + &mut buf as *mut _ as _, + MAX_PREFERRED_LENGTH, + &mut nb_entries, + &mut total_entries, + ); + + if status == NERR_Success { + groups = Vec::with_capacity(nb_entries as _); + + if !buf.is_null() { + for i in 0..nb_entries { + let tmp = buf.offset(i as _); + if tmp.is_null() { + break; + } + groups.push(to_str((*tmp).lgrui0_name)); + } + } + } else { + groups = Vec::new(); + sysinfo_debug!("NetUserGetLocalGroups failed with ret code {}", status); + } + if !buf.is_null() { + NetApiBufferFree(buf as *mut _); + } + + groups +} + +// FIXME: For now, the Uid is the user name, which is quite bad. Normally, there is `PSID` for +// that. But when getting the `PSID` from the processes, it doesn't match the ones we have for +// the users (`EqualSid`). Anyway, until I have time and motivation to fix this. It'll remain +// like that... +pub unsafe fn get_users() -> Vec<User> { + let mut users = Vec::new(); + let mut buffer: *mut USER_INFO_0 = null_mut(); + let mut nb_read = 0; + let mut total = 0; + let mut resume_handle: DWORD = 0; + + loop { + let status = NetUserEnum( + null_mut(), + 0, + FILTER_NORMAL_ACCOUNT, + &mut buffer as *mut _ as *mut _, + MAX_PREFERRED_LENGTH, + &mut nb_read, + &mut total, + &mut resume_handle as *mut _ as *mut _, + ); + if status == NERR_Success || status == ERROR_MORE_DATA { + let entries: &[USER_INFO_0] = std::slice::from_raw_parts(buffer, nb_read as _); + for entry in entries { + if entry.usri0_name.is_null() { + continue; + } + // let mut user: *mut USER_INFO_23 = null_mut(); + + // if NetUserGetInfo( + // null_mut(), + // entry.usri0_name, + // 23, + // &mut user as *mut _ as *mut _, + // ) == NERR_Success + // { + // let groups = get_groups_for_user((*user).usri23_name); + // users.push(User { + // uid: Uid(name.clone().into_boxed_str()), + // gid: Gid(0), + // name: to_str((*user).usri23_name), + // groups, + // }); + // } + // if !user.is_null() { + // NetApiBufferFree(user as *mut _); + // } + let groups = get_groups_for_user(entry.usri0_name); + let name = to_str(entry.usri0_name); + users.push(User { + uid: Uid(name.clone().into_boxed_str()), + gid: Gid(0), + name, + groups, + }); + } + } else { + sysinfo_debug!( + "NetUserEnum error: {}", + if status == winapi::shared::winerror::ERROR_ACCESS_DENIED { + "access denied" + } else if status == winapi::shared::winerror::ERROR_INVALID_LEVEL { + "invalid level" + } else { + "unknown error" + } + ); + } + if !buffer.is_null() { + NetApiBufferFree(buffer as *mut _); + buffer = null_mut(); + } + if status != ERROR_MORE_DATA { + break; + } + } + + // First part done. Second part now! + let mut nb_sessions = 0; + let mut uids: PLUID = null_mut(); + if LsaEnumerateLogonSessions(&mut nb_sessions, &mut uids) != STATUS_SUCCESS { + sysinfo_debug!("LsaEnumerateLogonSessions failed"); + } else { + for offset in 0..nb_sessions { + let entry = uids.add(offset as _); + let mut data: PSECURITY_LOGON_SESSION_DATA = null_mut(); + + if LsaGetLogonSessionData(entry, &mut data) == STATUS_SUCCESS && !data.is_null() { + let data = *data; + if data.LogonType == winapi::um::ntlsa::Network { + continue; + } + let name = to_str(data.UserName.Buffer); + if users.iter().any(|u| u.name == name) { + continue; + } + users.push(User { + uid: Uid(name.clone().into_boxed_str()), + gid: Gid(0), + name, + // There is no local groups for a non-local user. + groups: Vec::new(), + }); + } + if !data.is_null() { + LsaFreeReturnBuffer(data as *mut _); + } + } + } + + users +} diff --git a/vendor/sysinfo/src/windows/utils.rs b/vendor/sysinfo/src/windows/utils.rs new file mode 100644 index 000000000..419ee195c --- /dev/null +++ b/vendor/sysinfo/src/windows/utils.rs @@ -0,0 +1,36 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use winapi::shared::minwindef::FILETIME; +use winapi::um::winnt::LPWSTR; + +use std::time::SystemTime; + +#[inline] +pub(crate) fn filetime_to_u64(f: FILETIME) -> u64 { + (f.dwHighDateTime as u64) << 32 | (f.dwLowDateTime as u64) +} + +#[inline] +pub(crate) fn get_now() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .map(|n| n.as_secs()) + .unwrap_or(0) +} + +pub(crate) unsafe fn to_str(p: LPWSTR) -> String { + let mut i = 0; + + loop { + let c = *p.offset(i); + if c == 0 { + break; + } + i += 1; + } + let s = std::slice::from_raw_parts(p, i as _); + String::from_utf16(s).unwrap_or_else(|_e| { + sysinfo_debug!("Failed to convert to UTF-16 string: {}", _e); + String::new() + }) +} |