diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:18:32 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:18:32 +0000 |
commit | 4547b622d8d29df964fa2914213088b148c498fc (patch) | |
tree | 9fc6b25f3c3add6b745be9a2400a6e96140046e9 /vendor/sysinfo/src | |
parent | Releasing progress-linux version 1.66.0+dfsg1-1~progress7.99u1. (diff) | |
download | rustc-4547b622d8d29df964fa2914213088b148c498fc.tar.xz rustc-4547b622d8d29df964fa2914213088b148c498fc.zip |
Merging upstream version 1.67.1+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/sysinfo/src')
40 files changed, 2766 insertions, 1436 deletions
diff --git a/vendor/sysinfo/src/apple/app_store/process.rs b/vendor/sysinfo/src/apple/app_store/process.rs index 8c3348ee9..7c43697a6 100644 --- a/vendor/sysinfo/src/apple/app_store/process.rs +++ b/vendor/sysinfo/src/apple/app_store/process.rs @@ -79,4 +79,6 @@ impl ProcessExt for Process { fn group_id(&self) -> Option<Gid> { None } + + fn wait(&self) {} } diff --git a/vendor/sysinfo/src/apple/cpu.rs b/vendor/sysinfo/src/apple/cpu.rs index b1068e971..e613bdd2c 100644 --- a/vendor/sysinfo/src/apple/cpu.rs +++ b/vendor/sysinfo/src/apple/cpu.rs @@ -317,14 +317,14 @@ mod test { 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(); + let sysctl_value = line.split(':').nth(1).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(); + let sysctl_value = line.split(':').nth(1).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 index 9f0b4a3a3..d866d5bba 100644 --- a/vendor/sysinfo/src/apple/disk.rs +++ b/vendor/sysinfo/src/apple/disk.rs @@ -1,15 +1,23 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::utils::to_cpath; +use crate::sys::{ + ffi, + utils::{self, CFReleaser}, +}; use crate::{DiskExt, DiskType}; -#[cfg(target_os = "macos")] -pub(crate) use crate::sys::inner::disk::*; +use core_foundation_sys::array::CFArrayCreate; +use core_foundation_sys::base::kCFAllocatorDefault; +use core_foundation_sys::dictionary::{CFDictionaryGetValueIfPresent, CFDictionaryRef}; +use core_foundation_sys::number::{kCFBooleanTrue, CFBooleanRef, CFNumberGetValue}; +use core_foundation_sys::string::{self as cfs, CFStringRef}; -use libc::statfs; -use std::ffi::{OsStr, OsString}; -use std::mem; +use libc::c_void; + +use std::ffi::{CStr, OsStr, OsString}; +use std::os::unix::ffi::OsStrExt; use std::path::{Path, PathBuf}; +use std::ptr; #[doc = include_str!("../../md_doc/disk.md")] pub struct Disk { @@ -17,6 +25,7 @@ pub struct Disk { pub(crate) name: OsString, pub(crate) file_system: Vec<u8>, pub(crate) mount_point: PathBuf, + volume_url: RetainedCFURL, pub(crate) total_space: u64, pub(crate) available_space: u64, pub(crate) is_removable: bool, @@ -53,14 +62,334 @@ impl DiskExt for Disk { 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 + if let Some(requested_properties) = build_requested_properties(&[ + ffi::kCFURLVolumeAvailableCapacityKey, + ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey, + ]) { + match get_disk_properties(&self.volume_url, &requested_properties) { + Some(disk_props) => { + self.available_space = get_available_volume_space(&disk_props); + true + } + None => false, + } } else { + sysinfo_debug!("failed to create volume key list, skipping refresh"); false } } } } + +pub(super) unsafe fn get_disks() -> Vec<Disk> { + let raw_disks = { + let count = libc::getfsstat(ptr::null_mut(), 0, libc::MNT_NOWAIT); + if count < 1 { + return Vec::new(); + } + let bufsize = count * std::mem::size_of::<libc::statfs>() as libc::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 usize); + + disks + }; + + // Create a list of properties about the disk that we want to fetch. + let requested_properties = match build_requested_properties(&[ + ffi::kCFURLVolumeIsEjectableKey, + ffi::kCFURLVolumeIsRemovableKey, + ffi::kCFURLVolumeIsInternalKey, + ffi::kCFURLVolumeTotalCapacityKey, + ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey, + ffi::kCFURLVolumeAvailableCapacityKey, + ffi::kCFURLVolumeNameKey, + ffi::kCFURLVolumeIsBrowsableKey, + ffi::kCFURLVolumeIsLocalKey, + ]) { + Some(properties) => properties, + None => { + sysinfo_debug!("failed to create volume key list"); + return Vec::new(); + } + }; + + let mut disks = Vec::with_capacity(raw_disks.len()); + for c_disk in raw_disks { + let volume_url = match CFReleaser::new( + core_foundation_sys::url::CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, + c_disk.f_mntonname.as_ptr() as *const _, + c_disk.f_mntonname.len() as _, + false as _, + ), + ) { + Some(url) => url, + None => { + sysinfo_debug!("getfsstat returned incompatible paths"); + continue; + } + }; + + let prop_dict = match get_disk_properties(&volume_url, &requested_properties) { + Some(props) => props, + None => continue, + }; + + // Future note: There is a difference between `kCFURLVolumeIsBrowsableKey` and the + // `kCFURLEnumeratorSkipInvisibles` option of `CFURLEnumeratorOptions`. Specifically, + // the first one considers the writable `Data`(`/System/Volumes/Data`) partition to be + // browsable, while it is classified as "invisible" by CoreFoundation's volume emumerator. + let browsable = get_bool_value( + prop_dict.inner(), + DictKey::Extern(ffi::kCFURLVolumeIsBrowsableKey), + ) + .unwrap_or_default(); + + // Do not return invisible "disks". Most of the time, these are APFS snapshots, hidden + // system volumes, etc. Browsable is defined to be visible in the system's UI like Finder, + // disk utility, system information, etc. + // + // To avoid seemingly duplicating many disks and creating an inaccurate view of the system's resources, + // these are skipped entirely. + if !browsable { + continue; + } + + let local_only = get_bool_value( + prop_dict.inner(), + DictKey::Extern(ffi::kCFURLVolumeIsLocalKey), + ) + .unwrap_or(true); + + // Skip any drive that is not locally attached to the system. + // + // This includes items like SMB mounts, and matches the other platform's behavior. + if !local_only { + continue; + } + + let mount_point = PathBuf::from(OsStr::from_bytes( + CStr::from_ptr(c_disk.f_mntonname.as_ptr()).to_bytes(), + )); + + disks.extend(new_disk(mount_point, volume_url, c_disk, &prop_dict)) + } + + disks +} + +type RetainedCFArray = CFReleaser<core_foundation_sys::array::__CFArray>; +type RetainedCFDictionary = CFReleaser<core_foundation_sys::dictionary::__CFDictionary>; +type RetainedCFURL = CFReleaser<core_foundation_sys::url::__CFURL>; + +unsafe fn build_requested_properties(properties: &[CFStringRef]) -> Option<RetainedCFArray> { + CFReleaser::new(CFArrayCreate( + ptr::null_mut(), + properties.as_ptr() as *const *const c_void, + properties.len() as _, + &core_foundation_sys::array::kCFTypeArrayCallBacks, + )) +} + +fn get_disk_properties( + volume_url: &RetainedCFURL, + requested_properties: &RetainedCFArray, +) -> Option<RetainedCFDictionary> { + CFReleaser::new(unsafe { + ffi::CFURLCopyResourcePropertiesForKeys( + volume_url.inner(), + requested_properties.inner(), + ptr::null_mut(), + ) + }) +} + +fn get_available_volume_space(disk_props: &RetainedCFDictionary) -> u64 { + // We prefer `AvailableCapacityForImportantUsage` over `AvailableCapacity` because + // it takes more of the system's properties into account, like the trash, system-managed caches, + // etc. It generally also returns higher values too, because of the above, so it's a more accurate + // representation of what the system _could_ still use. + unsafe { + get_int_value( + disk_props.inner(), + DictKey::Extern(ffi::kCFURLVolumeAvailableCapacityForImportantUsageKey), + ) + .filter(|bytes| *bytes != 0) + .or_else(|| { + get_int_value( + disk_props.inner(), + DictKey::Extern(ffi::kCFURLVolumeAvailableCapacityKey), + ) + }) + } + .unwrap_or_default() as u64 +} + +pub(super) enum DictKey { + Extern(CFStringRef), + #[cfg(target_os = "macos")] + Defined(&'static str), +} + +unsafe fn get_dict_value<T, F: FnOnce(*const c_void) -> Option<T>>( + dict: CFDictionaryRef, + key: DictKey, + callback: F, +) -> Option<T> { + #[cfg(target_os = "macos")] + let _defined; + let key = match key { + DictKey::Extern(val) => val, + #[cfg(target_os = "macos")] + DictKey::Defined(val) => { + _defined = CFReleaser::new(cfs::CFStringCreateWithBytesNoCopy( + kCFAllocatorDefault, + val.as_ptr(), + val.len() as _, + cfs::kCFStringEncodingUTF8, + false as _, + core_foundation_sys::base::kCFAllocatorNull, + ))?; + + _defined.inner() + } + }; + + let mut value = std::ptr::null(); + if CFDictionaryGetValueIfPresent(dict, key.cast(), &mut value) != 0 { + callback(value) + } else { + None + } +} + +pub(super) unsafe fn get_str_value(dict: CFDictionaryRef, key: DictKey) -> Option<String> { + get_dict_value(dict, key, |v| { + let v = v as cfs::CFStringRef; + + let len_utf16 = cfs::CFStringGetLength(v) as usize; + let len_bytes = len_utf16 * 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: DictKey) -> Option<bool> { + get_dict_value(dict, key, |v| Some(v as CFBooleanRef == kCFBooleanTrue)) +} + +unsafe fn get_int_value(dict: CFDictionaryRef, key: DictKey) -> Option<i64> { + get_dict_value(dict, key, |v| { + let mut val: i64 = 0; + if CFNumberGetValue( + v.cast(), + core_foundation_sys::number::kCFNumberSInt64Type, + &mut val as *mut i64 as *mut c_void, + ) { + Some(val) + } else { + None + } + }) +} + +unsafe fn new_disk( + mount_point: PathBuf, + volume_url: RetainedCFURL, + c_disk: libc::statfs, + disk_props: &RetainedCFDictionary, +) -> Option<Disk> { + // IOKit is not available on any but the most recent (16+) iOS and iPadOS versions. + // Due to this, we can't query the medium type. All iOS devices use flash-based storage + // so we just assume the disk type is an SSD until Rust has a way to conditionally link to + // IOKit in more recent deployment versions. + #[cfg(target_os = "macos")] + let type_ = crate::sys::inner::disk::get_disk_type(&c_disk).unwrap_or(DiskType::Unknown(-1)); + #[cfg(not(target_os = "macos"))] + let type_ = DiskType::SSD; + + // Note: Since we requested these properties from the system, we don't expect + // these property retrievals to fail. + + let name = get_str_value( + disk_props.inner(), + DictKey::Extern(ffi::kCFURLVolumeNameKey), + ) + .map(OsString::from)?; + + let is_removable = { + let ejectable = get_bool_value( + disk_props.inner(), + DictKey::Extern(ffi::kCFURLVolumeIsEjectableKey), + ) + .unwrap_or_default(); + + let removable = get_bool_value( + disk_props.inner(), + DictKey::Extern(ffi::kCFURLVolumeIsRemovableKey), + ) + .unwrap_or_default(); + + let is_removable = ejectable || removable; + + if is_removable { + is_removable + } else { + // If neither `ejectable` or `removable` return `true`, fallback to checking + // if the disk is attached to the internal system. + let internal = get_bool_value( + disk_props.inner(), + DictKey::Extern(ffi::kCFURLVolumeIsInternalKey), + ) + .unwrap_or_default(); + + !internal + } + }; + + let total_space = get_int_value( + disk_props.inner(), + DictKey::Extern(ffi::kCFURLVolumeTotalCapacityKey), + )? as u64; + + let available_space = get_available_volume_space(disk_props); + + let file_system = IntoIterator::into_iter(c_disk.f_fstypename) + .filter_map(|b| if b != 0 { Some(b as u8) } else { None }) + .collect(); + + Some(Disk { + type_, + name, + file_system, + mount_point, + volume_url, + total_space, + available_space, + is_removable, + }) +} diff --git a/vendor/sysinfo/src/apple/ffi.rs b/vendor/sysinfo/src/apple/ffi.rs index 7a8248537..72822202f 100644 --- a/vendor/sysinfo/src/apple/ffi.rs +++ b/vendor/sysinfo/src/apple/ffi.rs @@ -1,21 +1,31 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use libc::c_void; +use core_foundation_sys::{ + array::CFArrayRef, dictionary::CFDictionaryRef, error::CFErrorRef, string::CFStringRef, + url::CFURLRef, +}; // 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); +#[link(name = "CoreFoundation", kind = "framework")] +extern "C" { + pub fn CFURLCopyResourcePropertiesForKeys( + url: CFURLRef, + keys: CFArrayRef, + error: *mut CFErrorRef, + ) -> CFDictionaryRef; -// #[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; + pub static kCFURLVolumeIsEjectableKey: CFStringRef; + pub static kCFURLVolumeIsRemovableKey: CFStringRef; + pub static kCFURLVolumeAvailableCapacityKey: CFStringRef; + pub static kCFURLVolumeAvailableCapacityForImportantUsageKey: CFStringRef; + pub static kCFURLVolumeTotalCapacityKey: CFStringRef; + pub static kCFURLVolumeNameKey: CFStringRef; + pub static kCFURLVolumeIsLocalKey: CFStringRef; + pub static kCFURLVolumeIsInternalKey: CFStringRef; + pub static kCFURLVolumeIsBrowsableKey: CFStringRef; +} #[cfg_attr(feature = "debug", derive(Eq, Hash, PartialEq))] #[derive(Clone)] diff --git a/vendor/sysinfo/src/apple/macos/component/arm.rs b/vendor/sysinfo/src/apple/macos/component/arm.rs new file mode 100644 index 000000000..328ffebfa --- /dev/null +++ b/vendor/sysinfo/src/apple/macos/component/arm.rs @@ -0,0 +1,179 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::ffi::CStr; + +use core_foundation_sys::array::{CFArrayGetCount, CFArrayGetValueAtIndex}; +use core_foundation_sys::base::{kCFAllocatorDefault, CFRetain}; +use core_foundation_sys::string::{ + kCFStringEncodingUTF8, CFStringCreateWithBytes, CFStringGetCStringPtr, +}; + +use crate::apple::inner::ffi::{ + kHIDPage_AppleVendor, kHIDUsage_AppleVendor_TemperatureSensor, kIOHIDEventTypeTemperature, + matching, IOHIDEventFieldBase, IOHIDEventGetFloatValue, IOHIDEventSystemClientCopyServices, + IOHIDEventSystemClientCreate, IOHIDEventSystemClientSetMatching, IOHIDServiceClientCopyEvent, + IOHIDServiceClientCopyProperty, __IOHIDEventSystemClient, __IOHIDServiceClient, + HID_DEVICE_PROPERTY_PRODUCT, +}; +use crate::sys::utils::CFReleaser; +use crate::ComponentExt; + +pub(crate) struct Components { + pub inner: Vec<Component>, + client: Option<CFReleaser<__IOHIDEventSystemClient>>, +} + +impl Components { + pub(crate) fn new() -> Self { + Self { + inner: vec![], + client: None, + } + } + + pub(crate) fn refresh(&mut self) { + self.inner.clear(); + + unsafe { + let matches = match CFReleaser::new(matching( + kHIDPage_AppleVendor, + kHIDUsage_AppleVendor_TemperatureSensor, + )) { + Some(m) => m, + None => return, + }; + + if self.client.is_none() { + let client = + match CFReleaser::new(IOHIDEventSystemClientCreate(kCFAllocatorDefault)) { + Some(c) => c, + None => return, + }; + // Without this call, client is freed during the execution of the program. It must be kept! + CFRetain(client.inner() as _); + self.client = Some(client); + } + + let client = self.client.as_ref().unwrap(); + + let _ = IOHIDEventSystemClientSetMatching(client.inner(), matches.inner()); + + let services = match CFReleaser::new(IOHIDEventSystemClientCopyServices(client.inner())) + { + Some(s) => s, + None => return, + }; + + let key_ref = match CFReleaser::new(CFStringCreateWithBytes( + kCFAllocatorDefault, + HID_DEVICE_PROPERTY_PRODUCT.as_ptr(), + HID_DEVICE_PROPERTY_PRODUCT.len() as _, + kCFStringEncodingUTF8, + false as _, + )) { + Some(r) => r, + None => return, + }; + + let count = CFArrayGetCount(services.inner()); + + for i in 0..count { + let service = match CFReleaser::new( + CFArrayGetValueAtIndex(services.inner(), i) as *const _ + ) { + Some(s) => s, + None => continue, + }; + + let name = match CFReleaser::new(IOHIDServiceClientCopyProperty( + service.inner(), + key_ref.inner(), + )) { + Some(n) => n, + None => continue, + }; + + let name_ptr = + CFStringGetCStringPtr(name.inner() as *const _, kCFStringEncodingUTF8); + let name_str = CStr::from_ptr(name_ptr).to_string_lossy().to_string(); + + let mut component = Component::new(name_str, None, None, service); + component.refresh(); + + self.inner.push(component); + } + } + } +} + +unsafe impl Send for Components {} +unsafe impl Sync for Components {} + +#[doc = include_str!("../../../../md_doc/component.md")] +pub struct Component { + service: CFReleaser<__IOHIDServiceClient>, + temperature: f32, + label: String, + max: f32, + critical: Option<f32>, +} + +impl Component { + pub(crate) fn new( + label: String, + max: Option<f32>, + critical: Option<f32>, + service: CFReleaser<__IOHIDServiceClient>, + ) -> Self { + Self { + service, + label, + max: max.unwrap_or(0.), + critical, + temperature: 0., + } + } +} + +unsafe impl Send for Component {} +unsafe impl Sync for Component {} + +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) { + unsafe { + let event = match CFReleaser::new(IOHIDServiceClientCopyEvent( + self.service.inner() as *const _, + kIOHIDEventTypeTemperature, + 0, + 0, + )) { + Some(e) => e, + None => return, + }; + + self.temperature = IOHIDEventGetFloatValue( + event.inner(), + IOHIDEventFieldBase(kIOHIDEventTypeTemperature), + ) as _; + if self.temperature > self.max { + self.max = self.temperature; + } + } + } +} diff --git a/vendor/sysinfo/src/apple/macos/component/mod.rs b/vendor/sysinfo/src/apple/macos/component/mod.rs new file mode 100644 index 000000000..50b359e61 --- /dev/null +++ b/vendor/sysinfo/src/apple/macos/component/mod.rs @@ -0,0 +1,13 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub(crate) mod x86; + +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub use self::x86::*; + +#[cfg(target_arch = "aarch64")] +pub(crate) mod arm; + +#[cfg(target_arch = "aarch64")] +pub use self::arm::*; diff --git a/vendor/sysinfo/src/apple/macos/component.rs b/vendor/sysinfo/src/apple/macos/component/x86.rs index 384efb950..415f90455 100644 --- a/vendor/sysinfo/src/apple/macos/component.rs +++ b/vendor/sysinfo/src/apple/macos/component/x86.rs @@ -1,13 +1,13 @@ // Take a look at the license at the top of the repository in the LICENSE file. -use crate::sys::ffi; +use crate::sys::{ffi, macos::utils::IOReleaser}; use crate::ComponentExt; use libc::{c_char, c_int, c_void}; use std::mem; -pub(crate) const COMPONENTS_TEMPERATURE_IDS: &[(&str, &[i8])] = &[ +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" ( @@ -21,33 +21,69 @@ pub(crate) const COMPONENTS_TEMPERATURE_IDS: &[(&str, &[i8])] = &[ pub(crate) struct ComponentFFI { input_structure: ffi::KeyData_t, val: ffi::Val_t, + /// It is the `System::connection`. We need it to not require an extra argument + /// in `ComponentExt::refresh`. + connection: ffi::io_connect_t, } impl ComponentFFI { - fn new(key: &[i8], con: ffi::io_connect_t) -> Option<ComponentFFI> { + fn new(key: &[i8], connection: ffi::io_connect_t) -> Option<ComponentFFI> { unsafe { - get_key_size(con, key) + get_key_size(connection, key) .ok() .map(|(input_structure, val)| ComponentFFI { input_structure, val, + connection, }) } } - fn temperature(&self, con: ffi::io_connect_t) -> Option<f32> { - get_temperature_inner(con, &self.input_structure, &self.val) + fn temperature(&self) -> Option<f32> { + get_temperature_inner(self.connection, &self.input_structure, &self.val) + } +} + +/// Used to get CPU information, not supported on iOS, or inside the default macOS sandbox. +pub(crate) struct Components { + pub inner: Vec<Component>, + connection: Option<IoService>, +} + +impl Components { + pub(crate) fn new() -> Self { + Self { + inner: Vec::with_capacity(2), + connection: IoService::new_connection(), + } + } + + pub(crate) fn refresh(&mut self) { + if let Some(ref connection) = self.connection { + let connection = connection.inner(); + self.inner.clear(); + // getting CPU critical temperature + let critical_temp = + get_temperature(connection, &['T' as i8, 'C' as i8, '0' as i8, 'D' as i8, 0]); + + for (id, v) in COMPONENTS_TEMPERATURE_IDS.iter() { + if let Some(c) = + Component::new((*id).to_owned(), None, critical_temp, v, connection) + { + self.inner.push(c); + } + } + } } } -#[doc = include_str!("../../../md_doc/component.md")] +#[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 { @@ -60,16 +96,13 @@ impl Component { 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, - }) + ffi_part.temperature().map(|temperature| Component { + temperature, + label, + max: max.unwrap_or(temperature), + critical, + ffi_part, + }) } } @@ -91,7 +124,7 @@ impl ComponentExt for Component { } fn refresh(&mut self) { - if let Some(temp) = self.ffi_part.temperature(self.connection) { + if let Some(temp) = self.ffi_part.temperature() { self.temperature = temp; if self.temperature > self.max { self.max = self.temperature; @@ -213,9 +246,81 @@ fn get_temperature_inner( None } -pub(crate) fn get_temperature(con: ffi::io_connect_t, key: &[i8]) -> Option<f32> { +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) } } + +pub(crate) struct IoService(ffi::io_connect_t); + +impl IoService { + fn new(obj: ffi::io_connect_t) -> Option<Self> { + if obj == 0 { + None + } else { + Some(Self(obj)) + } + } + + pub(crate) fn inner(&self) -> ffi::io_connect_t { + self.0 + } + + // code from https://github.com/Chris911/iStats + // Not supported on iOS, or in the default macOS + pub(crate) fn new_connection() -> Option<Self> { + let mut iterator: ffi::io_iterator_t = 0; + + unsafe { + let matching_dictionary = ffi::IOServiceMatching(b"AppleSMC\0".as_ptr() as *const i8); + let result = ffi::IOServiceGetMatchingServices( + ffi::kIOMasterPortDefault, + matching_dictionary, + &mut iterator, + ); + if result != ffi::KIO_RETURN_SUCCESS { + sysinfo_debug!("Error: IOServiceGetMatchingServices() = {}", result); + return None; + } + let iterator = match IOReleaser::new(iterator) { + Some(i) => i, + None => { + sysinfo_debug!("Error: IOServiceGetMatchingServices() succeeded but returned invalid descriptor"); + return None; + } + }; + + let device = match IOReleaser::new(ffi::IOIteratorNext(iterator.inner())) { + Some(d) => d, + None => { + sysinfo_debug!("Error: no SMC found"); + return None; + } + }; + + let mut conn = 0; + let result = ffi::IOServiceOpen(device.inner(), libc::mach_task_self(), 0, &mut conn); + if result != ffi::KIO_RETURN_SUCCESS { + sysinfo_debug!("Error: IOServiceOpen() = {}", result); + return None; + } + let conn = IoService::new(conn); + if conn.is_none() { + sysinfo_debug!( + "Error: IOServiceOpen() succeeded but returned invalid descriptor..." + ); + } + conn + } + } +} + +impl Drop for IoService { + fn drop(&mut self) { + unsafe { + ffi::IOServiceClose(self.0); + } + } +} diff --git a/vendor/sysinfo/src/apple/macos/disk.rs b/vendor/sysinfo/src/apple/macos/disk.rs index 7cc5a83ce..3a4372a2f 100644 --- a/vendor/sysinfo/src/apple/macos/disk.rs +++ b/vendor/sysinfo/src/apple/macos/disk.rs @@ -1,199 +1,126 @@ // 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 crate::sys::ffi; +use crate::sys::{ + disk::{get_str_value, DictKey}, + macos::utils::IOReleaser, + utils::CFReleaser, +}; +use crate::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::base::{kCFAllocatorDefault, kCFAllocatorNull}; use core_foundation_sys::string as cfs; -use libc::{c_char, c_int, c_void, statfs}; +use std::ffi::CStr; -use std::ffi::{OsStr, OsString}; -use std::mem; -use std::os::unix::ffi::OsStrExt; -use std::path::PathBuf; -use std::ptr; +pub(crate) fn get_disk_type(disk: &libc::statfs) -> Option<DiskType> { + let characteristics_string = unsafe { + CFReleaser::new(cfs::CFStringCreateWithBytesNoCopy( + kCFAllocatorDefault, + ffi::kIOPropertyDeviceCharacteristicsKey.as_ptr(), + ffi::kIOPropertyDeviceCharacteristicsKey.len() as _, + cfs::kCFStringEncodingUTF8, + false as _, + kCFAllocatorNull, + ))? + }; -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)) - } -} + // Removes `/dev/` from the value. + let bsd_name = unsafe { + CStr::from_ptr(disk.f_mntfromname.as_ptr()) + .to_bytes() + .strip_prefix(b"/dev/") + .or_else(|| { + sysinfo_debug!("unknown disk mount path format"); + None + })? + }; + + // We don't need to wrap this in an auto-releaser because the following call to `IOServiceGetMatchingServices` + // will take ownership of one retain reference. + let matching = + unsafe { ffi::IOBSDNameMatching(ffi::kIOMasterPortDefault, 0, bsd_name.as_ptr().cast()) }; -pub(crate) fn get_disks(session: ffi::DASessionRef) -> Vec<Disk> { - if session.is_null() { - return Vec::new(); + if matching.is_null() { + return None; } - 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<_>>() + let mut service_iterator: ffi::io_iterator_t = 0; + + if unsafe { + ffi::IOServiceGetMatchingServices( + ffi::kIOMasterPortDefault, + matching.cast(), + &mut service_iterator, + ) + } != libc::KERN_SUCCESS + { + return None; } -} -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 -} + // Safety: We checked for success, so there is always a valid iterator, even if its empty. + let service_iterator = unsafe { IOReleaser::new_unchecked(service_iterator) }; -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 + let mut parent_entry: ffi::io_registry_entry_t = 0; + + while let Some(mut current_service_entry) = + IOReleaser::new(unsafe { ffi::IOIteratorNext(service_iterator.inner()) }) + { + // Note: This loop is required in a non-obvious way. Due to device properties existing as a tree + // in IOKit, we may need an arbitrary number of calls to `IORegistryEntryCreateCFProperty` in order to find + // the values we are looking for. The function may return nothing if we aren't deep enough into the registry + // tree, so we need to continue going from child->parent node until its found. + loop { + if unsafe { + ffi::IORegistryEntryGetParentEntry( + current_service_entry.inner(), + ffi::kIOServicePlane.as_ptr().cast(), + &mut parent_entry, + ) + } != libc::KERN_SUCCESS + { + break; } - } 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)) -} + current_service_entry = match IOReleaser::new(parent_entry) { + Some(service) => service, + // There were no more parents left + None => break, + }; -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; - } + let properties_result = unsafe { + CFReleaser::new(ffi::IORegistryEntryCreateCFProperty( + current_service_entry.inner(), + characteristics_string.inner(), + kCFAllocatorDefault, + 0, + )) + }; + + if let Some(device_properties) = properties_result { + let disk_type = unsafe { + super::disk::get_str_value( + device_properties.inner(), + DictKey::Defined(ffi::kIOPropertyMediumTypeKey), + ) + }; - 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; + if let Some(disk_type) = disk_type.and_then(|medium| match medium.as_str() { + _ if medium == ffi::kIOPropertyMediumTypeSolidStateKey => Some(DiskType::SSD), + _ if medium == ffi::kIOPropertyMediumTypeRotationalKey => Some(DiskType::HDD), + _ => None, + }) { + return Some(disk_type); + } else { + // Many external drive vendors do not advertise their device's storage medium. + // + // In these cases, assuming that there were _any_ properties about them registered, we fallback + // to `HDD` when no storage medium is provided by the device instead of `Unknown`. + return Some(DiskType::HDD); } - 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, - }) } + + None } diff --git a/vendor/sysinfo/src/apple/macos/ffi.rs b/vendor/sysinfo/src/apple/macos/ffi.rs index f884701d9..0b9c82cfa 100644 --- a/vendor/sysinfo/src/apple/macos/ffi.rs +++ b/vendor/sysinfo/src/apple/macos/ffi.rs @@ -1,105 +1,111 @@ // 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 core_foundation_sys::base::{mach_port_t, CFAllocatorRef}; +use core_foundation_sys::dictionary::{CFDictionaryRef, CFMutableDictionaryRef}; +use core_foundation_sys::string::CFStringRef; -use libc::{c_char, c_void}; -#[cfg(not(feature = "apple-sandbox"))] -use libc::{mach_port_t, size_t}; +use libc::{c_char, kern_return_t}; -pub(crate) use crate::sys::ffi::*; +// Note: IOKit is only available on MacOS up until very recent iOS versions: https://developer.apple.com/documentation/iokit -#[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. +#[allow(non_camel_case_types)] +pub type io_object_t = mach_port_t; + +#[allow(non_camel_case_types)] +pub type io_iterator_t = io_object_t; +#[allow(non_camel_case_types)] +pub type io_registry_entry_t = io_object_t; +#[allow(non_camel_case_types)] +pub type io_name_t = *const c_char; - // 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 type IOOptionBits = u32; - pub fn IOServiceMatching(a: *const c_char) -> *mut c_void; +#[allow(non_upper_case_globals)] +pub const kIOServicePlane: &str = "IOService\0"; +#[allow(non_upper_case_globals)] +pub const kIOPropertyDeviceCharacteristicsKey: &str = "Device Characteristics"; +#[allow(non_upper_case_globals)] +pub const kIOPropertyMediumTypeKey: &str = "Medium Type"; +#[allow(non_upper_case_globals)] +pub const kIOPropertyMediumTypeSolidStateKey: &str = "Solid State"; +#[allow(non_upper_case_globals)] +pub const kIOPropertyMediumTypeRotationalKey: &str = "Rotational"; +// Note: Obtaining information about disks using IOKIt is allowed inside the default macOS App Sandbox. +#[link(name = "IOKit", kind = "framework")] +extern "C" { pub fn IOServiceGetMatchingServices( - a: mach_port_t, - b: *mut c_void, - c: *mut io_iterator_t, - ) -> i32; + mainPort: mach_port_t, + matching: CFMutableDictionaryRef, + existing: *mut io_iterator_t, + ) -> kern_return_t; 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; -} + pub fn IOObjectRelease(obj: io_object_t) -> 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( + pub fn IORegistryEntryCreateCFProperty( + entry: io_registry_entry_t, + key: CFStringRef, allocator: CFAllocatorRef, - session: DASessionRef, - path: *const c_char, - ) -> DADiskRef; - // pub fn DADiskGetBSDName(disk: DADiskRef) -> *const c_char; - - pub fn DADiskCopyDescription(disk: DADiskRef) -> CFMutableDictionaryRef; -} + options: IOOptionBits, + ) -> CFDictionaryRef; + pub fn IORegistryEntryGetParentEntry( + entry: io_registry_entry_t, + plane: io_name_t, + parent: *mut io_registry_entry_t, + ) -> kern_return_t; -pub type DADiskRef = *const __DADisk; -pub type DASessionRef = *const __DASession; + pub fn IOBSDNameMatching( + mainPort: mach_port_t, + options: u32, + bsdName: *const c_char, + ) -> CFMutableDictionaryRef; -// 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 {} + // This is deprecated as of macOS 12.0, but Rust doesn't have a good way to only use the replacement on 12+. + pub static kIOMasterPortDefault: mach_port_t; +} -#[cfg(not(feature = "apple-sandbox"))] +#[cfg(all( + not(feature = "apple-sandbox"), + any(target_arch = "x86", target_arch = "x86_64") +))] mod io_service { - use super::mach_port_t; + use super::{io_object_t, mach_port_t}; + use core_foundation_sys::dictionary::CFMutableDictionaryRef; + use libc::{c_char, kern_return_t, size_t, task_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; + pub type io_service_t = io_object_t; + + #[allow(non_camel_case_types)] + pub type task_port_t = task_t; + + extern "C" { + pub fn IOServiceMatching(a: *const c_char) -> CFMutableDictionaryRef; + + pub fn IOServiceOpen( + device: io_service_t, + owning_task: task_port_t, + type_: u32, + connect: *mut io_connect_t, + ) -> kern_return_t; + + pub fn IOServiceClose(a: io_connect_t) -> kern_return_t; + + #[allow(dead_code)] + pub fn IOConnectCallStructMethod( + connection: mach_port_t, + selector: u32, + inputStruct: *const KeyData_t, + inputStructCnt: size_t, + outputStruct: *mut KeyData_t, + outputStructCnt: *mut size_t, + ) -> kern_return_t; + } #[cfg_attr(feature = "debug", derive(Debug, Eq, Hash, PartialEq))] #[repr(C)] @@ -143,8 +149,13 @@ mod io_service { pub bytes: [i8; 32], // SMCBytes_t } + #[allow(dead_code)] pub const KERNEL_INDEX_SMC: i32 = 2; + + #[allow(dead_code)] pub const SMC_CMD_READ_KEYINFO: u8 = 9; + + #[allow(dead_code)] pub const SMC_CMD_READ_BYTES: u8 = 5; pub const KIO_RETURN_SUCCESS: i32 = 0; @@ -153,4 +164,128 @@ mod io_service { #[cfg(feature = "apple-sandbox")] mod io_service {} +#[cfg(all( + not(feature = "apple-sandbox"), + any(target_arch = "x86", target_arch = "x86_64") +))] +pub use io_service::*; + +#[cfg(all(not(feature = "apple-sandbox"), target_arch = "aarch64"))] +mod io_service { + use std::ptr::null; + + use core_foundation_sys::array::CFArrayRef; + use core_foundation_sys::base::{CFAllocatorRef, CFRelease}; + use core_foundation_sys::dictionary::{ + kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks, CFDictionaryCreate, + CFDictionaryRef, + }; + use core_foundation_sys::number::{kCFNumberSInt32Type, CFNumberCreate}; + use core_foundation_sys::string::{CFStringCreateWithCString, CFStringRef}; + + #[repr(C)] + pub struct __IOHIDServiceClient(libc::c_void); + + pub type IOHIDServiceClientRef = *const __IOHIDServiceClient; + + #[repr(C)] + pub struct __IOHIDEventSystemClient(libc::c_void); + + pub type IOHIDEventSystemClientRef = *const __IOHIDEventSystemClient; + + #[repr(C)] + pub struct __IOHIDEvent(libc::c_void); + + pub type IOHIDEventRef = *const __IOHIDEvent; + + #[allow(non_upper_case_globals)] + pub const kIOHIDEventTypeTemperature: i64 = 15; + + #[inline] + #[allow(non_snake_case)] + pub fn IOHIDEventFieldBase(event_type: i64) -> i64 { + event_type << 16 + } + + #[cfg(not(feature = "apple-sandbox"))] + extern "C" { + pub fn IOHIDEventSystemClientCreate(allocator: CFAllocatorRef) + -> IOHIDEventSystemClientRef; + + pub fn IOHIDEventSystemClientSetMatching( + client: IOHIDEventSystemClientRef, + matches: CFDictionaryRef, + ) -> i32; + + pub fn IOHIDEventSystemClientCopyServices(client: IOHIDEventSystemClientRef) -> CFArrayRef; + + pub fn IOHIDServiceClientCopyProperty( + service: IOHIDServiceClientRef, + key: CFStringRef, + ) -> CFStringRef; + + pub fn IOHIDServiceClientCopyEvent( + service: IOHIDServiceClientRef, + v0: i64, + v1: i32, + v2: i64, + ) -> IOHIDEventRef; + + pub fn IOHIDEventGetFloatValue(event: IOHIDEventRef, field: i64) -> f64; + } + + pub(crate) const HID_DEVICE_PROPERTY_PRODUCT: &[u8] = b"Product\0"; + + pub(crate) const HID_DEVICE_PROPERTY_PRIMARY_USAGE: &[u8] = b"PrimaryUsage\0"; + pub(crate) const HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE: &[u8] = b"PrimaryUsagePage\0"; + + #[allow(non_upper_case_globals)] + pub(crate) const kHIDPage_AppleVendor: i32 = 0xff00; + + #[allow(non_upper_case_globals)] + pub(crate) const kHIDUsage_AppleVendor_TemperatureSensor: i32 = 0x0005; + + pub(crate) fn matching(page: i32, usage: i32) -> CFDictionaryRef { + unsafe { + let keys = [ + CFStringCreateWithCString( + null() as *const _, + HID_DEVICE_PROPERTY_PRIMARY_USAGE_PAGE.as_ptr() as *const _, + 0, + ), + CFStringCreateWithCString( + null() as *const _, + HID_DEVICE_PROPERTY_PRIMARY_USAGE.as_ptr() as *const _, + 0, + ), + ]; + + let nums = [ + CFNumberCreate(null(), kCFNumberSInt32Type, &page as *const _ as *const _), + CFNumberCreate(null(), kCFNumberSInt32Type, &usage as *const _ as *const _), + ]; + + let dict = CFDictionaryCreate( + null(), + &keys as *const _ as *const _, + &nums as *const _ as *const _, + 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks, + ); + + for key in keys { + CFRelease(key as _); + } + + for num in nums { + CFRelease(num as _); + } + + dict + } + } +} + +#[cfg(all(not(feature = "apple-sandbox"), target_arch = "aarch64"))] pub use io_service::*; diff --git a/vendor/sysinfo/src/apple/macos/mod.rs b/vendor/sysinfo/src/apple/macos/mod.rs index 172bbfddc..856c5931d 100644 --- a/vendor/sysinfo/src/apple/macos/mod.rs +++ b/vendor/sysinfo/src/apple/macos/mod.rs @@ -2,16 +2,19 @@ pub mod disk; pub mod ffi; +pub(crate) mod utils; #[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 index f146126cf..fff9c1f71 100644 --- a/vendor/sysinfo/src/apple/macos/process.rs +++ b/vendor/sysinfo/src/apple/macos/process.rs @@ -30,7 +30,7 @@ pub struct Process { old_stime: u64, start_time: u64, run_time: u64, - updated: bool, + pub(crate) updated: bool, cpu_usage: f32, user_id: Option<Uid>, group_id: Option<Gid>, @@ -184,6 +184,20 @@ impl ProcessExt for Process { fn group_id(&self) -> Option<Gid> { self.group_id } + + fn wait(&self) { + let mut status = 0; + // attempt waiting + unsafe { + if libc::waitpid(self.pid.0, &mut status, 0) < 0 { + // attempt failed (non-child process) so loop until process ends + let duration = std::time::Duration::from_millis(10); + while kill(self.pid.0, 0) == 0 { + std::thread::sleep(duration); + } + } + } + } } #[allow(deprecated)] // Because of libc::mach_absolute_time. @@ -202,7 +216,9 @@ pub(crate) fn compute_cpu_usage( .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; + if total_time_diff > 0 { + p.cpu_usage = (total_time_diff as f64 / time_interval * 100.) as f32; + } } else { p.cpu_usage = 0.; } @@ -236,7 +252,6 @@ pub(crate) fn compute_cpu_usage( }; } } - p.updated = true; } /*pub fn set_time(p: &mut Process, utime: u64, stime: u64) { @@ -247,18 +262,6 @@ pub(crate) fn compute_cpu_usage( 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 @@ -274,12 +277,22 @@ unsafe fn get_task_info(pid: Pid) -> libc::proc_taskinfo { } #[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 - // } +fn check_if_pid_is_alive(pid: Pid, check_if_alive: bool) -> bool { + // In case we are iterating all pids we got from `proc_listallpids`, then + // there is no point checking if the process is alive since it was returned + // from this function. + if !check_if_alive { + return true; + } + unsafe { + if kill(pid.0, 0) == 0 { + return true; + } + // `kill` failed but it might not be because the process is dead. + let errno = libc::__error(); + // If errno is equal to ESCHR, it means the process is dead. + !errno.is_null() && *errno != libc::ESRCH + } } #[inline] @@ -293,91 +306,52 @@ fn do_get_env_path(env: &str, root: &mut PathBuf, check: &mut bool) { } } -pub(crate) fn update_process( - wrap: &Wrap, +unsafe fn get_bsd_info(pid: Pid) -> Option<libc::proc_bsdinfo> { + 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 _ + { + None + } else { + Some(info) + } +} + +unsafe fn create_new_process( pid: Pid, mut size: size_t, - time_interval: Option<f64>, now: u64, refresh_kind: ProcessRefreshKind, + info: Option<libc::proc_bsdinfo>, ) -> 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 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 info = match info { + Some(info) => info, + None => { let mut buffer: Vec<u8> = Vec::with_capacity(libc::PROC_PIDPATHINFO_MAXSIZE as _); match libc::proc_pidpath( pid.0, @@ -399,158 +373,228 @@ pub(crate) fn update_process( } 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 parent = match info.pbi_ppid as i32 { + 0 => None, + p => Some(Pid(p)), + }; + + let mut proc_args = Vec::with_capacity(size as _); + 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 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 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); + 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); } - start = cp; - let mut c = 0; - let mut cmd = Vec::with_capacity(n_args as usize); - while c < n_args && cp < ptr.add(size) { + 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 { - c += 1; - cmd.push(get_unchecked_str(cp, start)); + 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) + } - #[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; + p.virtual_memory = task_info.pti_virtual_size; - 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, - ) + 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)) +} + +pub(crate) fn update_process( + wrap: &Wrap, + pid: Pid, + size: size_t, + time_interval: Option<f64>, + now: u64, + refresh_kind: ProcessRefreshKind, + check_if_alive: bool, +) -> Result<Option<Process>, ()> { + 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. + return if check_if_pid_is_alive(pid, check_if_alive) { + p.updated = true; + Ok(None) } else { - get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) + Err(()) + }; + } + if let Some(info) = get_bsd_info(pid) { + if info.pbi_start_tvsec != p.start_time { + // We don't it to be removed, just replaced. + p.updated = true; + // The owner of this PID changed. + return create_new_process(pid, size, now, refresh_kind, Some(info)); } + } + 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 { - get_environ(ptr, cp, size, PathBuf::new(), do_get_env_path) + // It very likely means that the process is dead... + if check_if_pid_is_alive(pid, check_if_alive) { + (0, 0, Some(ThreadStatus::Running)) + } else { + return Err(()); + } }; - 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.status = thread_status; - p.memory = task_info.pti_resident_size / 1_000; - p.virtual_memory = task_info.pti_virtual_size / 1_000; + if refresh_kind.cpu() { + compute_cpu_usage(p, task_info, system_time, user_time, time_interval); + } - 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); + p.memory = task_info.pti_resident_size; + p.virtual_memory = task_info.pti_virtual_size; + if refresh_kind.disk_usage() { + update_proc_disk_activity(p); + } + p.updated = true; + return Ok(None); } - Ok(Some(p)) + create_new_process(pid, size, now, refresh_kind, get_bsd_info(pid)) } } @@ -577,6 +621,7 @@ fn update_proc_disk_activity(p: &mut Process) { } } +#[allow(unknown_lints)] #[allow(clippy::uninit_vec)] pub(crate) fn get_proc_list() -> Option<Vec<Pid>> { unsafe { diff --git a/vendor/sysinfo/src/apple/macos/utils.rs b/vendor/sysinfo/src/apple/macos/utils.rs new file mode 100644 index 000000000..ff870db55 --- /dev/null +++ b/vendor/sysinfo/src/apple/macos/utils.rs @@ -0,0 +1,30 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::num::NonZeroU32; + +type IoObject = NonZeroU32; + +pub(crate) struct IOReleaser(IoObject); + +impl IOReleaser { + pub(crate) fn new(obj: u32) -> Option<Self> { + IoObject::new(obj).map(Self) + } + + pub(crate) unsafe fn new_unchecked(obj: u32) -> Self { + // Chance at catching in-development mistakes + debug_assert_ne!(obj, 0); + Self(IoObject::new_unchecked(obj)) + } + + #[inline] + pub(crate) fn inner(&self) -> u32 { + self.0.get() + } +} + +impl Drop for IOReleaser { + fn drop(&mut self) { + unsafe { super::ffi::IOObjectRelease(self.0.get() as _) }; + } +} diff --git a/vendor/sysinfo/src/apple/mod.rs b/vendor/sysinfo/src/apple/mod.rs index daff800a2..fd6fdec2c 100644 --- a/vendor/sysinfo/src/apple/mod.rs +++ b/vendor/sysinfo/src/apple/mod.rs @@ -2,6 +2,7 @@ #[cfg(target_os = "macos")] pub(crate) mod macos; + #[cfg(target_os = "macos")] pub(crate) use self::macos as inner; diff --git a/vendor/sysinfo/src/apple/network.rs b/vendor/sysinfo/src/apple/network.rs index f1316920e..3c4918155 100644 --- a/vendor/sysinfo/src/apple/network.rs +++ b/vendor/sysinfo/src/apple/network.rs @@ -26,6 +26,7 @@ impl Networks { } } + #[allow(unknown_lints)] #[allow(clippy::cast_ptr_alignment)] #[allow(clippy::uninit_vec)] fn update_networks(&mut self) { diff --git a/vendor/sysinfo/src/apple/system.rs b/vendor/sysinfo/src/apple/system.rs index abe617dee..12abbd23b 100644 --- a/vendor/sysinfo/src/apple/system.rs +++ b/vendor/sysinfo/src/apple/system.rs @@ -3,12 +3,8 @@ 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, @@ -32,6 +28,9 @@ use libc::{ vm_statistics64, _SC_PAGESIZE, }; +#[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] +use super::inner::component::Components; + #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] declare_signals! { c_int, @@ -87,55 +86,23 @@ pub struct System { 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>, + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] + components: Components, 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, @@ -192,17 +159,14 @@ impl SystemExt for System { 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(), + page_size_kb: sysconf(_SC_PAGESIZE) as _, + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] + components: Components::new(), 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, @@ -226,8 +190,8 @@ impl SystemExt for System { &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; + self.swap_total = xs.xsu_total; + self.swap_free = xs.xsu_avail; } // get ram info if self.mem_total < 1 { @@ -238,7 +202,6 @@ impl SystemExt for System { &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>(); @@ -257,16 +220,14 @@ impl SystemExt for System { // * 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_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); } } @@ -275,22 +236,9 @@ impl SystemExt for System { #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] fn refresh_components_list(&mut self) {} - #[cfg(all(target_os = "macos", not(feature = "apple-sandbox")))] + #[cfg(not(any(target_os = "ios", 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); - } - } - } + self.components.refresh(); } fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind) { @@ -356,6 +304,7 @@ impl SystemExt for System { time_interval, now, refresh_kind, + false, ) { Ok(x) => x, _ => None, @@ -366,7 +315,8 @@ impl SystemExt for System { entries.into_iter().for_each(|entry| { self.process_list.insert(entry.pid(), entry); }); - self.clear_procs(); + self.process_list + .retain(|_, proc_| std::mem::replace(&mut proc_.updated, false)); } } @@ -390,6 +340,7 @@ impl SystemExt for System { time_interval, now, refresh_kind, + true, ) } { Ok(Some(p)) => { @@ -401,17 +352,8 @@ impl SystemExt for System { } } - #[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); - } + self.disks = unsafe { get_disks() }; } fn refresh_users_list(&mut self) { @@ -491,12 +433,24 @@ impl SystemExt for System { self.swap_total - self.swap_free } + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] fn components(&self) -> &[Component] { - &self.components + &self.components.inner } + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn components(&self) -> &[Component] { + &[] + } + + #[cfg(not(any(target_os = "ios", feature = "apple-sandbox")))] fn components_mut(&mut self) -> &mut [Component] { - &mut self.components + &mut self.components.inner + } + + #[cfg(any(target_os = "ios", feature = "apple-sandbox"))] + fn components_mut(&mut self) -> &mut [Component] { + &mut [] } fn disks(&self) -> &[Disk] { @@ -507,6 +461,13 @@ impl SystemExt for System { &mut self.disks } + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + self.disks.sort_unstable_by(compare); + } + fn uptime(&self) -> u64 { unsafe { let csec = libc::time(::std::ptr::null_mut()); @@ -598,7 +559,7 @@ impl SystemExt for System { && size > 0 { // now create a buffer with the size and get the real value - let mut buf = vec![0_u8; size as usize]; + let mut buf = vec![0_u8; size as _]; if get_sys_value_by_name( b"kern.osproductversion\0", @@ -621,6 +582,10 @@ impl SystemExt for System { } } } + + fn distribution_id(&self) -> String { + std::env::consts::OS.to_owned() + } } impl Default for System { @@ -629,43 +594,6 @@ impl Default for System { } } -// 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]; @@ -737,7 +665,7 @@ fn get_system_info(value: c_int, default: Option<&str>) -> Option<String> { default.map(|s| s.to_owned()) } else { // set the buffer to the correct size - let mut buf = vec![0_u8; size as usize]; + let mut buf = vec![0_u8; size as _]; if sysctl( mib.as_mut_ptr(), diff --git a/vendor/sysinfo/src/apple/utils.rs b/vendor/sysinfo/src/apple/utils.rs index 019295b95..408c02c31 100644 --- a/vendor/sysinfo/src/apple/utils.rs +++ b/vendor/sysinfo/src/apple/utils.rs @@ -1,6 +1,39 @@ // Take a look at the license at the top of the repository in the LICENSE file. +use core_foundation_sys::base::CFRelease; use libc::c_char; +use std::ptr::NonNull; + +// A helper using to auto release the resource got from CoreFoundation. +// More information about the ownership policy for CoreFoundation pelease refer the link below: +// https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/uid/20001148-CJBEJBHH +#[repr(transparent)] +pub(crate) struct CFReleaser<T>(NonNull<T>); + +impl<T> CFReleaser<T> { + pub(crate) fn new(ptr: *const T) -> Option<Self> { + // This cast is OK because `NonNull` is a transparent wrapper + // over a `*const T`. Additionally, mutability doesn't matter with + // pointers here. + NonNull::new(ptr as *mut T).map(Self) + } + + pub(crate) fn inner(&self) -> *const T { + self.0.as_ptr().cast() + } +} + +impl<T> Drop for CFReleaser<T> { + fn drop(&mut self) { + unsafe { CFRelease(self.0.as_ptr().cast()) } + } +} + +// Safety: These are safe to implement because we only wrap non-mutable +// CoreFoundation types, which are generally threadsafe unless noted +// otherwise. +unsafe impl<T> Send for CFReleaser<T> {} +unsafe impl<T> Sync for CFReleaser<T> {} pub(crate) fn cstr_to_rust(c: *const c_char) -> Option<String> { cstr_to_rust_with_size(c, None) @@ -28,7 +61,6 @@ pub(crate) fn cstr_to_rust_with_size(c: *const c_char, size: Option<usize>) -> O } } -#[cfg(target_os = "macos")] pub(crate) fn vec_to_rust(buf: Vec<i8>) -> Option<String> { String::from_utf8( buf.into_iter() diff --git a/vendor/sysinfo/src/c_interface.rs b/vendor/sysinfo/src/c_interface.rs index e7cc5359b..0914a8135 100644 --- a/vendor/sysinfo/src/c_interface.rs +++ b/vendor/sysinfo/src/c_interface.rs @@ -462,7 +462,7 @@ pub extern "C" fn sysinfo_process_current_directory(process: CProcess) -> RStrin pub extern "C" fn sysinfo_rstring_free(s: RString) { if !s.is_null() { unsafe { - let _ = CString::from_raw(s as usize as *mut i8); + let _ = CString::from_raw(s as usize as *mut _); } } } diff --git a/vendor/sysinfo/src/common.rs b/vendor/sysinfo/src/common.rs index 15cbe87be..2710204f5 100644 --- a/vendor/sysinfo/src/common.rs +++ b/vendor/sysinfo/src/common.rs @@ -283,7 +283,7 @@ on Windows as other platforms get this information alongside the Process informa ); } -/// Used to determine what you want to refresh specifically on the [`Process`] type. +/// Used to determine what you want to refresh specifically on the [`Cpu`] 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 @@ -302,7 +302,7 @@ on Windows as other platforms get this information alongside the Process informa /// } /// ``` /// -/// [`Process`]: crate::Process +/// [`Cpu`]: crate::Cpu #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub struct CpuRefreshKind { cpu_usage: bool, @@ -667,7 +667,7 @@ macro_rules! xid { ($(#[$outer:meta])+ $name:ident, $type:ty) => { $(#[$outer])+ #[repr(transparent)] - #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] + #[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] pub struct $name(pub(crate) $type); impl std::ops::Deref for $name { diff --git a/vendor/sysinfo/src/debug.rs b/vendor/sysinfo/src/debug.rs index e2f4361d9..ef460eb49 100644 --- a/vendor/sysinfo/src/debug.rs +++ b/vendor/sysinfo/src/debug.rs @@ -105,7 +105,7 @@ impl fmt::Debug for Networks { f, "Networks {{ {} }}", self.iter() - .map(|x| format!("{:?}", x)) + .map(|x| format!("{x:?}")) .collect::<Vec<_>>() .join(", ") ) diff --git a/vendor/sysinfo/src/freebsd/component.rs b/vendor/sysinfo/src/freebsd/component.rs index 6529be73c..c4e84fe63 100644 --- a/vendor/sysinfo/src/freebsd/component.rs +++ b/vendor/sysinfo/src/freebsd/component.rs @@ -55,9 +55,7 @@ pub unsafe fn get_components(nb_cpus: usize) -> Vec<Component> { 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(); + let id = format!("dev.cpu.{core}.temperature\0").as_bytes().to_vec(); if let Some(temperature) = refresh_component(&id) { components.push(Component { id, diff --git a/vendor/sysinfo/src/freebsd/process.rs b/vendor/sysinfo/src/freebsd/process.rs index b3302edbe..b0dda0f76 100644 --- a/vendor/sysinfo/src/freebsd/process.rs +++ b/vendor/sysinfo/src/freebsd/process.rs @@ -5,6 +5,8 @@ use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, use std::fmt; use std::path::{Path, PathBuf}; +use libc::kill; + use super::utils::{get_sys_value_str, WrapMap}; #[doc(hidden)] @@ -141,6 +143,20 @@ impl ProcessExt for Process { fn group_id(&self) -> Option<Gid> { Some(self.group_id) } + + fn wait(&self) { + let mut status = 0; + // attempt waiting + unsafe { + if libc::waitpid(self.pid.0, &mut status, 0) < 0 { + // attempt failed (non-child process) so loop until process ends + let duration = std::time::Duration::from_millis(10); + while kill(self.pid.0, 0) == 0 { + std::thread::sleep(duration); + } + } + } + } } pub(crate) unsafe fn get_process_data( @@ -171,28 +187,34 @@ pub(crate) unsafe fn get_process_data( 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; + let virtual_memory = kproc.ki_size as _; + let memory = (kproc.ki_rssize as u64).saturating_mul(page_size as _); // FIXME: This is to get the "real" run time (in micro-seconds). // let run_time = (kproc.ki_runtime + 5_000) / 10_000; + let start_time = kproc.ki_start.tv_sec as u64; + 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 _; + // If the `start_time` we just got is different from the one stored, it means it's not the + // same process. + if proc_.start_time == start_time { + 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); + + 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); } - - return Ok(None); } // This is a new process, we need to get more information! @@ -222,7 +244,6 @@ pub(crate) unsafe fn get_process_data( // .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, @@ -249,6 +270,6 @@ pub(crate) unsafe fn get_process_data( old_read_bytes: 0, written_bytes: kproc.ki_rusage.ru_oublock as _, old_written_bytes: 0, - updated: true, + updated: false, })) } diff --git a/vendor/sysinfo/src/freebsd/system.rs b/vendor/sysinfo/src/freebsd/system.rs index 16da2de50..12298bbcf 100644 --- a/vendor/sysinfo/src/freebsd/system.rs +++ b/vendor/sysinfo/src/freebsd/system.rs @@ -126,11 +126,8 @@ impl SystemExt for System { frequency = get_frequency_for_cpu(pos); } } - self.cpus.push(Cpu::new( - format!("cpu {}", pos), - vendor_id.clone(), - frequency, - )); + 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(); @@ -307,6 +304,13 @@ impl SystemExt for System { &mut self.disks } + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + self.disks.sort_unstable_by(compare); + } + fn uptime(&self) -> u64 { unsafe { let csec = libc::time(::std::ptr::null_mut()); @@ -354,6 +358,10 @@ impl SystemExt for System { fn os_version(&self) -> Option<String> { self.system_info.get_os_release() } + + fn distribution_id(&self) -> String { + std::env::consts::OS.to_owned() + } } impl Default for System { @@ -377,10 +385,6 @@ impl System { #[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(); @@ -404,7 +408,8 @@ impl System { }; // We remove all processes that don't exist anymore. - self.process_list.retain(|_, v| v.updated); + self.process_list + .retain(|_, v| std::mem::replace(&mut v.updated, false)); for (kproc, proc_) in procs { self.add_missing_proc_info(kd, kproc, proc_); @@ -651,8 +656,8 @@ impl SystemInfo { ) }); ( - used.saturating_mul(self.page_size as _) / 1_000, - total.saturating_mul(self.page_size as _) / 1_000, + used.saturating_mul(self.page_size as _), + total.saturating_mul(self.page_size as _), ) } } @@ -661,14 +666,14 @@ impl SystemInfo { 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; + return nb_pages.saturating_mul(self.page_size as _); } // 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 + total_memory } } @@ -686,10 +691,9 @@ impl SystemInfo { if let Some(arc_size) = self.zfs.arc_size() { mem_wire -= arc_size; } - let used = mem_active + mem_active .saturating_mul(self.page_size as _) - .saturating_add(mem_wire); - used / 1_000 + .saturating_add(mem_wire) } } @@ -705,11 +709,10 @@ impl SystemInfo { 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 + buffers_mem + .saturating_add(inactive_mem.saturating_mul(self.page_size as _)) + .saturating_add(cached_mem.saturating_mul(self.page_size as _)) + .saturating_add(free_mem.saturating_mul(self.page_size as _)) } } @@ -730,8 +733,10 @@ impl SystemInfo { 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 mut tmp: u64 = new_cp_time[i] as _; + total_new += tmp; + tmp = old_cp_time[i] as _; + total_old += tmp; } let total_diff = total_new - total_old; diff --git a/vendor/sysinfo/src/freebsd/utils.rs b/vendor/sysinfo/src/freebsd/utils.rs index 00de3e9d9..5745a37f6 100644 --- a/vendor/sysinfo/src/freebsd/utils.rs +++ b/vendor/sysinfo/src/freebsd/utils.rs @@ -169,7 +169,7 @@ pub(crate) fn get_sys_value_str_by_name(name: &[u8]) -> Option<String> { && 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]; + let mut buf: Vec<libc::c_char> = vec![0; size as _]; if libc::sysctlbyname( name.as_ptr() as *const c_char, @@ -210,7 +210,7 @@ pub(crate) fn get_system_info(mib: &[c_int], default: Option<&str>) -> Option<St 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]; + let mut buf: Vec<libc::c_char> = vec![0; size as _]; if libc::sysctl( mib.as_ptr(), @@ -287,7 +287,7 @@ pub(crate) unsafe fn get_frequency_for_cpu(cpu_nb: c_int) -> u64 { // 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(), + format!("dev.cpu.{cpu_nb}.freq\0").as_bytes(), &mut frequency, ) { frequency = 0; diff --git a/vendor/sysinfo/src/lib.rs b/vendor/sysinfo/src/lib.rs index 977de23a3..0800d2562 100644 --- a/vendor/sysinfo/src/lib.rs +++ b/vendor/sysinfo/src/lib.rs @@ -7,6 +7,7 @@ #![allow(clippy::upper_case_acronyms)] #![allow(clippy::non_send_fields_in_send_ty)] #![allow(renamed_and_removed_lints)] +#![allow(clippy::assertions_on_constants)] #![allow(unknown_lints)] #[macro_use] @@ -328,9 +329,10 @@ mod test { #[test] fn check_system_info() { + let s = System::new(); + // 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 @@ -345,6 +347,8 @@ mod test { .expect("Failed to get long OS version") .is_empty()); } + + assert!(!s.distribution_id().is_empty()); } #[test] @@ -429,7 +433,6 @@ mod test { // 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; @@ -441,7 +444,48 @@ mod test { } s.refresh_cpu(); for proc_ in s.cpus() { - assert_ne!(proc_.frequency(), 0); + assert_eq!(proc_.frequency(), 0); + } + // In a VM, it'll fail. + if std::env::var("APPLE_CI").is_err() && std::env::var("FREEBSD_CI").is_err() { + s.refresh_cpu_specifics(CpuRefreshKind::everything()); + for proc_ in s.cpus() { + assert_ne!(proc_.frequency(), 0); + } + } + } + + // In case `Process::updated` is misused, `System::refresh_processes` might remove them + // so this test ensures that it doesn't happen. + #[test] + fn check_refresh_process_update() { + if !System::IS_SUPPORTED { + return; } + let mut s = System::new_all(); + let total = s.processes().len() as isize; + s.refresh_processes(); + let new_total = s.processes().len() as isize; + // There should be almost no difference in the processes count. + assert!( + (new_total - total).abs() <= 5, + "{} <= 5", + (new_total - total).abs() + ); + } + + // We ensure that the `Process` cmd information is retrieved as expected. + #[test] + fn check_cmd_line() { + if !System::IS_SUPPORTED { + return; + } + let mut sys = System::new(); + sys.refresh_processes_specifics(ProcessRefreshKind::new()); + + assert!(sys + .processes() + .iter() + .any(|(_, process)| !process.cmd().is_empty())); } } diff --git a/vendor/sysinfo/src/linux/component.rs b/vendor/sysinfo/src/linux/component.rs index 3a10588e5..7815103b4 100644 --- a/vendor/sysinfo/src/linux/component.rs +++ b/vendor/sysinfo/src/linux/component.rs @@ -1,143 +1,309 @@ // Take a look at the license at the top of the repository in the LICENSE file. +// Information about values readable from `hwmon` sysfs. +// +// Values in /sys/class/hwmonN are `c_long` or `c_ulong` +// transposed to rust we only read `u32` or `i32` values. use crate::ComponentExt; use std::collections::HashMap; -use std::fs::{metadata, read_dir, File}; +use std::fs::{read_dir, File}; use std::io::Read; use std::path::{Path, PathBuf}; #[doc = include_str!("../../md_doc/component.md")] +#[derive(Default)] pub struct Component { - temperature: f32, - max: f32, - critical: Option<f32>, + /// Optional associated device of a `Component`. + device_model: Option<String>, + /// The chip name. + /// + /// Kernel documentation extract: + /// ```txt + /// This should be a short, lowercase string, not containing + /// whitespace, dashes, or the wildcard character '*'. + /// This attribute represents the chip name. It is the only + /// mandatory attribute. + /// I2C devices get this attribute created automatically. + /// ``` + name: String, + /// Temperature current value + /// - Read in: `temp[1-*]_input`. + /// - Unit: read as millidegree Celsius converted to Celsius. + temperature: Option<f32>, + /// Maximum value computed by sysinfo + max: Option<f32>, + /// Max threshold provided by the chip/kernel + /// - Read in:`temp[1-*]_max` + /// - Unit: read as millidegree Celsius converted to Celsius. + threshold_max: Option<f32>, + /// Min threshold provided by the chip/kernel. + /// - Read in:`temp[1-*]_min` + /// - Unit: read as millidegree Celsius converted to Celsius. + threshold_min: Option<f32>, + /// Critical threshold provided by the chip/kernel previous user write. + /// Read in `temp[1-*]_crit`: + /// Typically greater than corresponding temp_max values. + /// - Unit: read as millidegree Celsius converted to Celsius. + threshold_critical: Option<f32>, + /// Sensor type, not common but can exist! + /// + /// Read in: `temp[1-*]_type` Sensor type selection. + /// Values integer: + /// - 1: CPU embedded diode + /// - 2: 3904 transistor + /// - 3: thermal diode + /// - 4: thermistor + /// - 5: AMD AMDSI + /// - 6: Intel PECI + /// Not all types are supported by all chips + sensor_type: Option<TermalSensorType>, + /// Component Label + /// + /// For formating detail see `Component::label` function docstring. + /// + /// ## Linux implementation details + /// + /// read n: `temp[1-*]_label` Suggested temperature channel label. + /// Value: Text string + /// + /// Should only be created if the driver has hints about what + /// this temperature channel is being used for, and user-space + /// doesn't. In all other cases, the label is provided by user-space. label: String, - input_file: PathBuf, + // TODO: not used now. + // Historical minimum temperature + // - Read in:`temp[1-*]_lowest + // - Unit: millidegree Celsius + // + // Temperature critical min value, typically lower than + // corresponding temp_min values. + // - Read in:`temp[1-*]_lcrit` + // - Unit: millidegree Celsius + // + // Temperature emergency max value, for chips supporting more than + // two upper temperature limits. Must be equal or greater than + // corresponding temp_crit values. + // - temp[1-*]_emergency + // - Unit: millidegree Celsius + /// File to read current temperature shall be `temp[1-*]_input` + /// It may be absent but we don't continue if absent. + input_file: Option<PathBuf>, + /// `temp[1-*]_highest file` to read if disponnible highest value. + highest_file: Option<PathBuf>, } +// Read arbitrary data from sysfs. 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 + let mut f = File::open(file).ok()?; + f.read_to_string(&mut reader).ok()?; + reader.truncate(reader.trim_end().len()); + Some(reader) +} + +/// Designed at first for reading an `i32` or `u32` aka `c_long` +/// from a `/sys/class/hwmon` sysfs file. +fn read_number_from_file<N>(file: &Path) -> Option<N> +where + N: std::str::FromStr, +{ + let mut reader = [0u8; 32]; + let mut f = File::open(file).ok()?; + let n = f.read(&mut reader).ok()?; + // parse and trim would complain about `\0`. + let number = &reader[..n]; + let number = std::str::from_utf8(number).ok()?; + let number = number.trim(); + // Assert that we cleaned a little bit that string. + if cfg!(feature = "debug") { + assert!(!number.contains('\n') && !number.contains('\0')); } + number.parse().ok() } -fn is_file<T: AsRef<Path>>(path: T) -> bool { - metadata(path).ok().map(|m| m.is_file()).unwrap_or(false) +// Read a temperature from a `tempN_item` sensor form the sysfs. +// number returned will be in mili-celsius. +// +// Don't call it on `label`, `name` or `type` file. +#[inline] +fn get_temperature_from_file(file: &Path) -> Option<f32> { + let temp = read_number_from_file(file); + convert_temp_celsius(temp) } -fn append_files(components: &mut Vec<Component>, folder: &Path) { - let mut matchings: HashMap<u32, Vec<String>> = HashMap::with_capacity(10); +/// Takes a raw temperature in mili-celsius and convert it to celsius +#[inline] +fn convert_temp_celsius(temp: Option<i32>) -> Option<f32> { + temp.map(|n| (n as f32) / 1000f32) +} - 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), - ); - } - } - } +/// Information about thermal sensor. It may be unavailable as it's +/// kernel module and chip dependant. +enum TermalSensorType { + /// 1: CPU embedded diode + CPUEmbeddedDiode, + /// 2: 3904 transistor + Transistor3904, + /// 3: thermal diode + ThermalDiode, + /// 4: thermistor + Thermistor, + /// 5: AMD AMDSI + AMDAMDSI, + /// 6: Intel PECI + IntelPECI, + /// Not all types are supported by all chips so we keep space for + /// unknown sensors. + Unknown(u8), +} + +impl From<u8> for TermalSensorType { + fn from(input: u8) -> Self { + match input { + 0 => Self::CPUEmbeddedDiode, + 1 => Self::Transistor3904, + 3 => Self::ThermalDiode, + 4 => Self::Thermistor, + 5 => Self::AMDAMDSI, + 6 => Self::IntelPECI, + n => Self::Unknown(n), } - 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)); - } + } +} + +/// Check given `item` dispatch to read the right `file` with the right parsing and store data in +/// given `component`. `id` is provided for `label` creation. +fn fill_component(component: &mut Component, item: &str, folder: &Path, file: &str) { + let hwmon_file = folder.join(file); + match item { + "type" => { + component.sensor_type = + read_number_from_file::<u8>(&hwmon_file).map(TermalSensorType::from) + } + "input" => { + let temperature = get_temperature_from_file(&hwmon_file); + component.input_file = Some(hwmon_file); + component.temperature = temperature; + // Maximum know try to get it from `highest` if not available + // use current temperature + if component.max.is_none() { + component.max = temperature; } } + "label" => component.label = get_file_line(&hwmon_file, 10).unwrap_or_default(), + "highest" => { + component.max = get_temperature_from_file(&hwmon_file).or(component.temperature); + component.highest_file = Some(hwmon_file); + } + "max" => component.threshold_max = get_temperature_from_file(&hwmon_file), + "min" => component.threshold_min = get_temperature_from_file(&hwmon_file), + "crit" => component.threshold_critical = get_temperature_from_file(&hwmon_file), + _ => { + sysinfo_debug!( + "This hwmon-temp file is still not supported! Contributions are appreciated.;) {:?}", + hwmon_file, + ); + } } } 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, + /// Read out `hwmon` info (hardware monitor) from `folder` + /// to get values' path to be used on refresh as well as files containing `max`, + /// `critical value` and `label`. Then we store everything into `components`. + /// + /// Note that a thermal [Component] must have a way to read its temperature. + /// If not, it will be ignored and not added into `components`. + /// + /// ## What is read: + /// + /// - Mandatory: `name` the name of the `hwmon`. + /// - Mandatory: `tempN_input` Drop [Component] if missing + /// - Optional: sensor `label`, in the general case content of `tempN_label` + /// see below for special cases + /// - Optional: `label` + /// - Optional: `/device/model` + /// - Optional: hightest historic value in `tempN_hightest`. + /// - Optional: max threshold value defined in `tempN_max` + /// - Optional: critical threshold value defined in `tempN_crit` + /// + /// Where `N` is a u32 associated to a sensor like `temp1_max`, `temp1_input`. + /// + /// ## Doc to Linux kernel API. + /// + /// Kernel hwmon API: https://www.kernel.org/doc/html/latest/hwmon/hwmon-kernel-api.html + /// DriveTemp kernel API: https://docs.kernel.org/gpu/amdgpu/thermal.html#hwmon-interfaces + /// Amdgpu hwmon interface: https://www.kernel.org/doc/html/latest/hwmon/drivetemp.html + fn from_hwmon(components: &mut Vec<Component>, folder: &Path) -> Option<()> { + let dir = read_dir(folder).ok()?; + let mut matchings: HashMap<u32, Component> = HashMap::with_capacity(10); + for entry in dir.flatten() { + let entry = entry.path(); + let filename = entry.file_name().and_then(|x| x.to_str()).unwrap_or(""); + if entry.is_dir() || !filename.starts_with("temp") { + continue; + } + + let (id, item) = filename.split_once('_')?; + let id = id.get(4..)?.parse::<u32>().ok()?; + + let component = matchings.entry(id).or_insert_with(Component::default); + let name = get_file_line(&folder.join("name"), 16); + component.name = name.unwrap_or_default(); + let device_model = get_file_line(&folder.join("device/model"), 16); + component.device_model = device_model; + fill_component(component, item, folder, filename); + } + let compo = matchings + .into_iter() + .map(|(id, mut c)| { + // sysinfo expose a generic interface with a `label`. + // Problem: a lot of sensors don't have a label or a device model! ¯\_(ツ)_/¯ + // So let's pretend we have a unique label! + // See the table in `Component::label` documentation for the table detail. + c.label = c.format_label("temp", id); + c + }) + // Remove components without `tempN_input` file termal. `Component` doesn't support this kind of sensors yet + .filter(|c| c.input_file.is_some()); + + components.extend(compo); + Some(()) + } + + /// Compute a label out of available information. + /// See the table in `Component::label`'s documentation. + fn format_label(&self, class: &str, id: u32) -> String { + let Component { + device_model, + name, label, - input_file: input_path.to_path_buf(), - max: max.unwrap_or(0.0), - critical, - }; - c.refresh(); - c + .. + } = self; + let has_label = !label.is_empty(); + match (has_label, device_model) { + (true, Some(device_model)) => { + format!("{name} {label} {device_model} {class}{id}") + } + (true, None) => format!("{name} {label}"), + (false, Some(device_model)) => format!("{name} {device_model}"), + (false, None) => format!("{name} {class}{id}"), + } } } impl ComponentExt for Component { fn temperature(&self) -> f32 { - self.temperature + self.temperature.unwrap_or(f32::NAN) } fn max(&self) -> f32 { - self.max + self.max.unwrap_or(f32::NAN) } fn critical(&self) -> Option<f32> { - self.critical + self.threshold_critical } fn label(&self) -> &str { @@ -145,22 +311,28 @@ impl ComponentExt for Component { } 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; - } - } + let current = self + .input_file + .as_ref() + .and_then(|file| get_temperature_from_file(file.as_path())); + // tries to read out kernel highest if not compute something from temperature. + let max = self + .highest_file + .as_ref() + .and_then(|file| get_temperature_from_file(file.as_path())) + .or_else(|| { + let last = self.temperature?; + let current = current?; + Some(last.max(current)) + }); + self.max = max; + self.temperature = current; } } 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/")) { + if let Ok(dir) = read_dir(Path::new("/sys/class/hwmon/")) { for entry in dir.flatten() { let entry = entry.path(); if !entry.is_dir() @@ -172,18 +344,9 @@ pub(crate) fn get_components() -> Vec<Component> { { continue; } - append_files(&mut components, &entry); + Component::from_hwmon(&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 index f3970c318..103f5362a 100644 --- a/vendor/sysinfo/src/linux/cpu.rs +++ b/vendor/sysinfo/src/linux/cpu.rs @@ -4,9 +4,209 @@ use std::collections::HashSet; use std::fs::File; -use std::io::Read; +use std::io::{BufRead, BufReader, Read}; -use crate::CpuExt; +use crate::sys::utils::to_u64; +use crate::{CpuExt, CpuRefreshKind}; + +macro_rules! to_str { + ($e:expr) => { + unsafe { std::str::from_utf8_unchecked($e) } + }; +} + +pub(crate) struct CpusWrapper { + pub(crate) global_cpu: Cpu, + pub(crate) cpus: Vec<Cpu>, + /// 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, + got_cpu_frequency: bool, +} + +impl CpusWrapper { + pub(crate) fn new() -> Self { + Self { + 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), + need_cpus_update: true, + got_cpu_frequency: false, + } + } + + pub(crate) fn refresh_if_needed( + &mut self, + only_update_global_cpu: bool, + refresh_kind: CpuRefreshKind, + ) { + if self.need_cpus_update { + self.refresh(only_update_global_cpu, refresh_kind); + } + } + + pub(crate) fn refresh(&mut self, only_update_global_cpu: bool, refresh_kind: CpuRefreshKind) { + let f = match File::open("/proc/stat") { + Ok(f) => f, + Err(_e) => { + sysinfo_debug!("failed to retrieve CPU information: {:?}", _e); + return; + } + }; + let buf = BufReader::new(f); + + self.need_cpus_update = false; + 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() { + #[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; + } + } + + pub(crate) fn get_global_raw_times(&self) -> (u64, u64) { + (self.global_cpu.total_time, self.global_cpu.old_total_time) + } + + pub(crate) fn len(&self) -> usize { + self.cpus.len() + } + + pub(crate) fn is_empty(&self) -> bool { + self.cpus.is_empty() + } + + pub(crate) fn set_need_cpus_update(&mut self) { + self.need_cpus_update = true; + } +} /// Struct containing values to compute a CPU usage. #[derive(Clone, Copy)] @@ -224,10 +424,6 @@ impl CpuExt for Cpu { } } -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!( diff --git a/vendor/sysinfo/src/linux/disk.rs b/vendor/sysinfo/src/linux/disk.rs index 5a313fd27..6d7fc083c 100644 --- a/vendor/sysinfo/src/linux/disk.rs +++ b/vendor/sysinfo/src/linux/disk.rs @@ -1,7 +1,7 @@ // 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 crate::sys::utils::{get_all_data, to_cpath}; +use crate::{DiskExt, DiskType}; use libc::statvfs; use std::ffi::{OsStr, OsString}; @@ -60,7 +60,7 @@ impl DiskExt for Disk { fn refresh(&mut self) -> bool { unsafe { let mut stat: statvfs = mem::zeroed(); - let mount_point_cpath = utils::to_cpath(&self.mount_point); + let mount_point_cpath = 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); @@ -78,7 +78,7 @@ fn new_disk( file_system: &[u8], removable_entries: &[PathBuf], ) -> Option<Disk> { - let mount_point_cpath = utils::to_cpath(mount_point); + let mount_point_cpath = to_cpath(mount_point); let type_ = find_type_for_device_name(device_name); let mut total = 0; let mut available = 0; @@ -136,9 +136,10 @@ fn find_type_for_device_name(device_name: &OsStr) -> DiskType { 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'); + real_path = match real_path.find('p') { + Some(idx) => &real_path["/dev/".len()..idx], + None => &real_path["/dev/".len()..], + }; } else if device_name_path.starts_with("/dev/root") { // Recursively solve, for example /dev/mmcblk0p1 if real_path != device_name_path { @@ -146,9 +147,10 @@ fn find_type_for_device_name(device_name: &OsStr) -> DiskType { } } 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'); + real_path = match real_path.find('p') { + Some(idx) => &real_path["/dev/".len()..idx], + None => &real_path["/dev/".len()..], + }; } else { // Default case: remove /dev/ and expects the name presents under /sys/block/ // For example, /dev/dm-0 to dm-0 diff --git a/vendor/sysinfo/src/linux/network.rs b/vendor/sysinfo/src/linux/network.rs index 0153c151a..c8da2bcb3 100644 --- a/vendor/sysinfo/src/linux/network.rs +++ b/vendor/sysinfo/src/linux/network.rs @@ -297,7 +297,7 @@ mod test { 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"); + fs::create_dir(itf2_dir).expect("failed to create subdirectory"); let mut interfaces = HashMap::new(); diff --git a/vendor/sysinfo/src/linux/process.rs b/vendor/sysinfo/src/linux/process.rs index 0b06e26f1..d7d61b5bc 100644 --- a/vendor/sysinfo/src/linux/process.rs +++ b/vendor/sysinfo/src/linux/process.rs @@ -11,8 +11,10 @@ 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::sys::system::SystemInfo; +use crate::sys::utils::{ + get_all_data, get_all_data_from_file, realpath, FileCounter, PathHandler, PathPush, +}; use crate::utils::into_iter; use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; @@ -93,7 +95,7 @@ pub struct Process { pub(crate) status: ProcessStatus, /// Tasks run by this process. pub tasks: HashMap<Pid, Process>, - pub(crate) stat_file: Option<File>, + pub(crate) stat_file: Option<FileCounter>, old_read_bytes: u64, old_written_bytes: u64, read_bytes: u64, @@ -101,16 +103,11 @@ pub struct Process { } impl Process { - pub(crate) fn new( - pid: Pid, - parent: Option<Pid>, - start_time_without_boot_time: u64, - info: &SystemInfo, - ) -> Process { + pub(crate) fn new(pid: Pid) -> Process { Process { name: String::with_capacity(20), pid, - parent, + parent: None, cmd: Vec::with_capacity(2), environ: Vec::with_capacity(10), exe: PathBuf::new(), @@ -124,8 +121,8 @@ impl Process { 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), + start_time_without_boot_time: 0, + start_time: 0, run_time: 0, user_id: None, group_id: None, @@ -222,14 +219,16 @@ impl ProcessExt for Process { 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; + fn wait(&self) { + let mut status = 0; + // attempt waiting + unsafe { + if libc::waitpid(self.pid.0, &mut status, 0) < 0 { + // attempt failed (non-child process) so loop until process ends + let duration = std::time::Duration::from_millis(10); + while kill(self.pid.0, 0) == 0 { + std::thread::sleep(duration); } } } @@ -260,9 +259,7 @@ pub(crate) fn set_time(p: &mut Process, utime: u64, stime: u64) { } 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) { + let data = match get_all_data(path.join("io"), 16_384) { Ok(d) => d, Err(_) => return, }; @@ -306,75 +303,50 @@ impl<'a, T> Wrap<'a, T> { 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(()), - }; +#[inline(always)] +fn compute_start_time_without_boot_time(parts: &[&str], info: &SystemInfo) -> u64 { + // To be noted that the start time is invalid here, it still needs to be converted into + // "real" time. + u64::from_str(parts[21]).unwrap_or(0) / info.clock_cycle +} - 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)); - } +fn _get_stat_data(path: &Path, stat_file: &mut Option<FileCounter>) -> Result<String, ()> { + let mut file = File::open(path.join("stat")).map_err(|_| ())?; + let data = get_all_data_from_file(&mut file, 1024).map_err(|_| ())?; + *stat_file = FileCounter::new(file); + Ok(data) +} - let mut tmp = PathBuf::from(path); +#[inline(always)] +fn get_status(p: &mut Process, part: &str) { + p.status = part + .chars() + .next() + .map(ProcessStatus::from) + .unwrap_or_else(|| ProcessStatus::Unknown(0)); +} - 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)?; +fn refresh_user_group_ids<P: PathPush>(p: &mut Process, path: &mut P) { + if let Some((user_id, group_id)) = get_uid_and_gid(path.join("status")) { + p.user_id = Some(Uid(user_id)); + p.group_id = Some(Gid(group_id)); + } +} + +fn retrieve_all_new_process_info( + pid: Pid, + proc_list: &Process, + parts: &[&str], + path: &Path, + info: &SystemInfo, + refresh_kind: ProcessRefreshKind, + uptime: u64, +) -> Process { + let mut p = Process::new(pid); + let mut tmp = PathHandler::new(path); let name = parts[1]; - let parent_pid = if proc_list.pid.0 != 0 { + p.parent = if proc_list.pid.0 != 0 { Some(proc_list.pid) } else { match Pid::from_str(parts[3]) { @@ -383,20 +355,15 @@ pub(crate) fn _get_process_data( } }; - // 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.start_time_without_boot_time = compute_start_time_without_boot_time(parts, info); + p.start_time = p + .start_time_without_boot_time + .saturating_add(info.boot_time); - 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)); - } + refresh_user_group_ids(&mut p, &mut tmp); } if proc_list.pid.0 != 0 { @@ -410,38 +377,28 @@ pub(crate) fn _get_process_data( 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() { + + match tmp.join("exe").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() - }; + // Do not use cmd[0] because it is not the same thing. + // See https://github.com/GuillaumeGomez/sysinfo/issues/697. + p.exe = 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); + + p.cmd = copy_from_file(tmp.join("cmdline")); + p.environ = copy_from_file(tmp.join("environ")); + p.cwd = realpath(tmp.join("cwd")); + p.root = realpath(tmp.join("root")); } update_time_and_memory( path, &mut p, - &parts, + parts, proc_list.memory, proc_list.virtual_memory, uptime, @@ -451,7 +408,91 @@ pub(crate) fn _get_process_data( if refresh_kind.disk_usage() { update_process_disk_activity(&mut p, path); } - Ok((Some(p), pid)) + p +} + +pub(crate) fn _get_process_data( + path: &Path, + proc_list: &mut Process, + pid: Pid, + uptime: u64, + info: &SystemInfo, + refresh_kind: ProcessRefreshKind, +) -> Result<(Option<Process>, Pid), ()> { + let pid = match path.file_name().and_then(|x| x.to_str()).map(Pid::from_str) { + Some(Ok(nb)) if nb != pid => nb, + _ => return Err(()), + }; + + let parent_memory = proc_list.memory; + let parent_virtual_memory = proc_list.virtual_memory; + + let data; + let parts = if let Some(ref mut entry) = proc_list.tasks.get_mut(&pid) { + data = if let Some(mut f) = entry.stat_file.take() { + match get_all_data_from_file(&mut f, 1024) { + Ok(data) => { + // Everything went fine, we put back the file descriptor. + entry.stat_file = Some(f); + data + } + Err(_) => { + // It's possible that the file descriptor is no longer valid in case the + // original process was terminated and another one took its place. + _get_stat_data(path, &mut entry.stat_file)? + } + } + } else { + _get_stat_data(path, &mut entry.stat_file)? + }; + let parts = parse_stat_file(&data).ok_or(())?; + let start_time_without_boot_time = compute_start_time_without_boot_time(&parts, info); + + // It's possible that a new process took this same PID when the "original one" terminated. + // If the start time differs, then it means it's not the same process anymore and that we + // need to get all its information, hence why we check it here. + if start_time_without_boot_time == entry.start_time_without_boot_time { + get_status(entry, parts[2]); + update_time_and_memory( + path, + entry, + &parts, + parent_memory, + parent_virtual_memory, + uptime, + info, + refresh_kind, + ); + if refresh_kind.disk_usage() { + update_process_disk_activity(entry, path); + } + if refresh_kind.user() && entry.user_id.is_none() { + refresh_user_group_ids(entry, &mut PathBuf::from(path)); + } + return Ok((None, pid)); + } + parts + } else { + let mut stat_file = None; + let data = _get_stat_data(path, &mut stat_file)?; + let parts = parse_stat_file(&data).ok_or(())?; + + let mut p = + retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime); + p.stat_file = stat_file; + return Ok((Some(p), pid)); + }; + + // If we're here, it means that the PID still exists but it's a different process. + let p = retrieve_all_new_process_info(pid, proc_list, &parts, path, info, refresh_kind, uptime); + match proc_list.tasks.get_mut(&pid) { + Some(ref mut entry) => **entry = p, + // If it ever enters this case, it means that the process was removed from the HashMap + // in-between with the usage of dark magic. + None => unreachable!(), + } + // Since this PID is already in the HashMap, no need to add it again. + Ok((None, pid)) } #[allow(clippy::too_many_arguments)] @@ -473,9 +514,9 @@ fn update_time_and_memory( 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. + // vsz correspond to the Virtual memory size in bytes. // see: https://man7.org/linux/man-pages/man5/proc.5.html - entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0) / 1_000; + entry.virtual_memory = u64::from_str(parts[22]).unwrap_or(0); if entry.virtual_memory >= parent_virtual_memory { entry.virtual_memory -= parent_virtual_memory; } @@ -504,73 +545,65 @@ pub(crate) fn refresh_procs( 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 - } + let d = match fs::read_dir(path) { + Ok(d) => d, + Err(_) => return false, + }; + let folders = d + .filter_map(|entry| { + let entry = entry.ok()?; + let entry = entry.path(); + + if entry.is_dir() { + Some(entry) + } else { + None + } + }) + .collect::<Vec<_>>(); + if pid.0 == 0 { + let proc_list = Wrap(UnsafeCell::new(proc_list)); + + #[cfg(feature = "multithread")] + use rayon::iter::ParallelIterator; + + into_iter(folders) + .filter_map(|e| { + let (p, _) = _get_process_data( + e.as_path(), + proc_list.get(), + pid, + uptime, + info, + refresh_kind, + ) + .ok()?; + p }) - .collect::<Vec<_>>(); - 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 + .collect::<Vec<_>>() } else { - false - } + let mut updated_pids = Vec::with_capacity(folders.len()); + let new_tasks = folders + .iter() + .filter_map(|e| { + let (p, pid) = + _get_process_data(e.as_path(), proc_list, pid, uptime, info, refresh_kind) + .ok()?; + updated_pids.push(pid); + p + }) + .collect::<Vec<_>>(); + // Sub-tasks are not cleaned up outside so we do it here directly. + proc_list + .tasks + .retain(|&pid, _| updated_pids.iter().any(|&x| x == pid)); + new_tasks + } + .into_iter() + .for_each(|e| { + proc_list.tasks.insert(e.pid(), e); + }); + true } fn copy_from_file(entry: &Path) -> Vec<String> { @@ -618,7 +651,7 @@ fn get_uid_and_gid(file_path: &Path) -> Option<(uid_t, gid_t)> { } } - let status_data = get_all_data(&file_path, 16_385).ok()?; + 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 @@ -653,29 +686,7 @@ fn get_uid_and_gid(file_path: &Path) -> Option<(uid_t, gid_t)> { } } -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>, ()> { +fn parse_stat_file(data: &str) -> Option<Vec<&str>> { // The stat file is "interesting" to parse, because spaces cannot // be used as delimiters. The second field stores the command name // surrounded by parentheses. Unfortunately, whitespace and @@ -687,14 +698,14 @@ fn parse_stat_file(data: &str) -> Result<Vec<&str>, ()> { 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.push(data_it.next()?); + let mut data_it = data_it.next()?.rsplitn(2, ')'); + let data = data_it.next()?; + parts.push(data_it.next()?); parts.extend(data.split_whitespace()); // Remove command name '(' if let Some(name) = parts[1].strip_prefix('(') { parts[1] = name; } - Ok(parts) + Some(parts) } diff --git a/vendor/sysinfo/src/linux/system.rs b/vendor/sysinfo/src/linux/system.rs index 7a8d0a010..3c4fce345 100644 --- a/vendor/sysinfo/src/linux/system.rs +++ b/vendor/sysinfo/src/linux/system.rs @@ -4,7 +4,7 @@ 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::sys::utils::{get_all_data, to_u64}; use crate::{ CpuRefreshKind, Disk, LoadAvg, Networks, Pid, ProcessRefreshKind, RefreshKind, SystemExt, User, }; @@ -63,12 +63,6 @@ pub(crate) fn get_max_nb_fds() -> isize { } } -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); @@ -111,7 +105,7 @@ impl SystemInfo { fn new() -> Self { unsafe { Self { - page_size_kb: (sysconf(_SC_PAGESIZE) / 1024) as _, + page_size_kb: sysconf(_SC_PAGESIZE) as _, clock_cycle: sysconf(_SC_CLK_TCK) as _, boot_time: boot_time(), } @@ -163,22 +157,16 @@ pub struct System { mem_available: u64, mem_buffers: u64, mem_page_cache: u64, + mem_shmem: 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, + cpus: CpusWrapper, } impl System { @@ -193,15 +181,14 @@ impl System { 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()); - } + self.cpus + .refresh_if_needed(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 (new, old) = self.cpus.get_global_raw_times(); let total_time = if old > new { 1 } else { new - old }; ( total_time as f32 / self.cpus.len() as f32, @@ -226,128 +213,7 @@ impl System { } 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; - } - } + self.cpus.refresh(only_update_global_cpu, refresh_kind); } } @@ -356,8 +222,7 @@ impl SystemExt for System { 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 process_list = Process::new(Pid(0)); let mut s = System { process_list, mem_total: 0, @@ -365,33 +230,16 @@ impl SystemExt for System { mem_available: 0, mem_buffers: 0, mem_page_cache: 0, + mem_shmem: 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), + cpus: CpusWrapper::new(), components: Vec::new(), disks: Vec::with_capacity(2), networks: Networks::new(), users: Vec::new(), - need_cpus_update: true, - info, - got_cpu_frequency: false, + info: SystemInfo::new(), }; s.refresh_specifics(refreshes); s @@ -403,14 +251,20 @@ impl SystemExt for System { fn refresh_memory(&mut self) { if let Ok(data) = get_all_data("/proc/meminfo", 16_385) { + let mut mem_available_found = false; + 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("MemAvailable") => { + mem_available_found = true; + &mut self.mem_available + } Some("Buffers") => &mut self.mem_buffers, Some("Cached") => &mut self.mem_page_cache, + Some("Shmem") => &mut self.mem_shmem, Some("SReclaimable") => &mut self.mem_slab_reclaimable, Some("SwapTotal") => &mut self.swap_total, Some("SwapFree") => &mut self.swap_free, @@ -419,10 +273,21 @@ impl SystemExt for System { 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; + *field = value.saturating_mul(1_024); } } } + + // Linux < 3.14 may not have MemAvailable in /proc/meminfo + // So it should fallback to the old way of estimating available memory + // https://github.com/KittyKatt/screenFetch/issues/386#issuecomment-249312716 + if !mem_available_found { + self.mem_available = self.mem_free + + self.mem_buffers + + self.mem_page_cache + + self.mem_slab_reclaimable + - self.mem_shmem; + } } } @@ -441,7 +306,7 @@ impl SystemExt for System { refresh_kind, ); self.clear_procs(refresh_kind); - self.need_cpus_update = true; + self.cpus.set_need_cpus_update(); } fn refresh_process_specifics(&mut self, pid: Pid, refresh_kind: ProcessRefreshKind) -> bool { @@ -469,7 +334,7 @@ impl SystemExt for System { sysinfo_debug!("Cannot compute process CPU usage: no cpus found..."); return found; } - let (new, old) = get_raw_times(&self.global_cpu); + let (new, old) = self.cpus.get_global_raw_times(); let total_time = (if old >= new { 1 } else { new - old }) as f32; let max_cpu_usage = self.get_max_process_cpu_usage(); @@ -513,11 +378,11 @@ impl SystemExt for System { } fn global_cpu_info(&self) -> &Cpu { - &self.global_cpu + &self.cpus.global_cpu } fn cpus(&self) -> &[Cpu] { - &self.cpus + &self.cpus.cpus } fn physical_core_count(&self) -> Option<usize> { @@ -537,11 +402,7 @@ impl SystemExt for System { } fn used_memory(&self) -> u64 { - self.mem_total - - self.mem_free - - self.mem_buffers - - self.mem_page_cache - - self.mem_slab_reclaimable + self.mem_total - self.mem_available } fn total_swap(&self) -> u64 { @@ -573,6 +434,13 @@ impl SystemExt for System { &mut self.disks } + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + self.disks.sort_unstable_by(compare); + } + fn uptime(&self) -> u64 { let content = get_all_data("/proc/uptime", 50).unwrap_or_default(); content @@ -691,6 +559,25 @@ impl SystemExt for System { fn os_version(&self) -> Option<String> { get_system_info_android(InfoType::OsVersion) } + + #[cfg(not(target_os = "android"))] + fn distribution_id(&self) -> String { + get_system_info_linux( + InfoType::DistributionID, + Path::new("/etc/os-release"), + Path::new(""), + ) + .unwrap_or_else(|| std::env::consts::OS.to_owned()) + } + + #[cfg(target_os = "android")] + fn distribution_id(&self) -> String { + // Currently get_system_info_android doesn't support InfoType::DistributionID and always + // returns None. This call is done anyway for consistency with non-Android implementation + // and to suppress dead-code warning for DistributionID on Android. + get_system_info_android(InfoType::DistributionID) + .unwrap_or_else(|| std::env::consts::OS.to_owned()) + } } impl Default for System { @@ -699,16 +586,6 @@ impl Default for System { } } -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: @@ -716,6 +593,9 @@ enum InfoType { /// - Linux: The distributions name Name, OsVersion, + /// Machine-parseable ID of a distribution, see + /// https://www.freedesktop.org/software/systemd/man/os-release.html#ID= + DistributionID, } #[cfg(not(target_os = "android"))] @@ -726,6 +606,7 @@ fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> O let info_str = match info { InfoType::Name => "NAME=", InfoType::OsVersion => "VERSION_ID=", + InfoType::DistributionID => "ID=", }; for line in reader.lines().flatten() { @@ -744,6 +625,10 @@ fn get_system_info_linux(info: InfoType, path: &Path, fallback_path: &Path) -> O let info_str = match info { InfoType::OsVersion => "DISTRIB_RELEASE=", InfoType::Name => "DISTRIB_ID=", + InfoType::DistributionID => { + // lsb-release is inconsistent with os-release and unsupported. + return None; + } }; for line in reader.lines().flatten() { if let Some(stripped) = line.strip_prefix(info_str) { @@ -759,6 +644,10 @@ fn get_system_info_android(info: InfoType) -> Option<String> { let name: &'static [u8] = match info { InfoType::Name => b"ro.product.model\0", InfoType::OsVersion => b"ro.build.version.release\0", + InfoType::DistributionID => { + // Not supported. + return None; + } }; let mut value_buffer = vec![0u8; libc::PROP_VALUE_MAX as usize]; @@ -792,6 +681,7 @@ mod test { fn lsb_release_fallback_android() { assert!(get_system_info_android(InfoType::OsVersion).is_some()); assert!(get_system_info_android(InfoType::Name).is_some()); + assert!(get_system_info_android(InfoType::DistributionID).is_none()); } #[test] @@ -838,6 +728,10 @@ DISTRIB_DESCRIPTION="Ubuntu 20.10" get_system_info_linux(InfoType::Name, &tmp1, Path::new("")), Some("Ubuntu".to_owned()) ); + assert_eq!( + get_system_info_linux(InfoType::DistributionID, &tmp1, Path::new("")), + Some("ubuntu".to_owned()) + ); // Check for the "fallback" path: "/etc/lsb-release" assert_eq!( @@ -848,5 +742,9 @@ DISTRIB_DESCRIPTION="Ubuntu 20.10" get_system_info_linux(InfoType::Name, Path::new(""), &tmp2), Some("Ubuntu".to_owned()) ); + assert_eq!( + get_system_info_linux(InfoType::DistributionID, Path::new(""), &tmp2), + None + ); } } diff --git a/vendor/sysinfo/src/linux/utils.rs b/vendor/sysinfo/src/linux/utils.rs index cd881a3d1..60a9aa2b0 100644 --- a/vendor/sysinfo/src/linux/utils.rs +++ b/vendor/sysinfo/src/linux/utils.rs @@ -2,7 +2,9 @@ use std::fs::File; use std::io::{self, Read, Seek, SeekFrom}; -use std::path::Path; +use std::path::{Path, PathBuf}; + +use crate::sys::system::REMAINING_FILES; pub(crate) fn get_all_data_from_file(file: &mut File, size: usize) -> io::Result<String> { let mut buf = String::with_capacity(size); @@ -17,39 +19,105 @@ pub(crate) fn get_all_data<P: AsRef<Path>>(file_path: P, size: usize) -> io::Res } #[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 { +pub(crate) fn realpath(path: &Path) -> std::path::PathBuf { + match std::fs::read_link(path) { + Ok(f) => f, + Err(_e) => { + sysinfo_debug!("failed to get real path for {:?}: {:?}", path, _e); 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(), + } + } +} + +/// Type used to correctly handle the `REMAINING_FILES` global. +pub(crate) struct FileCounter(File); + +impl FileCounter { + pub(crate) fn new(f: File) -> Option<Self> { + unsafe { + if let Ok(ref mut x) = REMAINING_FILES.lock() { + if **x > 0 { + **x -= 1; + return Some(Self(f)); } + // All file descriptors we were allowed are being used. + } + } + None + } +} + +impl std::ops::Deref for FileCounter { + type Target = File; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +impl std::ops::DerefMut for FileCounter { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Drop for FileCounter { + fn drop(&mut self) { + unsafe { + if let Ok(ref mut x) = crate::sys::system::REMAINING_FILES.lock() { + **x += 1; } } } } + +/// This type is used in `retrieve_all_new_process_info` because we have a "parent" path and +/// from it, we `pop`/`join` every time because it's more memory efficient than using `Path::join`. +pub(crate) struct PathHandler(PathBuf); + +impl PathHandler { + pub(crate) fn new(path: &Path) -> Self { + // `path` is the "parent" for all paths which will follow so we add a fake element at + // the end since every `PathHandler::join` call will first call `pop` internally. + Self(path.join("a")) + } +} + +pub(crate) trait PathPush { + fn join(&mut self, p: &str) -> &Path; +} + +impl PathPush for PathHandler { + fn join(&mut self, p: &str) -> &Path { + self.0.pop(); + self.0.push(p); + self.0.as_path() + } +} + +// This implementation allows to skip one allocation that is done in `PathHandler`. +impl PathPush for PathBuf { + fn join(&mut self, p: &str) -> &Path { + self.push(p); + self.as_path() + } +} + +pub(crate) fn to_u64(v: &[u8]) -> u64 { + let mut x = 0; + + for c in v { + x *= 10; + x += u64::from(c - b'0'); + } + x +} + +/// Converts a path to a NUL-terminated `Vec<u8>` suitable for use with C functions. +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 +} diff --git a/vendor/sysinfo/src/traits.rs b/vendor/sysinfo/src/traits.rs index 3d5eafaa8..7b442990c 100644 --- a/vendor/sysinfo/src/traits.rs +++ b/vendor/sysinfo/src/traits.rs @@ -208,6 +208,18 @@ pub trait ProcessExt: Debug { /// println!("{}", process.exe().display()); /// } /// ``` + /// + /// ### Implementation notes + /// + /// On Linux, this method will return an empty path if there + /// was an error trying to read `/proc/<pid>/exe`. This can + /// happen, for example, if the permission levels or UID namespaces + /// between the caller and target processes are different. + /// + /// It is also the case that `cmd[0]` is _not_ usually a correct + /// replacement for this. + /// A process [may change its `cmd[0]` value](https://man7.org/linux/man-pages/man5/proc.5.html) + /// freely, making this an untrustworthy source of information. fn exe(&self) -> &Path; /// Returns the pid of the process. @@ -258,26 +270,26 @@ pub trait ProcessExt: Debug { /// ``` fn root(&self) -> &Path; - /// Returns the memory usage (in KB). + /// Returns the memory usage (in bytes). /// /// ```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()); + /// println!("{} bytes", process.memory()); /// } /// ``` fn memory(&self) -> u64; - /// Returns the virtual memory usage (in KB). + /// Returns the virtual memory usage (in bytes). /// /// ```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()); + /// println!("{} bytes", process.virtual_memory()); /// } /// ``` fn virtual_memory(&self) -> u64; @@ -400,6 +412,21 @@ pub trait ProcessExt: Debug { /// } /// ``` fn group_id(&self) -> Option<Gid>; + + /// Wait for process termination. + /// + /// ```no_run + /// use sysinfo::{Pid, ProcessExt, System, SystemExt}; + /// + /// let mut s = System::new_all(); + /// + /// if let Some(process) = s.process(Pid::from(1337)) { + /// eprintln!("Waiting for pid 1337"); + /// process.wait(); + /// eprintln!("Pid 1337 exited"); + /// } + /// ``` + fn wait(&self); } /// Contains all the methods of the [`Cpu`][crate::Cpu] struct. @@ -650,7 +677,7 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// 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())`. + /// `refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage())`. /// /// ```no_run /// use sysinfo::{System, SystemExt}; @@ -659,7 +686,7 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// s.refresh_cpu(); /// ``` fn refresh_cpu(&mut self) { - self.refresh_cpu_specifics(CpuRefreshKind::everything()) + self.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()) } /// Refreshes CPUs specific information. @@ -668,10 +695,10 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// list nor users list. /// /// ```no_run - /// use sysinfo::{System, SystemExt}; + /// use sysinfo::{System, SystemExt, CpuRefreshKind}; /// /// let mut s = System::new_all(); - /// s.refresh_all(); + /// s.refresh_cpu_specifics(CpuRefreshKind::everything()); /// ``` fn refresh_cpu_specifics(&mut self, refresh_kind: CpuRefreshKind); @@ -866,6 +893,12 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// If you want only the processes with exactly the given `name`, take a look at /// [`SystemExt::processes_by_exact_name`]. /// + /// **⚠️ 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. + /// /// ```no_run /// use sysinfo::{ProcessExt, System, SystemExt}; /// @@ -891,6 +924,12 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// If you instead want the processes containing `name`, take a look at /// [`SystemExt::processes_by_name`]. /// + /// **⚠️ 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. + /// /// ```no_run /// use sysinfo::{ProcessExt, System, SystemExt}; /// @@ -957,17 +996,17 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// ``` fn physical_core_count(&self) -> Option<usize>; - /// Returns the RAM size in KB. + /// Returns the RAM size in bytes. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); - /// println!("{} KB", s.total_memory()); + /// println!("{} bytes", s.total_memory()); /// ``` fn total_memory(&self) -> u64; - /// Returns the amount of free RAM in KB. + /// Returns the amount of free RAM in bytes. /// /// Generally, "free" memory refers to unallocated memory whereas "available" memory refers to /// memory that is available for (re)use. @@ -979,11 +1018,11 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); - /// println!("{} KB", s.free_memory()); + /// println!("{} bytes", s.free_memory()); /// ``` fn free_memory(&self) -> u64; - /// Returns the amount of available RAM in KB. + /// Returns the amount of available RAM in bytes. /// /// Generally, "free" memory refers to unallocated memory whereas "available" memory refers to /// memory that is available for (re)use. @@ -995,47 +1034,47 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); - /// println!("{} KB", s.available_memory()); + /// println!("{} bytes", s.available_memory()); /// ``` fn available_memory(&self) -> u64; - /// Returns the amount of used RAM in KB. + /// Returns the amount of used RAM in bytes. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); - /// println!("{} KB", s.used_memory()); + /// println!("{} bytes", s.used_memory()); /// ``` fn used_memory(&self) -> u64; - /// Returns the SWAP size in KB. + /// Returns the SWAP size in bytes. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); - /// println!("{} KB", s.total_swap()); + /// println!("{} bytes", s.total_swap()); /// ``` fn total_swap(&self) -> u64; - /// Returns the amount of free SWAP in KB. + /// Returns the amount of free SWAP in bytes. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); - /// println!("{} KB", s.free_swap()); + /// println!("{} bytes", s.free_swap()); /// ``` fn free_swap(&self) -> u64; - /// Returns the amount of used SWAP in KB. + /// Returns the amount of used SWAP in bytes. /// /// ```no_run /// use sysinfo::{System, SystemExt}; /// /// let s = System::new_all(); - /// println!("{} KB", s.used_swap()); + /// println!("{} bytes", s.used_swap()); /// ``` fn used_swap(&self) -> u64; @@ -1063,18 +1102,6 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// ``` 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 @@ -1092,6 +1119,18 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// ```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 disks list. + /// + /// ```no_run + /// use sysinfo::{DiskExt, System, SystemExt}; + /// /// let mut s = System::new_all(); /// for disk in s.disks_mut() { /// disk.refresh(); @@ -1099,6 +1138,17 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// ``` fn disks_mut(&mut self) -> &mut [Disk]; + /// Sort the disk list with the provided callback. + /// + /// Internally, it is using the [`slice::sort_unstable_by`] function, so please refer to it + /// for implementation details. + /// + /// ⚠️ If you use [`SystemExt::refresh_disks_list`], you need to use this method before using + /// [`SystemExt::disks`] or [`SystemExt::disks_mut`] if you want them to be sorted. + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering; + /// Returns the network interfaces object. /// /// ```no_run @@ -1212,6 +1262,23 @@ pub trait SystemExt: Sized + Debug + Default + Send + Sync { /// ``` fn long_os_version(&self) -> Option<String>; + /// Returns the distribution id as defined by os-release, + /// or [`std::env::consts::OS`]. + /// + /// See also + /// - <https://www.freedesktop.org/software/systemd/man/os-release.html#ID=> + /// - <https://doc.rust-lang.org/std/env/consts/constant.OS.html> + /// + /// **Important**: this information is computed every time this function is called. + /// + /// ```no_run + /// use sysinfo::{System, SystemExt}; + /// + /// let s = System::new(); + /// println!("Distribution ID: {:?}", s.distribution_id()); + /// ``` + fn distribution_id(&self) -> String; + /// Returns the system hostname based off DNS /// /// **Important**: this information is computed every time this function is called. @@ -1463,10 +1530,17 @@ pub trait ComponentExt: Debug { /// println!("{}°C", component.temperature()); /// } /// ``` + /// + /// ## Linux + /// + /// Returns `f32::NAN` if it failed to retrieve it. fn temperature(&self) -> f32; /// Returns the maximum temperature of the component (in celsius degree). /// + /// Note: if `temperature` is higher than the current `max`, + /// `max` value will be updated on refresh. + /// /// ```no_run /// use sysinfo::{ComponentExt, System, SystemExt}; /// @@ -1475,6 +1549,11 @@ pub trait ComponentExt: Debug { /// println!("{}°C", component.max()); /// } /// ``` + /// + /// ## Linux + /// + /// May be computed by sysinfo from kernel. + /// Returns `f32::NAN` if it failed to retrieve it. fn max(&self) -> f32; /// Returns the highest temperature before the component halts (in celsius degree). @@ -1487,6 +1566,10 @@ pub trait ComponentExt: Debug { /// println!("{:?}°C", component.critical()); /// } /// ``` + /// + /// ## Linux + /// + /// Critical threshold defined by chip or kernel. fn critical(&self) -> Option<f32>; /// Returns the label of the component. @@ -1499,6 +1582,19 @@ pub trait ComponentExt: Debug { /// println!("{}", component.label()); /// } /// ``` + /// + /// ## Linux + /// + /// Since components informations are retrieved thanks to `hwmon`, + /// the labels are generated as follows. + /// Note: it may change and it was inspired by `sensors` own formatting. + /// + /// | name | label | device_model | id_sensor | Computed label by `sysinfo` | + /// |---------|--------|------------|----------|----------------------| + /// | ✓ | ✓ | ✓ | ✓ | `"{name} {label} {device_model} temp{id}"` | + /// | ✓ | ✓ | ✗ | ✓ | `"{name} {label} {id}"` | + /// | ✓ | ✗ | ✓ | ✓ | `"{name} {device_model}"` | + /// | ✓ | ✗ | ✗ | ✓ | `"{name} temp{id}"` | fn label(&self) -> &str; /// Refreshes component. diff --git a/vendor/sysinfo/src/unknown/process.rs b/vendor/sysinfo/src/unknown/process.rs index c4656d3f5..4a12ec829 100644 --- a/vendor/sysinfo/src/unknown/process.rs +++ b/vendor/sysinfo/src/unknown/process.rs @@ -89,4 +89,6 @@ impl ProcessExt for Process { fn group_id(&self) -> Option<Gid> { None } + + fn wait(&self) {} } diff --git a/vendor/sysinfo/src/unknown/system.rs b/vendor/sysinfo/src/unknown/system.rs index 4603ae737..c205cdf96 100644 --- a/vendor/sysinfo/src/unknown/system.rs +++ b/vendor/sysinfo/src/unknown/system.rs @@ -123,6 +123,13 @@ impl SystemExt for System { &mut [] } + fn sort_disks_by<F>(&mut self, _compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + // does nothing. + } + fn uptime(&self) -> u64 { 0 } @@ -159,6 +166,10 @@ impl SystemExt for System { None } + fn distribution_id(&self) -> String { + std::env::consts::OS.to_owned() + } + fn host_name(&self) -> Option<String> { None } diff --git a/vendor/sysinfo/src/utils.rs b/vendor/sysinfo/src/utils.rs index 578ab61c6..70d96d9fa 100644 --- a/vendor/sysinfo/src/utils.rs +++ b/vendor/sysinfo/src/utils.rs @@ -1,19 +1,5 @@ // 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( @@ -27,7 +13,7 @@ pub(crate) fn to_cpath(path: &std::path::Path) -> Vec<u8> { ), feature = "multithread" ), - not(feature = "apple-sandbox"), + not(all(target_os = "macos", feature = "apple-sandbox")), not(feature = "unknown-ci") ))] pub(crate) fn into_iter<T>(val: T) -> T::Iter @@ -51,7 +37,7 @@ where not(feature = "multithread") ), not(feature = "unknown-ci"), - not(feature = "apple-sandbox") + not(all(target_os = "macos", feature = "apple-sandbox")) ))] pub(crate) fn into_iter<T>(val: T) -> T::IntoIter where diff --git a/vendor/sysinfo/src/windows/disk.rs b/vendor/sysinfo/src/windows/disk.rs index ae393afb2..215fb8c58 100644 --- a/vendor/sysinfo/src/windows/disk.rs +++ b/vendor/sysinfo/src/windows/disk.rs @@ -16,9 +16,10 @@ 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, + PropertyStandardQuery, StorageDeviceSeekPenaltyProperty, IOCTL_STORAGE_QUERY_PROPERTY, + STORAGE_PROPERTY_QUERY, }; -use winapi::um::winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, HANDLE, ULARGE_INTEGER}; +use winapi::um::winnt::{BOOLEAN, FILE_SHARE_READ, FILE_SHARE_WRITE, HANDLE, ULARGE_INTEGER}; #[doc = include_str!("../../md_doc/disk.md")] pub struct Disk { @@ -122,14 +123,23 @@ unsafe fn get_drive_size(mount_point: &[u16]) -> Option<(u64, u64)> { ) != 0 { Some(( - *total_size.QuadPart() as u64, - *available_space.QuadPart() as u64, + *total_size.QuadPart() as _, + *available_space.QuadPart() as _, )) } else { None } } +// FIXME: To be removed once <https://github.com/retep998/winapi-rs/pull/1028> has been merged. +#[allow(non_snake_case)] +#[repr(C)] +struct DEVICE_SEEK_PENALTY_DESCRIPTOR { + Version: DWORD, + Size: DWORD, + IncursSeekPenalty: BOOLEAN, +} + pub(crate) unsafe fn get_disks() -> Vec<Disk> { let drives = GetLogicalDrives(); if drives == 0 { @@ -201,16 +211,12 @@ pub(crate) unsafe fn get_disks() -> Vec<Disk> { 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, + PropertyId: StorageDeviceSeekPenaltyProperty, + QueryType: PropertyStandardQuery, AdditionalParameters: [0], }; - let mut dtd: DEVICE_TRIM_DESCRIPTOR = std::mem::zeroed(); + let mut result: DEVICE_SEEK_PENALTY_DESCRIPTOR = std::mem::zeroed(); let mut dw_size = 0; let type_ = if DeviceIoControl( @@ -218,16 +224,16 @@ pub(crate) unsafe fn get_disks() -> Vec<Disk> { 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 result as *mut DEVICE_SEEK_PENALTY_DESCRIPTOR as *mut c_void, + size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as DWORD, &mut dw_size, std::ptr::null_mut(), ) == 0 - || dw_size != size_of::<DEVICE_TRIM_DESCRIPTOR>() as DWORD + || dw_size != size_of::<DEVICE_SEEK_PENALTY_DESCRIPTOR>() as DWORD { DiskType::Unknown(-1) } else { - let is_ssd = dtd.TrimEnabled != 0; + let is_ssd = result.IncursSeekPenalty == 0; if is_ssd { DiskType::SSD } else { diff --git a/vendor/sysinfo/src/windows/macros.rs b/vendor/sysinfo/src/windows/macros.rs deleted file mode 100644 index b0c024c7c..000000000 --- a/vendor/sysinfo/src/windows/macros.rs +++ /dev/null @@ -1,16 +0,0 @@ -// 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 index fa5d66beb..805e85269 100644 --- a/vendor/sysinfo/src/windows/mod.rs +++ b/vendor/sysinfo/src/windows/mod.rs @@ -3,8 +3,6 @@ mod component; mod cpu; mod disk; -#[macro_use] -mod macros; mod network; mod process; mod system; diff --git a/vendor/sysinfo/src/windows/process.rs b/vendor/sysinfo/src/windows/process.rs index bdc35c53e..7561c658f 100644 --- a/vendor/sysinfo/src/windows/process.rs +++ b/vendor/sysinfo/src/windows/process.rs @@ -1,5 +1,6 @@ // Take a look at the license at the top of the repository in the LICENSE file. +use crate::sys::system::is_proc_running; use crate::sys::utils::to_str; use crate::{DiskUsage, Gid, Pid, ProcessExt, ProcessRefreshKind, ProcessStatus, Signal, Uid}; @@ -48,8 +49,8 @@ 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, + PROCESS_QUERY_INFORMATION, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ, + RTL_OSVERSIONINFOEXW, TOKEN_QUERY, TOKEN_USER, ULARGE_INTEGER, }; impl fmt::Display for ProcessStatus { @@ -67,7 +68,19 @@ fn get_process_handler(pid: Pid) -> Option<HandleWrapper> { } let options = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; - unsafe { HandleWrapper::new(OpenProcess(options, FALSE, pid.0 as DWORD)) } + HandleWrapper::new(unsafe { OpenProcess(options, FALSE, pid.0 as DWORD) }) + .or_else(|| { + sysinfo_debug!("OpenProcess failed, error: {:?}", unsafe { GetLastError() }); + HandleWrapper::new(unsafe { + OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid.0 as DWORD) + }) + }) + .or_else(|| { + sysinfo_debug!("OpenProcess limited failed, error: {:?}", unsafe { + GetLastError() + }); + None + }) } unsafe fn get_process_user_id( @@ -266,11 +279,11 @@ unsafe fn get_h_mod(process_handler: &HandleWrapper, h_mod: &mut *mut c_void) -> ) != 0 } -unsafe fn get_exe(process_handler: &HandleWrapper, h_mod: *mut c_void) -> PathBuf { +unsafe fn get_exe(process_handler: &HandleWrapper) -> PathBuf { let mut exe_buf = [0u16; MAX_PATH + 1]; GetModuleFileNameExW( **process_handler, - h_mod as _, + std::ptr::null_mut(), exe_buf.as_mut_ptr(), MAX_PATH as DWORD + 1, ); @@ -306,7 +319,7 @@ impl Process { String::new() }; - let exe = get_exe(&process_handler, h_mod); + let exe = get_exe(&process_handler); let mut root = exe.clone(); root.pop(); let (cmd, environ, cwd) = match get_process_params(&process_handler) { @@ -316,7 +329,7 @@ impl Process { (Vec::new(), Vec::new(), PathBuf::new()) } }; - let (start_time, run_time) = get_start_and_run_time(&process_handler, now); + 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 { @@ -360,14 +373,8 @@ impl Process { 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 exe = get_exe(&handle); let mut root = exe.clone(); root.pop(); let (cmd, environ, cwd) = match get_process_params(&handle) { @@ -377,7 +384,7 @@ impl Process { (Vec::new(), Vec::new(), PathBuf::new()) } }; - let (start_time, run_time) = get_start_and_run_time(&handle, now); + 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)), @@ -451,6 +458,10 @@ impl Process { pub(crate) fn get_handle(&self) -> Option<HANDLE> { self.handle.as_ref().map(|h| ***h) } + + pub(crate) fn get_start_time(&self) -> Option<u64> { + self.handle.as_ref().map(|handle| get_start_time(***handle)) + } } impl ProcessExt for Process { @@ -537,25 +548,60 @@ impl ProcessExt for Process { fn group_id(&self) -> Option<Gid> { None } + + fn wait(&self) { + if let Some(handle) = self.get_handle() { + while is_proc_running(handle) { + if get_start_time(handle) != self.start_time() { + // PID owner changed so the previous process was finished! + return; + } + std::thread::sleep(std::time::Duration::from_millis(10)); + } + } else { + // In this case, we can't do anything so we just return. + sysinfo_debug!("can't wait on this process so returning"); + } + } } -unsafe fn get_start_and_run_time(handle: &HandleWrapper, now: u64) -> (u64, u64) { +#[inline] +unsafe fn get_process_times(handle: HANDLE) -> u64 { let mut fstart: FILETIME = zeroed(); let mut x = zeroed(); GetProcessTimes( - **handle, + 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); + super::utils::filetime_to_u64(fstart) +} + +#[inline] +fn compute_start(process_times: u64) -> u64 { // 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) + process_times / 10_000_000 - 11_644_473_600 +} + +fn get_start_and_run_time(handle: HANDLE, now: u64) -> (u64, u64) { + unsafe { + let process_times = get_process_times(handle); + let start = compute_start(process_times); + let run_time = check_sub(now, start); + (start, run_time) + } +} + +#[inline] +pub(crate) fn get_start_time(handle: HANDLE) -> u64 { + unsafe { + let process_times = get_process_times(handle); + compute_start(process_times) + } } #[allow(clippy::uninit_vec)] @@ -976,7 +1022,7 @@ pub(crate) fn compute_cpu_usage(p: &mut Process, nb_cpus: u64) { } p.cpu_usage = 100.0 - * (delta_user_time.saturating_add(delta_sys_time) as f32 / denominator as f32) + * (delta_user_time.saturating_add(delta_sys_time) as f32 / denominator) * nb_cpus as f32; } } @@ -1011,8 +1057,8 @@ pub(crate) fn update_memory(p: &mut Process) { 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; + p.memory = pmc.WorkingSetSize as _; + p.virtual_memory = pmc.PrivateUsage as _; } } } diff --git a/vendor/sysinfo/src/windows/system.rs b/vendor/sysinfo/src/windows/system.rs index 6abd30db5..643a7b4bc 100644 --- a/vendor/sysinfo/src/windows/system.rs +++ b/vendor/sysinfo/src/windows/system.rs @@ -9,7 +9,7 @@ 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::process::{get_start_time, update_memory, Process}; use crate::sys::tools::*; use crate::sys::users::get_users; use crate::sys::utils::get_now; @@ -64,6 +64,19 @@ pub struct System { users: Vec<User>, } +static WINDOWS_ELEVEN_BUILD_NUMBER: u32 = 22000; + +impl System { + fn is_windows_eleven(&self) -> bool { + WINDOWS_ELEVEN_BUILD_NUMBER + <= self + .kernel_version() + .unwrap_or_default() + .parse() + .unwrap_or(0) + } +} + // Useful for parallel iterations. struct Wrap<T>(T); @@ -73,7 +86,7 @@ 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, + Ok(n) => n.as_secs().saturating_sub(GetTickCount64() / 1_000), Err(_e) => { sysinfo_debug!("Failed to compute boot time: {:?}", _e); 0 @@ -117,10 +130,10 @@ impl SystemExt for System { ); for (pos, proc_) in self.cpus.iter_mut(refresh_kind).enumerate() { add_english_counter( - format!(r"\Processor({})\% Processor Time", pos), + format!(r"\Processor({pos})\% Processor Time"), query, get_key_used(proc_), - format!("{}_0", pos), + format!("{pos}_0"), ); } } @@ -162,8 +175,8 @@ impl SystemExt for System { 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; + self.mem_total = mem_info.ullTotalPhys as _; + self.mem_available = mem_info.ullAvailPhys as _; let mut perf_info: PERFORMANCE_INFORMATION = zeroed(); if GetPerformanceInfo(&mut perf_info, size_of::<PERFORMANCE_INFORMATION>() as u32) == TRUE @@ -178,8 +191,8 @@ impl SystemExt for System { .CommitTotal .saturating_sub(perf_info.PhysicalTotal), ); - self.swap_total = (swap_total / 1000) as u64; - self.swap_used = (swap_used / 1000) as u64; + self.swap_total = swap_total as _; + self.swap_used = swap_used as _; } } } @@ -190,12 +203,17 @@ impl SystemExt for System { #[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(); + let nb_cpus = self.cpus.len() as u64; + + if let Some(proc_) = self.process_list.get_mut(&pid) { + if let Some(ret) = refresh_existing_process(proc_, nb_cpus, now, refresh_kind) { + return ret; + } + // We need to re-make the process because the PID owner changed. + } if let Some(mut p) = Process::new_from_pid(pid, now, refresh_kind) { - p.update(refresh_kind, self.cpus.len() as u64, now); + p.update(refresh_kind, nb_cpus, now); p.updated = false; self.process_list.insert(pid, p); true @@ -266,10 +284,18 @@ impl SystemExt for System { 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; + if proc_ + .get_start_time() + .map(|start| start == proc_.start_time()) + .unwrap_or(true) + { + proc_.memory = pi.WorkingSetSize as _; + proc_.virtual_memory = pi.VirtualSize as _; + proc_.update(refresh_kind, nb_cpus, now); + return None; + } + // If the PID owner changed, we need to recompute the whole process. + sysinfo_debug!("owner changed for PID {}", proc_.pid()); } let name = get_process_name(&pi, pid); let mut p = Process::new_full( @@ -279,8 +305,8 @@ impl SystemExt for System { } else { None }, - (pi.WorkingSetSize as u64) / 1_000, - (pi.VirtualSize as u64) / 1_000, + pi.WorkingSetSize as _, + pi.VirtualSize as _, name, now, refresh_kind, @@ -386,6 +412,13 @@ impl SystemExt for System { &mut self.disks } + fn sort_disks_by<F>(&mut self, compare: F) + where + F: FnMut(&Disk, &Disk) -> std::cmp::Ordering, + { + self.disks.sort_unstable_by(compare); + } + fn users(&self) -> &[User] { &self.users } @@ -399,7 +432,7 @@ impl SystemExt for System { } fn uptime(&self) -> u64 { - unsafe { GetTickCount64() / 1000 } + unsafe { GetTickCount64() / 1_000 } } fn boot_time(&self) -> u64 { @@ -415,6 +448,14 @@ impl SystemExt for System { } fn long_os_version(&self) -> Option<String> { + if self.is_windows_eleven() { + return get_reg_string_value( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "ProductName", + ) + .map(|product_name| product_name.replace("Windows 10 ", "Windows 11 ")); + } get_reg_string_value( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", @@ -435,23 +476,29 @@ impl SystemExt for System { } 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", - ); + ) + .unwrap_or_default(); + let major = if self.is_windows_eleven() { + 11u32 + } else { + u32::from_le_bytes( + get_reg_value_u32( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + "CurrentMajorVersionNumber", + ) + .unwrap_or_default(), + ) + }; + Some(format!("{major} ({build_number})")) + } - Some(format!( - "{} ({})", - u32::from_le_bytes(major.unwrap_or_default()), - build_number.unwrap_or_default() - )) + fn distribution_id(&self) -> String { + std::env::consts::OS.to_owned() } } @@ -461,7 +508,7 @@ impl Default for System { } } -fn is_proc_running(handle: HANDLE) -> bool { +pub(crate) fn is_proc_running(handle: HANDLE) -> bool { let mut exit_code = 0; unsafe { let ret = GetExitCodeProcess(handle, &mut exit_code); @@ -469,22 +516,30 @@ fn is_proc_running(handle: HANDLE) -> bool { } } -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; +/// If it returns `None`, it means that the PID owner changed and that the `Process` must be +/// completely recomputed. +fn refresh_existing_process( + proc_: &mut Process, + nb_cpus: u64, + now: u64, + refresh_kind: ProcessRefreshKind, +) -> Option<bool> { + if let Some(handle) = proc_.get_handle() { + if get_start_time(handle) != proc_.start_time() { + sysinfo_debug!("owner changed for PID {}", proc_.pid()); + // PID owner changed! + return None; + } + if !is_proc_running(handle) { + return Some(false); } - update_memory(entry); - entry.update(refresh_kind, s.cpus.len() as u64, get_now()); - entry.updated = false; - true } else { - false + return Some(false); } + update_memory(proc_); + proc_.update(refresh_kind, nb_cpus, now); + proc_.updated = false; + Some(true) } #[allow(clippy::size_of_in_element_count)] @@ -495,7 +550,7 @@ pub(crate) fn get_process_name(process: &SYSTEM_PROCESS_INFORMATION, process_id: match process_id.0 { 0 => "Idle".to_owned(), 4 => "System".to_owned(), - _ => format!("<no name> Process {}", process_id), + _ => format!("<no name> Process {process_id}"), } } else { unsafe { |